"""
Redis y rutas de archivos para imports CSV (compartido por módulos layouts_csv).
Cada módulo usa un job_type (ej. "part", "cls", "bom") para prefijos y nombres de archivo.
"""
import os
import base64
import json
import logging
from typing import Optional, Set, List

from core.paths import layout_path

logger = logging.getLogger(__name__)

IMPORT_REDIS_TTL = 3600


def _get_redis():
    import redis
    url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0"))
    return redis.Redis.from_url(url, decode_responses=False)


def upload_dir() -> str:
    """Directorio temporal para CSV en el worker."""
    return layout_path("imports", "temp")


def error_dir() -> str:
    """Directorio de archivos JSONL de errores."""
    return layout_path("imports", "errors")


def storage_keys(job_type: str, job_id: str) -> tuple:
    """Prefijos Redis para un job_type y job_id. Devuelve (file_key, meta_key, error_lines_key)."""
    # Facturas usa prefijo "import_" sin tipo (compatibilidad con rutas existentes)
    if job_type == "" or job_type == "invoice":
        prefix = "import_"
    else:
        prefix = f"{job_type}_import_"
    return (
        f"{prefix}file:{job_id}",
        f"{prefix}meta:{job_id}",
        f"{prefix}error_lines:{job_id}",
    )


def file_path_for_job(job_type: str, job_id: str) -> str:
    """Ruta local del archivo CSV para un job."""
    if job_type == "" or job_type == "invoice":
        return os.path.join(upload_dir(), f"{job_id}.csv")
    return os.path.join(upload_dir(), f"{job_type}_{job_id}.csv")


def error_path_for_job(job_type: str, job_id: str) -> str:
    """Ruta del archivo JSONL de errores para un job."""
    os.makedirs(error_dir(), exist_ok=True)
    if job_type == "" or job_type == "invoice":
        return os.path.join(error_dir(), f"{job_id}.jsonl")
    return os.path.join(error_dir(), f"{job_type}_{job_id}.jsonl")


def ensure_file_from_redis(job_type: str, job_id: str, log_prefix: str = "") -> Optional[str]:
    """
    Descarga contenido del CSV desde Redis y lo escribe en disco.
    Devuelve la ruta del archivo o None si no hay datos o falla.
    """
    file_key, _, _ = storage_keys(job_type, job_id)
    r = _get_redis()
    data = r.get(file_key)
    if not data:
        return None
    try:
        raw = base64.b64decode(data)
    except Exception as e:
        logger.warning("%s failed to decode file from Redis: %s", log_prefix or job_type, e)
        return None
    path = file_path_for_job(job_type, job_id)
    os.makedirs(upload_dir(), exist_ok=True)
    with open(path, "wb") as f:
        f.write(raw)
    return path


def ensure_meta_from_redis(job_type: str, job_id: str, file_path: str, log_prefix: str = "") -> bool:
    """Descarga meta desde Redis y la escribe en .meta.json. Devuelve True si hubo datos."""
    _, meta_key, _ = storage_keys(job_type, job_id)
    r = _get_redis()
    data = r.get(meta_key)
    if not data:
        return False
    try:
        meta = json.loads(data.decode("utf-8"))
    except Exception as e:
        logger.warning("%s failed to decode meta from Redis: %s", log_prefix or job_type, e)
        return False
    meta_path = file_path.replace(".csv", ".meta.json")
    with open(meta_path, "w", encoding="utf-8") as f:
        json.dump(meta, f)
    return True


def store_error_lines(job_type: str, job_id: str, line_numbers: List[int]) -> None:
    """Guarda la lista de números de línea con error en Redis."""
    _, _, error_key = storage_keys(job_type, job_id)
    try:
        r = _get_redis()
        r.set(error_key, json.dumps(line_numbers).encode("utf-8"), ex=IMPORT_REDIS_TTL)
    except Exception as e:
        logger.warning("Failed to store error lines in Redis: %s", e)


def get_error_lines(job_type: str, job_id: str, error_path: str) -> Set[int]:
    """
    Obtiene el conjunto de líneas con error: primero desde Redis, si está vacío desde el JSONL.
    """
    _, _, error_key = storage_keys(job_type, job_id)
    lines = set()
    try:
        r = _get_redis()
        raw = r.get(error_key)
        if raw:
            lines = set(json.loads(raw.decode("utf-8")))
    except Exception as e:
        logger.debug("Could not load error lines from Redis: %s", e)
    if not lines and os.path.exists(error_path):
        with open(error_path, "r", encoding="utf-8") as f:
            for line in f:
                try:
                    err = json.loads(line)
                    if "line" in err:
                        lines.add(err["line"])
                except Exception:
                    pass
    return lines


def delete_import_from_redis(job_type: str, job_id: str) -> None:
    """Borra claves Redis del import (file, meta, error_lines)."""
    file_key, meta_key, error_key = storage_keys(job_type, job_id)
    try:
        r = _get_redis()
        r.delete(file_key, meta_key, error_key)
    except Exception as e:
        logger.warning("Failed to delete import keys from Redis: %s", e)


def cleanup_import_job(
    job_type: str,
    job_id: str,
    file_path: Optional[str] = None,
    error_path: Optional[str] = None,
    meta_path: Optional[str] = None,
) -> None:
    """Elimina archivos locales y claves Redis del job."""
    if file_path and os.path.exists(file_path):
        try:
            os.remove(file_path)
        except Exception as e:
            logger.warning("Cleanup: failed to remove file %s: %s", file_path, e)
    if error_path and os.path.exists(error_path):
        try:
            os.remove(error_path)
        except Exception as e:
            logger.warning("Cleanup: failed to remove error file %s: %s", error_path, e)
    if meta_path and os.path.exists(meta_path):
        try:
            os.remove(meta_path)
        except Exception as e:
            logger.warning("Cleanup: failed to remove meta %s: %s", meta_path, e)
    delete_import_from_redis(job_type, job_id)
