import json
import logging
import os
import secrets
import socket
import sys
from datetime import datetime, timezone, timedelta
from pathlib import Path

_PROJECT_ROOT = Path(__file__).resolve().parent.parent
_TZ = timezone(timedelta(hours=-3))  # ART (Argentina)


def _ahora() -> str:
    return datetime.now(_TZ).isoformat(timespec="seconds")


class _SanitizeFilter(logging.Filter):
    """Elimina saltos de línea de los mensajes para prevenir log injection."""
    def filter(self, record):
        record.msg = str(record.msg).replace('\n', ' ').replace('\r', ' ')
        if record.args:
            record.args = tuple(
                str(a).replace('\n', ' ').replace('\r', ' ') if isinstance(a, str) else a
                for a in (record.args if isinstance(record.args, tuple) else (record.args,))
            )
        return True


class _HybridFormatter(logging.Formatter):
    """
    2026-05-29T14:23:01-03:00 [INFO ] [proceso] [run:abc123] mensaje
    """
    def __init__(self, proceso: str, run_id: str):
        super().__init__()
        self.proceso = proceso
        self.run_id = run_id

    def format(self, record: logging.LogRecord) -> str:
        level = record.levelname.ljust(5)
        msg = record.getMessage()
        line = f"{_ahora()} [{level}] [{self.proceso}] [run:{self.run_id}] {msg}"
        if record.exc_info:
            line += " | " + self.formatException(record.exc_info).splitlines()[0]
        return line


class _JsonFormatter(logging.Formatter):
    def __init__(self, proceso: str, run_id: str):
        super().__init__()
        self.proceso = proceso
        self.run_id = run_id

    def format(self, record: logging.LogRecord) -> str:
        entry = {
            "ts": _ahora(),
            "level": record.levelname,
            "proceso": self.proceso,
            "run_id": self.run_id,
            "msg": record.getMessage(),
            "host": socket.gethostname(),
        }
        if record.exc_info:
            entry["exception"] = self.formatException(record.exc_info).splitlines()[0]
        return json.dumps(entry, ensure_ascii=False)


class _DBHandler(logging.Handler):
    """
    Handler que inserta cada log en PRS_procesosLogs_eventos via PRS_log_evento.
    Se activa llamando a setup_logging_db(id_log) después de PRS_iniciar.
    Solo loguea WARNING y ERROR para no saturar la tabla con mensajes DEBUG/INFO.
    """
    def __init__(self, id_log):
        super().__init__(level=logging.WARNING)
        self.id_log = int(id_log)

    def emit(self, record: logging.LogRecord):
        try:
            from helpers.db import ejecutar_sp
            msg = record.getMessage()
            if record.exc_info:
                msg += " | " + self.formatException(record.exc_info).splitlines()[0]
            ejecutar_sp('PRS_log_evento', [
                self.id_log,
                record.levelname,
                None,   # evento tipificado — se puede extraer del msg si sigue el formato KEY | ...
                msg[:500],
            ])
        except Exception:
            pass  # nunca romper el flujo principal por un error de logging


def setup_logging_db(id_log) -> None:
    """
    Agrega el handler de DB al logger raíz una vez que se tiene el id_log
    devuelto por PRS_iniciar. Llamar después de capturar id_log.

    Ejemplo:
        run_id = setup_logging("descarga")
        result = ejecutar_sp('PRS_iniciar', [..., run_id])
        id_log = result[0].get('id_log')
        setup_logging_db(id_log)
    """
    if id_log is None:
        return
    root = logging.getLogger()
    # Evitar duplicados si se llama dos veces
    for h in root.handlers:
        if isinstance(h, _DBHandler):
            return
    root.addHandler(_DBHandler(id_log))


def setup_logging(nombre_proceso: str) -> str:
    """
    Configura el logger raíz para el proceso indicado.
    Genera un run_id único por ejecución.
    Escribe en logs/<proceso>/YYYY-MM-DD.log  y  logs/_auditoria/YYYY-MM-DD.audit.jsonl
    Retorna el run_id generado.
    """
    run_id = secrets.token_hex(3)  # 6 chars hex, ej: "a3f9b2"
    hoy = datetime.now(_TZ).strftime("%Y-%m-%d")

    logs_dir = _PROJECT_ROOT / "logs" / nombre_proceso
    audit_dir = _PROJECT_ROOT / "logs" / "_auditoria"
    logs_dir.mkdir(parents=True, exist_ok=True)
    audit_dir.mkdir(parents=True, exist_ok=True)

    hybrid_fmt = _HybridFormatter(nombre_proceso, run_id)
    json_fmt = _JsonFormatter(nombre_proceso, run_id)

    fh = logging.FileHandler(logs_dir / f"{hoy}.log", encoding="utf-8")
    fh.setFormatter(hybrid_fmt)
    fh.setLevel(logging.DEBUG)

    ah = logging.FileHandler(audit_dir / f"{hoy}.audit.jsonl", encoding="utf-8")
    ah.setFormatter(json_fmt)
    ah.setLevel(logging.INFO)

    sh = logging.StreamHandler(sys.stdout)
    sh.setFormatter(hybrid_fmt)
    sh.setLevel(logging.INFO)

    root = logging.getLogger()
    root.handlers.clear()
    root.setLevel(logging.DEBUG)
    root.addFilter(_SanitizeFilter())
    root.addHandler(fh)
    root.addHandler(ah)
    root.addHandler(sh)

    logging.info(f"PROCESO_INICIADO | proceso={nombre_proceso} run_id={run_id} pid={os.getpid()} host={socket.gethostname()}")
    return run_id
