from __future__ import annotations

from datetime import datetime
import io
import mimetypes
import os
import zipfile
from typing import Any, Dict

from fastapi import APIRouter, Depends, File, HTTPException, Query, Response, UploadFile, status
from sqlalchemy.orm import Session

from core.config import settings
from core.database import get_core_db
from core.security import get_current_user, validate_access_to_resource
from core.s3_keys import expediente_archivo_document_key
from core.storage_s3 import delete_object_if_exists, get_object_bytes, object_exists, put_object_bytes

from .dto import (
    DigitalizacionTaskDetailResponse,
    DigitalizarRequest,
    DigitalizarResponse,
    ExpedienteArchivoCreateDTO,
    ExpedienteArchivoListResponse,
    ExpedienteArchivoResponseDTO,
    ExpedienteArchivoUpdateDTO,
    RegistrarDigitalizacionRequest,
)
from .models import ExpedienteArchivo
from .service import ExpedienteArchivoService
from .tasks import digitalizar_task

import logging
logger = logging.getLogger(__name__)

router = APIRouter(prefix="/expediente-archivos")


def _remove_stored_document_path(path: str | None) -> None:
    raw = (path or "").strip()
    if not raw:
        return
    try:
        if settings.use_s3_object_storage and not os.path.isabs(raw):
            delete_object_if_exists(raw)
            return
        if os.path.exists(raw):
            os.remove(raw)
    except Exception:
        logger.warning("No se pudo eliminar archivo previo de expediente: %s", raw, exc_info=True)


# ──────────────────────────────────────────────────────────────────────────── #
# CRUD                                                                         #
# ──────────────────────────────────────────────────────────────────────────── #

@router.get("/", response_model=ExpedienteArchivoListResponse)
def list_expediente_archivos(
    company_id: int = Query(...),
    page: int = Query(1, ge=1),
    page_size: int = Query(50, ge=1, le=200),
    search: str = Query(None),
    status: str = Query(None),
    rfc_consulta: str = Query(None),
    e_document: str = Query(None),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user)
    return ExpedienteArchivoService.list(
        db, company_id, tenant_id, page, page_size, 
        search=search, status=status, rfc_consulta=rfc_consulta, e_document=e_document
    )


@router.get("/{record_id}", response_model=ExpedienteArchivoResponseDTO)
def get_expediente_archivo(
    record_id: int,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user)
    record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id)
    if not record:
        raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.")
    return ExpedienteArchivoResponseDTO.model_validate(record)


@router.post("/", response_model=ExpedienteArchivoResponseDTO, status_code=status.HTTP_201_CREATED)
def create_expediente_archivo(
    dto: ExpedienteArchivoCreateDTO,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user)
    record = ExpedienteArchivoService.create(db, dto, company_id, tenant_id)
    return ExpedienteArchivoResponseDTO.model_validate(record)


@router.put("/{record_id}", response_model=ExpedienteArchivoResponseDTO)
def update_expediente_archivo(
    record_id: int,
    dto: ExpedienteArchivoUpdateDTO,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user)
    record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id)
    if not record:
        raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.")
    record = ExpedienteArchivoService.update(db, record, dto)
    return ExpedienteArchivoResponseDTO.model_validate(record)


@router.delete("/{record_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_expediente_archivo(
    record_id: int,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user)
    record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id)
    if not record:
        raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.")
    _remove_stored_document_path(record.archivo_digitalizado_en)
    ExpedienteArchivoService.delete(db, record)
    return None


_ARTIFACT_TYPE_MAP = {
    "acuse":                  ("acuse_pdf_path",              "application/pdf"),
    "envio-xml":              ("envio_xml_path",              "application/xml"),
    "respuesta-xml":          ("respuesta_xml_path",          "application/xml"),
    "consulta-envio-xml":     ("consulta_envio_xml_path",     "application/xml"),
    "consulta-respuesta-xml": ("consulta_respuesta_xml_path", "application/xml"),
}


