"""
Rutas de importación CSV para Exportación (encabezado y partidas).
Flujo: upload → scan → status (polling) → commit. Sin validaciones ni inserción aún.
"""
import json
import logging
import os
from uuid import uuid4
import csv
import io

from fastapi import APIRouter, File, HTTPException, UploadFile, Depends, Form, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from typing import Literal, Optional, Dict, Any

from core.celery_app import celery_app
from core.database import get_core_db
from core.security import get_current_user, validate_access_to_resource
from api.v1.modules.core.tasks_tracking import track_and_dispatch

from .schemas import ImportJobResponse, CommitRequest
from .tasks import scan_file, insert_valid_rows, JOB_TYPE, EXP_IMPORT_REDIS_TTL
from ..common import storage as common_storage
from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit
from ..common.responses import normalize_commit_status_payload

router = APIRouter()
logger = logging.getLogger(__name__)


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)


@router.post("/upload/{model_target}", response_model=ImportJobResponse)
async def upload_import_file(
    model_target: Literal["invoice_header", "invoice_details", "invoice_series"],
    file: UploadFile = File(...),
    footer_config: Optional[str] = Form(None),
    template_id: Optional[str] = Form(None),
    company_id: int = Query(..., description="Company ID"),
    operation_type: Optional[str] = Query("exp"),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """Subir CSV, guardar en Redis, encolar scan. operation_type=exp para exportación."""
    try:
        tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"])
    except Exception as e:
        logger.error("Exportación import: access validation failed: %s", e)
        raise HTTPException(status_code=403, detail="Invalid company access")

    if not file.filename or not file.filename.lower().endswith(".csv"):
        raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv")

    job_id = str(uuid4())
    contents = await file.read()

    default_template = (
        "exp_def_header" if model_target == "invoice_header"
        else "exp_def_series" if model_target == "invoice_series"
        else "exp_def_partidas"
    )
    meta_data = {
        "tenant_id": tenant_id,
        "company_id": company_id,
        "user_id": current_user.get("id"),
        "capture_user": (
            current_user.get("preferred_username")
            or current_user.get("email")
            or current_user.get("sub")
            or "CSV"
        ),
        "footer_config": footer_config,
        "operation_type": operation_type or "exp",
        "template_id": template_id or default_template,
    }

    try:
        common_storage.store_import_file(
            JOB_TYPE,
            job_id,
            contents,
            meta_data,
            tenant_id=int(tenant_id),
            company_id=company_id,
            ttl=EXP_IMPORT_REDIS_TTL,
            log_label="Exportación import",
        )
    except common_storage.ImportStoreError as e:
        logger.error("Exportación import: store error: %s", e)
        raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")
    except Exception as e:
        logger.error("Exportación import: Redis store error: %s", e)
        raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.")

    track_and_dispatch(
        db=db,
        task=scan_file,
        tenant_id=int(tenant_id),
        company_id=company_id,
        requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"),
        task_name="exportacion_scan_file",
        task_group="layouts_csv",
        task_origin="a76/layouts_csv/exportacion/upload",
        args=[job_id, model_target, footer_config],
        task_id=job_id,
    )

    return ImportJobResponse(
        job_id=job_id,
        status="queued",
        message="Archivo subido. Escaneo iniciado.",
    )


@router.get("/{job_id}/status")
async def get_import_status(job_id: str):
    """Polling: estado del escaneo o del commit."""
    task_result = celery_app.AsyncResult(job_id)

    if task_result.state == "PENDING":
        return {"status": "processing", "progress": 0}
    if task_result.state == "PROGRESS":
        info = task_result.info or {}
        return {
            "status": "processing",
            "progress": info.get("current", 0),
            "total": info.get("total", 0),
        }
    if task_result.state == "SUCCESS":
        result = task_result.result
        if isinstance(result, dict) and "status" in result:
            return normalize_commit_status_payload(result)
        return {"status": "finished", "result": result}

    if isinstance(getattr(task_result, "result", None), dict) and task_result.result.get("status") in ("finished", "warning"):
        return normalize_commit_status_payload(task_result.result)

    logger.warning("Exportación import task %s failed: state=%s", job_id, task_result.state)
    err_msg = None
    tb = getattr(task_result, "traceback", None)
    if tb and isinstance(tb, str):
        lines = [l.strip() for l in tb.strip().split("\n") if l.strip()]
        if lines:
            err_msg = lines[-1]
    if not err_msg:
        try:
            exc = task_result.get(propagate=False)
            if exc is not None:
                err_msg = str(exc)
        except Exception:
            pass
    if not err_msg:
        result = getattr(task_result, "result", None)
        if result is not None and not isinstance(result, dict):
            err_msg = str(result)
        elif isinstance(result, dict) and (result.get("error") or result.get("message")):
            err_msg = result.get("error") or result.get("message")
    return {"status": "failed", "error": err_msg or "Task failed"}


@router.post("/{job_id}/commit")
async def commit_import_job(
    job_id: str,
    body: CommitRequest,
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """Usuario confirma; se encola la tarea de commit (por ahora sin inserción real)."""
    _, meta_key, _ = common_storage.storage_keys(JOB_TYPE, job_id)
    r = _get_redis()
    commit_id = dispatch_tracked_layouts_csv_commit(
        db=db,
        current_user=current_user,
        redis_client=r,
        layout_job_id=job_id,
        meta_redis_key=meta_key,
        celery_task=insert_valid_rows,
        task_name="exportacion_insert_valid_rows",
        task_origin="a76/layouts_csv/exportacion/commit",
        args=[job_id, body.model_target],
    )
    return {
        "status": "committing",
        "message": "Proceso de commit iniciado.",
        "commit_job_id": commit_id,
    }


@router.get("/{job_id}/errors/scan-csv")
async def download_scan_errors_csv(job_id: str):
    """
    Descarga CSV con TODO el detalle de errores del scan (sin límite),
    leyendo el archivo JSONL generado por el worker.
    """

    # Exportación usa job_type "exp" (storage: exp_{job_id}.jsonl)
    error_path = common_storage.error_path_for_job(JOB_TYPE, job_id)
    if not os.path.exists(error_path):
        raise HTTPException(
            status_code=404,
            detail="Archivo de errores no encontrado. Vuelve a escanear o intenta más tarde.",
        )

    def row_iter():
        buffer = io.StringIO()
        writer = csv.writer(buffer)
        writer.writerow(["linea", "columna", "mensaje", "solucion"])
        yield buffer.getvalue()
        buffer.seek(0)
        buffer.truncate(0)

        with open(error_path, "r", encoding="utf-8") as f:
            for line in f:
                if not line.strip():
                    continue
                try:
                    payload = json.loads(line)
                except Exception:
                    continue

                items = payload if isinstance(payload, list) else [payload]
                for err in items:
                    if not isinstance(err, dict):
                        continue
                    buffer.seek(0)
                    buffer.truncate(0)
                    writer.writerow(
                        [
                            err.get("line", ""),
                            err.get("col", ""),
                            err.get("msg", ""),
                            err.get("solution", ""),
                        ]
                    )
                    yield buffer.getvalue()
                    buffer.seek(0)
                    buffer.truncate(0)

    headers = {
        "Content-Disposition": f'attachment; filename="errores_{job_id}.csv"'
    }
    return StreamingResponse(row_iter(), media_type="text/csv; charset=utf-8", headers=headers)
