#!/usr/bin/env python3
"""Demo sintetica v3 - clasificador de filas de una planilla de pedidos.

Clasifica cada fila de entrada.csv en exactamente una de:
  - sin_alerta_bajo_estas_reglas : fila no marcada por estas reglas. Si es la
                        fila de referencia de un caso (la primera del grupo
                        contra la que se compara una fila marcada), el motivo
                        lo indica.
  - repeticion_exacta : fila identica, campo por campo, a una fila anterior
                        (la misma fila cargada dos veces).
  - duplicado_probable: mismo cliente + mismo producto + misma fecha, pero con
                        datos divergentes (otro numero de pedido, otra
                        cantidad, otro precio u otro total). Requiere revision
                        humana.

Ninguna fila se descarta ni se oculta: salida.csv contiene todas las filas de
entrada mas las columnas 'fila' (numero de fila en entrada.csv contado desde 1
en los datos, sin encabezado), 'clasificacion' y 'motivo'.

Solo biblioteca estandar. Idempotente: corridas repetidas generan el mismo
salida.csv porque la clasificacion depende unicamente del contenido y del
orden de entrada.csv.
"""

import csv
import os
import unicodedata

BASE = os.path.dirname(os.path.abspath(__file__))
ENTRADA = os.path.join(BASE, "entrada.csv")
SALIDA = os.path.join(BASE, "salida.csv")
CAMPOS_CLAVE = ("cliente", "producto", "fecha")
CAMPOS_INTERNOS = ("fila", "clasificacion", "motivo")


def normalizar(texto):
    """Minusculas, sin acentos, espacios colapsados."""
    t = unicodedata.normalize("NFKD", texto)
    t = "".join(c for c in t if not unicodedata.combining(c))
    return " ".join(t.lower().split())


def firma(fila):
    """Firma completa: todos los campos, normalizados."""
    return tuple(normalizar(v) for k, v in fila.items() if k not in CAMPOS_INTERNOS)


def clave_entidad(fila):
    return tuple(normalizar(fila[c]) for c in CAMPOS_CLAVE)


def diferencias(original, sospechosa):
    """Lista de 'campo: valor_original vs valor_sospechosa' para los campos
    que difieren (ignora campos internos)."""
    diffs = []
    for k, v in original.items():
        if k in CAMPOS_INTERNOS:
            continue
        if normalizar(v) != normalizar(sospechosa[k]):
            diffs.append((k, v, sospechosa[k]))
    return diffs