@router.get("/{record_id}/artifacts/{artifact_type}", response_class=Response)
def download_artifact(
    record_id: int,
    artifact_type: str,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """Descarga un artefacto de digitalización (acuse PDF o XMLs) desde S3."""
    tenant_id = validate_access_to_resource(db, company_id, current_user)
    if artifact_type not in _ARTIFACT_TYPE_MAP:
        raise HTTPException(status_code=400, detail=f"Tipo de artefacto no válido: {artifact_type}")
    record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id)
    if not record:
        raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.")
    field_name, content_type = _ARTIFACT_TYPE_MAP[artifact_type]
    key = (getattr(record, field_name, None) or "").strip()
    if not key or key == "inline":
        raise HTTPException(status_code=404, detail="Artefacto no disponible para este expediente.")
    if not object_exists(key):
        raise HTTPException(status_code=404, detail="El archivo no se encontró en el almacenamiento.")
    ext = ".pdf" if content_type == "application/pdf" else ".xml"
    filename = f"{artifact_type}_{record.e_document or record_id}{ext}"
    return Response(
        content=get_object_bytes(key),
        media_type=content_type,
        headers={"Content-Disposition": f'attachment; filename="{filename}"'},
    )


@router.get("/{record_id}/artifacts-zip", response_class=Response)
def download_artifacts_zip(
    record_id: int,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """Descarga todos los artefactos disponibles de un expediente en un archivo ZIP."""
    tenant_id = validate_access_to_resource(db, company_id, current_user)
    record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id)
    if not record:
        raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.")

    base_name = record.e_document or str(record_id)
    artifact_files = [
        ("acuse",                  "acuse_pdf_path",              f"acuse_{base_name}.pdf"),
        ("envio-xml",              "envio_xml_path",              f"envio_{base_name}.xml"),
        ("respuesta-xml",          "respuesta_xml_path",          f"respuesta_{base_name}.xml"),
        ("consulta-envio-xml",     "consulta_envio_xml_path",     f"consulta_envio_{base_name}.xml"),
        ("consulta-respuesta-xml", "consulta_respuesta_xml_path", f"consulta_respuesta_{base_name}.xml"),
    ]

    buf = io.BytesIO()
    added = 0
    with zipfile.ZipFile(buf, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
        for _type, field_name, filename in artifact_files:
            key = (getattr(record, field_name, None) or "").strip()
            if not key or key == "inline":
                continue
            if not object_exists(key):
                continue
            zf.writestr(filename, get_object_bytes(key))
            added += 1

    if added == 0:
        raise HTTPException(status_code=404, detail="No hay artefactos disponibles para este expediente.")

    buf.seek(0)
    zip_filename = f"expediente_{base_name}.zip"
    return Response(
        content=buf.getvalue(),
        media_type="application/zip",
        headers={"Content-Disposition": f'attachment; filename="{zip_filename}"'},
    )


@router.post("/{record_id}/upload", response_model=Dict[str, Any])
async def upload_expediente_archivo_file(
    record_id: int,
    company_id: int = Query(...),
    file: UploadFile = File(...),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user)
    record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id)
    if not record:
        raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.")

    content = await file.read()
    if not content:
        raise HTTPException(status_code=400, detail="El archivo está vacío.")

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    # Borrar artefactos de digitalización previa al subir documento nuevo
    _ARTIFACT_FIELDS = [
        "acuse_pdf_path",
        "envio_xml_path",
        "respuesta_xml_path",
        "consulta_envio_xml_path",
        "consulta_respuesta_xml_path",
    ]
    for _field in _ARTIFACT_FIELDS:
        _old_key = (getattr(record, _field, None) or "").strip()
        if _old_key and _old_key != "inline":
            try:
                delete_object_if_exists(_old_key)
            except Exception:
                logger.warning("No se pudo eliminar artefacto previo %s=%s", _field, _old_key, exc_info=True)
        setattr(record, _field, None)

    # El documento fuente cambió — la digitalización anterior ya no aplica
    record.status = "pending"
    record.e_document = None
    record.num_operacion = None
    record.task_id = None
    record.external_task_id = None

    try:
        if settings.use_s3_object_storage:
            key = expediente_archivo_document_key(
                tenant_id,
                company_id,
                record.id,
                timestamp,
                file.filename or "documento.pdf",
            )
            ct = file.content_type or mimetypes.guess_type(file.filename or "")[0] or "application/octet-stream"
            _remove_stored_document_path(record.archivo_digitalizado_en)
            put_object_bytes(key, content, content_type=ct)
            stored = key
        else:
            key = expediente_archivo_document_key(
                tenant_id,
                company_id,
                record.id,
                timestamp,
                file.filename or "documento.pdf",
            )
            base = os.path.join("uploads", "expediente_archivos", str(company_id), str(record.id))
            os.makedirs(base, exist_ok=True)
            filename = key.rsplit("/", 1)[-1]
            stored = os.path.join(base, filename)
            _remove_stored_document_path(record.archivo_digitalizado_en)
            with open(stored, "wb") as destination:
                destination.write(content)

        record.archivo_digitalizado_en = stored
        record.nombre_archivo = file.filename or record.nombre_archivo
        db.add(record)
        db.commit()
        db.refresh(record)
    except HTTPException:
        db.rollback()
        raise
    except ValueError as exc:
        db.rollback()
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except Exception as exc:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error saving file: {str(exc)}") from exc

    return {
        "message": "Archivo cargado correctamente",
        "record_id": record.id,
        "path": stored,
        "nombre_archivo": record.nombre_archivo,
    }


