# -*- coding: utf-8 -*-
"""
limpeza.py — Limpeza e normalização do CSV bruto do MiX Telematics
Suporta automaticamente separador vírgula e ponto-e-vírgula
"""
import pandas as pd
import io

MAPA_TIPO = {
    "(TO) Freada Brusca":         "Freada Brusca",
    "(TO) Excesso de Velocidade": "Excesso de Velocidade",
    "Fora da Faixa Verde VW":     "RPM",
}

LIMITES_MINIMOS = {
    "Freada Brusca":         11,
    "Excesso de Velocidade": 85,
    "RPM":                 2100,
}

COLUNAS_OBRIGATORIAS = [
    "AssetName","EventStartDate","EventStartTime",
    "EventDescription","EventValue","TotalOccurs","StartOdo",
]

class ErroFormatoInesperado(Exception):
    pass

def _detectar_separador(dados_bytes: bytes) -> str:
    try:
        linhas = dados_bytes.decode("utf-8-sig", errors="replace").splitlines()
        linha = linhas[1] if len(linhas) > 1 else linhas[0]
        return "," if linha.count(",") > linha.count(";") else ";"
    except Exception:
        return ","

def _converter_numero_br(serie: pd.Series) -> pd.Series:
    def _conv(v):
        v = str(v).strip().strip('"')
        if v in ('', 'nan', 'None', '-', 'NaN'):
            return None
        if ',' in v:
            v = v.replace('.', '').replace(',', '.')
        elif v.count('.') > 1:
            v = v.replace('.', '')
        try:
            return float(v)
        except ValueError:
            return None
    return serie.apply(_conv)

def limpar_csv(dados_bytes: bytes, limites: dict = None) -> dict:
    """
    Recebe bytes do CSV bruto. Retorna dict com:
      df_limpo, df_descartados, stats, avisos
    """
    if limites is None:
        limites = LIMITES_MINIMOS.copy()

    avisos = []
    sep = _detectar_separador(dados_bytes)
    avisos.append(f"Separador detectado: {'vírgula' if sep == ',' else 'ponto-e-vírgula'}")

    df = pd.read_csv(
        io.BytesIO(dados_bytes), sep=sep,
        encoding="utf-8-sig", dtype=str, on_bad_lines="skip"
    )
    df.columns = [c.strip() for c in df.columns]
    total_bruto = len(df)

    faltando = [c for c in COLUNAS_OBRIGATORIAS if c not in df.columns]
    if faltando:
        raise ErroFormatoInesperado(
            f"Colunas obrigatórias ausentes: {faltando}. "
            f"Encontradas: {list(df.columns)}"
        )

    df = df.dropna(how="all")

    # Separar sem motorista
    df["_mot"] = (
        df["AssetName"].astype(str).str.strip().str.upper()
        .str.replace(r"\s+", " ", regex=True)
    )
    mask_sem = df["_mot"].isin(["UNKNOWN","NAN","","NONE","-"])
    df_desc   = df[mask_sem].copy()
    df        = df[~mask_sem].copy()
    qtd_sem   = len(df_desc)
    if qtd_sem:
        avisos.append(f"{qtd_sem} evento(s) sem motorista identificado — baixe o relatório PDF.")

    # Mapear tipo
    df["tipo"] = df["EventDescription"].map(MAPA_TIPO)
    nao_map = df.loc[df["tipo"].isna(), "EventDescription"].unique().tolist()
    if nao_map:
        avisos.append(f"Categorias não reconhecidas (ignoradas): {nao_map}")
    df = df.dropna(subset=["tipo"])

    # Datas
    df["data"] = pd.to_datetime(df["EventStartDate"], dayfirst=True, errors="coerce")
    inv = df["data"].isna().sum()
    if inv:
        avisos.append(f"{inv} linha(s) com data inválida descartadas.")
    df = df.dropna(subset=["data"])
    df["hora"] = df["EventStartTime"].astype(str).str.strip()

    # Valores
    df["valor"]        = _converter_numero_br(df["EventValue"])
    df["odometro"]     = _converter_numero_br(df["StartOdo"])
    df["total_occurs"] = pd.to_numeric(df["TotalOccurs"], errors="coerce").fillna(1)
    df = df.dropna(subset=["valor"])

    # Limites mínimos
    lim_linha  = df["tipo"].map(limites)
    mask_baixo = df["valor"] < lim_linha
    qtd_baixo  = int(mask_baixo.sum())
    if qtd_baixo:
        avisos.append(f"{qtd_baixo} evento(s) abaixo do limite mínimo descartados.")
    df = df[~mask_baixo].copy()

    df["motorista"] = df["_mot"]

    # Chave única
    df["chave_evento"] = (
        df["motorista"] + "|" +
        df["data"].dt.strftime("%Y-%m-%d") + "|" +
        df["hora"] + "|" +
        df["tipo"] + "|" +
        df["valor"].astype(str) + "|" +
        df["odometro"].fillna(0).astype(str)
    )

    dup = df.duplicated(subset=["chave_evento"]).sum()
    if dup:
        avisos.append(f"{dup} duplicata(s) no próprio arquivo removidas.")
    df = df.drop_duplicates(subset=["chave_evento"])

    df_limpo = df[[
        "chave_evento","motorista","data","hora",
        "tipo","valor","total_occurs"
    ]].reset_index(drop=True)

    # Relatório de descartados
    cols_rel = [c for c in [
        "AssetName","EventStartDate","EventStartTime","EventDescription",
        "EventValue","FleetNumber","F_StartCity","F_StartRegion"
    ] if c in df_desc.columns]
    df_desc_rel = df_desc[cols_rel].rename(columns={
        "AssetName":"Motorista","EventStartDate":"Data","EventStartTime":"Hora",
        "EventDescription":"Tipo de Evento","EventValue":"Valor",
        "FleetNumber":"Frota","F_StartCity":"Cidade","F_StartRegion":"Estado",
    }).reset_index(drop=True)

    stats = {
        "total_bruto":       total_bruto,
        "sem_motorista":     qtd_sem,
        "abaixo_limite":     qtd_baixo,
        "validos":           len(df_limpo),
        "motoristas_unicos": int(df_limpo["motorista"].nunique()),
        "breakdown":         df_limpo["tipo"].value_counts().to_dict(),
        "periodo_inicio":    str(df_limpo["data"].min().date()) if len(df_limpo) else None,
        "periodo_fim":       str(df_limpo["data"].max().date()) if len(df_limpo) else None,
    }

    return {
        "df_limpo":       df_limpo,
        "df_descartados": df_desc_rel,
        "stats":          stats,
        "avisos":         avisos,
    }