def main():
    with open(ENTRADA, newline="", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        campos = [c for c in reader.fieldnames if c not in CAMPOS_INTERNOS]
        filas = [dict(r) for r in reader]

    if not filas:
        raise SystemExit("entrada.csv no tiene filas de datos")

    # Numero de fila legible: 1 = primera fila de datos.
    for i, fila in enumerate(filas, start=1):
        fila["fila"] = i
        fila["clasificacion"] = None
        fila["motivo"] = ""

    # referencias_de: fila de referencia -> filas marcadas que se comparan
    # contra ella. Sirve para que cada fila no marcada pueda mostrar sus
    # referencias relacionadas (los conteos no las abarcan).
    referencias_de = {}

    # Pasada 1: repeticiones exactas (firma completa ya vista).
    vistos = {}
    for fila in filas:
        f = firma(fila)
        previa = vistos.get(f)
        if previa is not None:
            fila["clasificacion"] = "repeticion_exacta"
            fila["motivo"] = (
                "fila identica a la fila {} (todos los campos iguales)"
                .format(vistos[f])
            )
            referencias_de.setdefault(previa, []).append(
                (fila["fila"], "repeticion_exacta")
            )
        else:
            vistos[f] = fila["fila"]
            fila["clasificacion"] = None

    # Pasada 2: duplicados probables dentro de cada grupo
    # cliente + producto + fecha, solo entre filas no marcadas como repeticion.
    grupos = {}
    for fila in filas:
        if fila["clasificacion"] is None:
            grupos.setdefault(clave_entidad(fila), []).append(fila)

    for grupo in grupos.values():
        original = grupo[0]
        for fila in grupo[1:]:
            diffs = diferencias(original, fila)
            nombres = [d[0] for d in diffs]
            if nombres == ["pedido_id"]:
                motivo = (
                    "mismo cliente, producto y fecha que la fila {} y los "
                    "mismos datos, pero con otro numero de pedido ({})"
                    .format(original["fila"], fila["pedido_id"])
                )
            elif diffs:
                detalle = "; ".join(
                    "{} '{}' vs '{}'".format(k, a, b) for k, a, b in diffs
                )
                motivo = (
                    "mismo cliente, producto y fecha que la fila {}, pero "
                    "con datos que no coinciden: {}"
                    .format(original["fila"], detalle)
                )
            else:  # no deberia ocurrir: firma distinta pero sin diferencias
                motivo = (
                    "coincide con la fila {} en cliente, producto y fecha"
                    .format(original["fila"])
                )
            fila["clasificacion"] = "duplicado_probable"
            fila["motivo"] = motivo
            referencias_de.setdefault(original["fila"], []).append(
                (fila["fila"], "duplicado_probable")
            )

    # Resto: filas no marcadas por estas reglas. Si una de ellas es la fila
    # de referencia de un caso, el motivo lo dice: los conteos asignan una
    # marca por fila y no abarcan las referencias involucradas.
    for fila in filas:
        if fila["clasificacion"] is None:
            fila["clasificacion"] = "sin_alerta_bajo_estas_reglas"
            motivo = "no marcada por estas reglas"
            refs = referencias_de.get(fila["fila"])
            if refs:
                detalle = " y ".join(
                    "la fila {} ({})".format(
                        n,
                        "repeticion exacta"
                        if tipo == "repeticion_exacta"
                        else "caso para revisar",
                    )
                    for n, tipo in refs
                )
                motivo += "; es la fila de referencia de " + detalle
            fila["motivo"] = motivo

    # Salida: todas las filas, en el orden original, con la clasificacion.
    # 'fila' va primera para que las referencias de los motivos (la fila N)
    # sean localizables en el artefacto entregado.
    columnas = ["fila"] + campos + ["clasificacion", "motivo"]
    with open(SALIDA, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=columnas)
        writer.writeheader()
        for fila in filas:
            writer.writerow({k: fila[k] for k in columnas})

    # Cifras reales calculadas del dataset.
    conteos = {"sin_alerta_bajo_estas_reglas": 0, "repeticion_exacta": 0, "duplicado_probable": 0}
    for fila in filas:
        conteos[fila["clasificacion"]] += 1
    total = len(filas)

    print("=== Resumen de la demo sintetica v3 ===")
    print("Total de filas en entrada.csv : {}".format(total))
    print("  sin_alerta_bajo_estas_reglas: {}".format(conteos["sin_alerta_bajo_estas_reglas"]))
    print("  repeticiones exactas        : {}".format(conteos["repeticion_exacta"]))
    print("  duplicados probables        : {}".format(conteos["duplicado_probable"]))
    suma = conteos["sin_alerta_bajo_estas_reglas"] + conteos["repeticion_exacta"] + conteos["duplicado_probable"]
    print("Chequeo: {} + {} + {} = {} {}".format(
        conteos["sin_alerta_bajo_estas_reglas"], conteos["repeticion_exacta"],
        conteos["duplicado_probable"], suma,
        "OK" if suma == total else "ERROR",
    ))
    print("Cifras (R01): {} filas no marcadas por estas reglas, {} repeticiones exactas y {} filas para revisar".format(
        conteos["sin_alerta_bajo_estas_reglas"], conteos["repeticion_exacta"],
        conteos["duplicado_probable"],
    ))
    print("Nota: los conteos asignan una marca por fila y no abarcan todas las")
    print("referencias: las filas de referencia de un caso quedan dentro de las")
    print("no marcadas (el motivo de cada fila indicada lo senala).")
    print("Salida escrita en: {}".format(SALIDA))


if __name__ == "__main__":
    main()