# ──────────────────────────────────────────────────────────────────────────── #
# Digitalización                                                               #
# ──────────────────────────────────────────────────────────────────────────── #

@router.post("/digitalizar/{record_id}", response_model=DigitalizarResponse)
def digitalizar_expediente_archivo(
    record_id: int,
    body: DigitalizarRequest,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """
    Lanza la tarea Celery de digitalización para un expediente existente.
    """
    tenant_id = validate_access_to_resource(db, company_id, current_user)
    record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id)
    if not record:
        raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.")

    task = digitalizar_task.apply_async(
        kwargs={
            "expediente_id": record_id,
            "request_data": body.model_dump(),
            "company_id": company_id,
            "tenant_id": tenant_id,
        },
        headers={
            "rls_tenant_id": str(int(tenant_id)),
            "rls_company_id": str(int(company_id)),
        },
    )

    return DigitalizarResponse(
        task_id=task.id,
        message="Tarea de digitalización iniciada.",
        status="pending",
    )


@router.post("/registrar-digitalizacion/", response_model=Dict[str, Any])
def registrar_digitalizacion(
    body: RegistrarDigitalizacionRequest,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """
    Lanza tareas de digitalización en batch para múltiples expedientes.
    """
    tenant_id = validate_access_to_resource(db, company_id, current_user)
    launched = []
    errors_list = []

    for record_id in body.ids_archivos:
        record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id)
        if not record:
            errors_list.append({"id": record_id, "error": "No encontrado"})
            continue

        task = digitalizar_task.apply_async(
            kwargs={
                "expediente_id": record_id,
                "request_data": {"rfc_consulta": body.rfc_consulta},
                "company_id": company_id,
                "tenant_id": tenant_id,
            },
            headers={
                "rls_tenant_id": str(int(tenant_id)),
                "rls_company_id": str(int(company_id)),
            },
        )
        launched.append({"id": record_id, "task_id": task.id})

    return {"launched": launched, "errors": errors_list}


@router.get("/status-digitalizacion-task/{task_id}", response_model=DigitalizacionTaskDetailResponse)
def get_digitalizacion_task_status(
    task_id: str,
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """
    Consulta el estado de una tarea Celery de digitalización.
    """
    return ExpedienteArchivoService.get_task_status(task_id)
