"""
Rutas para gestión de DODA (Documentos de Operación de Aduana)
"""

import io
import logging
from datetime import datetime, timezone
from typing import Any, Dict, List

from fastapi import APIRouter, Depends, HTTPException, Query, status
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session

from core import storage_s3
from core.config import settings
from core.database import get_core_db
from core.s3_keys import doda_report_pdf_key
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from .dto import (
    DodaCreateDTO,
    DodaResponseDTO,
    DodaUpdateDTO,
    DodaDetailResponseDTO,
    DodaContainerCreateDTO,
    DodaContainerResponseDTO,
    DodaContainerUpdateDTO,
    DodaContainerSealCreateDTO,
    DodaContainerSealResponseDTO,
    DodaAmericanPedimentoCreateDTO,
    DodaAmericanPedimentoResponseDTO,
    DodaAmericanPedimentoUpdateDTO,
    DodaPedimentoCreateDTO,
    DodaPedimentoResponseDTO,
    DodaPedimentoUpdateDTO,
)
from .models import Doda
from .service import DodaService
from .alta_service import DodaAltaService
from .external_service import DodaExternalService
from .alta_log_dto import (
    DodaAltaLogCreateDTO,
    DodaAltaLogListResponse,
    DodaAltaLogResponseDTO,
    DodaAltaLogUpdateDTO,
)
from .alta_log_service import DodaAltaLogService
from .fingerprint import build_doda_fingerprint
from .print_cache import touch_invalidate_doda_report
from .report_service import DodaReportPdfService
from .export_service import (
    build_export_text,
    build_pedimentos_export_text,
    list_dodas_in_date_range,
    list_pedimentos_for_doda_export,
    parse_export_params,
    parse_pedimento_export_format,
    _content_type_and_filename,
)
from core.security import get_current_user, validate_access_to_resource

logger = logging.getLogger(__name__)


def _coalesce_external_result_payload(payload: Dict[str, Any]) -> Dict[str, Any]:
    result = payload.get("result")
    if isinstance(result, dict):
        return result
    return payload


def _apply_consulta_success_to_doda(
    doda: Doda,
    payload: Dict[str, Any],
) -> None:
    source = _coalesce_external_result_payload(payload)
    mapping = {
        "integration_number": "integration_number",
        "numero_integracion": "integration_number",
        "transaction_number": "transaction_number",
        "numero_transaccion": "transaction_number",
        "sat_digital_seal": "sat_digital_seal",
        "sello_digital_sat": "sat_digital_seal",
        "sat_certificate": "sat_certificate",
        "certificado_sat": "sat_certificate",
        "serial_number": "serial_number",
        "numero_serie": "serial_number",
        "electronic_signature": "electronic_signature",
        "firma_electronica": "electronic_signature",
        "original_chain": "original_chain",
        "cadena_original": "original_chain",
        "sat_original_chain": "sat_original_chain",
        "cadena_original_sat": "sat_original_chain",
        "linq_sat_qr": "linq_sat_qr",
        "link_sat_qr": "linq_sat_qr",
        "xml_doda_sent_path": "xml_doda_sent_path",
        "xml_doda_response_path": "xml_doda_response_path",
        "status": "status",
    }
    for src_key, dst_attr in mapping.items():
        value = source.get(src_key)
        if value is not None and value != "":
            setattr(doda, dst_attr, str(value))

# Router independiente para rutas literales (deben registrarse antes que /{id})
router = APIRouter(prefix="/doda", tags=["doda"])

# ============ RUTAS LITERALES (antes del CRUD /{id}) ============


@router.get(
    "/export",
    summary="Exportar DODA por rango de fechas (CSV, TSV como XLS, o TXT con |)",
)
async def export_doda_list(
    company_id: int = Query(..., description="Company ID"),
    date_from: str = Query(..., description="Fecha inicio (YYYY-MM-DD)"),
    date_to: str = Query(..., description="Fecha fin (YYYY-MM-DD)"),
    file_format: str = Query("csv", alias="format", description="csv, xls o txt"),
    date_mode: str = Query("formatted", description="raw o formatted (fechas/horas)"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """
    Listado al estilo legacy: filtra `doda_date` (YYYYMMDD) entre inicio y fin.
    """
    tenant_id = int(validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]))
    try:
        d0, d1, fmt, mode = parse_export_params(
            date_from, date_to, file_format, date_mode
        )
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e))

    rows = list_dodas_in_date_range(
        db, tenant_id=tenant_id, company_id=company_id, date_start=d0, date_end=d1
    )
    if not rows:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="No existen DODA en el rango de fechas seleccionado.",
        )

    text = build_export_text(rows, export_format=fmt, date_mode=mode)
    content_type, default_name = _content_type_and_filename(fmt)
    data = ("\ufeff" + text).encode("utf-8")
    return StreamingResponse(
        io.BytesIO(data),
        media_type=content_type,
        headers={
            "Content-Disposition": f'attachment; filename="{default_name}"',
        },
    )


@router.get(
    "/export/pedimentos/{doda_id}",
    summary="Exportar líneas de pedimento de un DODA (CSV, TSV como XLS, TXT con |)",
)
async def export_doda_pedimentos(
    doda_id: int,
    company_id: int = Query(..., description="Company ID"),
    file_format: str = Query("xls", alias="format", description="csv, xls o txt"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """
    Reporte por DODA seleccionado: columnas alineadas al listado de pedimentos (PATENTE, DOCUMENTO, COVE, etc.).
    Si no hay líneas, se devuelve el archivo solo con encabezados.
    """
    tenant_id = int(validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]))
    try:
        fmt = parse_pedimento_export_format(file_format)
    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)
        ) from e

    doda = DodaService.get_by_id(db, doda_id, tenant_id, company_id)
    if not doda:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="DODA no encontrado."
        )

    rows = list_pedimentos_for_doda_export(
        db, tenant_id=tenant_id, company_id=company_id, doda_id=doda_id
    )
    text = build_pedimentos_export_text(rows, export_format=fmt)
    content_type, _ = _content_type_and_filename(fmt)
    fname = f"doda_pedimentos_{doda_id}.{fmt.value}"
    data = ("\ufeff" + text).encode("utf-8")
    return StreamingResponse(
        io.BytesIO(data),
        media_type=content_type,
        headers={"Content-Disposition": f'attachment; filename="{fname}"'},
    )


# Incluir rutas CRUD (contiene GET /{id}, POST /, PUT /{id}, DELETE /{id}).
# Se registra DESPUÉS de los endpoints literales para que /export, /alta-logs,
# /alta-status no sean capturados por el parámetro /{id}.
_crud_router = TenantCRUDRoutes(
    service=DodaService,
    create_schema=DodaCreateDTO,
    update_schema=DodaUpdateDTO,
    response_schema=DodaResponseDTO,
    prefix="",
    tags=["doda"],
    resource_name="DODA",
    id_name="doda_id",
    enable_list=True,
    enable_filters=True,
    list_permissions=["cat_doda.view"],
    get_permissions=["cat_doda.view"],
    create_permissions=["cat_doda.create"],
    update_permissions=["cat_doda.edit"],
    delete_permissions=["cat_doda.delete"],
).router
router.include_router(_crud_router)


@router.get(
    "/{doda_id}/detail",
    response_model=DodaDetailResponseDTO,
    summary="Get DODA by ID with all details",
)
async def get_doda_detail(
    doda_id: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    doda = DodaService.get_by_id(db, doda_id, tenant_id, company_id)
    if not doda:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="DODA not found",
        )
    return DodaDetailResponseDTO.model_validate(doda)


@router.get(
    "/{doda_id}/print",
    summary="Imprimir DODA (PDF)",
    responses={422: {"description": "Validación (p. ej. falta sello digital SAT)."}},
)
async def print_doda_pdf(
    doda_id: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """
    Genera o reutiliza el PDF almacenado en S3 cuando el contenido no ha cambiado
    (huella SHA-256 de DODA + hijos).
    """
    tenant_id = int(
        validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    )
    doda = DodaService.get_by_id(db, doda_id, tenant_id, company_id)
    if not doda:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="DODA not found",
        )

    if not (doda.sat_digital_seal and str(doda.sat_digital_seal).strip()):
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="Falta el sello digital SAT requerido para imprimir el DODA.",
        )

    content_fp = build_doda_fingerprint(db, doda_id)
    expected_key = doda_report_pdf_key(tenant_id, company_id, doda_id)

    can_reuse = (
        doda.doda_report_source_fingerprint == content_fp
        and doda.doda_report_pdf_path == expected_key
        and bool(doda.doda_report_pdf_path)
    )
    if can_reuse and settings.use_s3_object_storage and storage_s3.object_exists(expected_key):
        data = storage_s3.get_object_bytes(expected_key)
        return StreamingResponse(
            io.BytesIO(data),
            media_type="application/pdf",
            headers={"Content-Disposition": f'inline; filename="doda_{doda_id}.pdf"'},
        )

    # Re-generar: eliminar caché previa (S3 + columnas) y volver a guardar
    if settings.use_s3_object_storage:
        touch_invalidate_doda_report(
            db,
            tenant_id=tenant_id,
            company_id=company_id,
            doda_id=doda_id,
            doda=None,
        )

    doda_fresh = DodaService.get_by_id(db, doda_id, tenant_id, company_id)
    if not doda_fresh:
        raise HTTPException(status_code=404, detail="DODA not found")

    service = DodaReportPdfService()
    pdf = service.build_pdf_for_doda(db, doda_fresh)
    if settings.use_s3_object_storage:
        storage_s3.put_object_bytes(expected_key, pdf, content_type="application/pdf")

    now = datetime.now(timezone.utc)
    doda_fresh.doda_report_pdf_path = expected_key if settings.use_s3_object_storage else None
    doda_fresh.doda_report_pdf_generated_at = now
    doda_fresh.doda_report_source_fingerprint = content_fp
    db.add(doda_fresh)
    db.commit()
    if settings.use_s3_object_storage:
        pdf = storage_s3.get_object_bytes(expected_key)

    return StreamingResponse(
        io.BytesIO(pdf),
        media_type="application/pdf",
        headers={"Content-Disposition": f'inline; filename="doda_{doda_id}.pdf"'},
    )


# ============ CONTAINERS ENDPOINTS ============
@router.get(
    "/{doda_id}/containers",
    response_model=List[DodaContainerResponseDTO],
    summary="Get containers for DODA",
)
async def get_doda_containers(
    doda_id: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """Get all containers for a specific DODA"""
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    containers = DodaService.get_containers(db, doda_id)
    return [DodaContainerResponseDTO.model_validate(c) for c in containers]


@router.post(
    "/{doda_id}/containers",
    response_model=DodaContainerResponseDTO,
    status_code=status.HTTP_201_CREATED,
    summary="Add container to DODA",
)
async def add_container(
    doda_id: int,
    container_data: DodaContainerCreateDTO,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """Add a new container to a DODA"""
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
    container = DodaService.add_container(db, doda_id, container_data)
    if not container:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="DODA not found",
        )
    return DodaContainerResponseDTO.model_validate(container)


@router.put(
    "/{doda_id}/containers/{container_line}",
    response_model=DodaContainerResponseDTO,
    summary="Update container",
)
async def update_container(
    doda_id: int,
    container_line: int,
    container_data: DodaContainerUpdateDTO,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """Update a container"""
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
    container = DodaService.update_container(
        db, doda_id, container_line, container_data
    )
    if not container:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Container not found",
        )
    return DodaContainerResponseDTO.model_validate(container)


@router.delete(
    "/{doda_id}/containers/{container_line}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Delete container from DODA",
)
async def delete_container(
    doda_id: int,
    container_line: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """
    Elimina un contenedor del DODA.
    Devuelve 409 si el contenedor tiene precintos (candados) asignados.
    """
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
    DodaService.delete_container(db, doda_id, container_line)
    return None


def _seal_to_response(seal) -> DodaContainerSealResponseDTO:
    """ORM usa doda_id; DTO expone doda_sys_id."""
    return DodaContainerSealResponseDTO(
        id=seal.id,
        doda_sys_id=seal.doda_id,
        seal_line=seal.seal_line,
        seal_value=seal.seal_value,
    )


# ============ CONTAINER SEALS (PRECINTOS) ============
@router.get(
    "/{doda_id}/containers/{container_line}/seals",
    response_model=List[DodaContainerSealResponseDTO],
    summary="Listar precintos de un contenedor",
)
async def get_container_seals(
    doda_id: int,
    container_line: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    seals = DodaService.get_seals_for_container(db, doda_id, container_line)
    return [_seal_to_response(s) for s in seals]


@router.post(
    "/{doda_id}/containers/{container_line}/seals",
    response_model=DodaContainerSealResponseDTO,
    status_code=status.HTTP_201_CREATED,
    summary="Agregar precinto a un contenedor",
)
async def add_container_seal(
    doda_id: int,
    container_line: int,
    seal_data: DodaContainerSealCreateDTO,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
    seal = DodaService.add_seal(db, doda_id, container_line, seal_data)
    return _seal_to_response(seal)


@router.delete(
    "/{doda_id}/containers/{container_line}/seals/{seal_line}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Eliminar precinto de un contenedor",
)
async def delete_container_seal(
    doda_id: int,
    container_line: int,
    seal_line: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
    DodaService.delete_seal(db, doda_id, container_line, seal_line)
    return None


# ============ AMERICAN PEDIMENTOS ENDPOINTS ============
@router.get(
    "/{doda_id}/american-pedimentos",
    response_model=List[DodaAmericanPedimentoResponseDTO],
    summary="Get American pedimentos for DODA",
)
async def get_american_pedimentos(
    doda_id: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """Get all American pedimentos for a specific DODA"""
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    pedimentos = DodaService.get_american_pedimentos(db, doda_id)
    return [DodaAmericanPedimentoResponseDTO.model_validate(p) for p in pedimentos]


@router.post(
    "/{doda_id}/american-pedimentos",
    response_model=DodaAmericanPedimentoResponseDTO,
    status_code=status.HTTP_201_CREATED,
    summary="Add American pedimento to DODA",
)
async def add_american_pedimento(
    doda_id: int,
    pedimento_data: DodaAmericanPedimentoCreateDTO,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """Add a new American pedimento to a DODA"""
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
    pedimento = DodaService.add_american_pedimento(db, doda_id, pedimento_data)
    if not pedimento:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="DODA not found",
        )
    return DodaAmericanPedimentoResponseDTO.model_validate(pedimento)


# ============ AMERICAN PEDIMENTOS (DELETE) ============
@router.delete(
    "/{doda_id}/american-pedimentos/{pedimento_line}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Delete American pedimento from DODA",
)
async def delete_american_pedimento(
    doda_id: int,
    pedimento_line: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """Elimina un pedimento americano del DODA."""
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
    DodaService.delete_american_pedimento(db, doda_id, pedimento_line)
    return None


# ============ PEDIMENTOS ENDPOINTS ============
@router.get(
    "/{doda_id}/pedimentos",
    response_model=List[DodaPedimentoResponseDTO],
    summary="Get pedimentos for DODA",
)
async def get_doda_pedimentos(
    doda_id: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """Get all pedimentos for a specific DODA"""
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    pedimentos = DodaService.get_pedimentos(db, doda_id)
    return [DodaPedimentoResponseDTO.model_validate(p) for p in pedimentos]


@router.post(
    "/{doda_id}/pedimentos",
    response_model=DodaPedimentoResponseDTO,
    status_code=status.HTTP_201_CREATED,
    summary="Add pedimento to DODA",
)
async def add_pedimento(
    doda_id: int,
    pedimento_data: DodaPedimentoCreateDTO,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """Add a new pedimento to a DODA"""
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
    pedimento = DodaService.add_pedimento(db, doda_id, pedimento_data)
    if not pedimento:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="DODA not found",
        )
    return DodaPedimentoResponseDTO.model_validate(pedimento)


@router.delete(
    "/{doda_id}/pedimentos/{pedimento_line}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Delete pedimento from DODA",
)
async def delete_pedimento(
    doda_id: int,
    pedimento_line: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    """Elimina un pedimento del DODA."""
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
    DodaService.delete_pedimento(db, doda_id, pedimento_line)
    return None


# ============ ALTA DODA ENDPOINTS ============


@router.get(
    "/{doda_id}/alta/elegibilidad",
    summary="Verificar elegibilidad para Alta DODA",
    tags=["doda-alta"],
)
async def get_doda_elegibilidad(
    doda_id: int,
    company_id: int = Query(..., description="Company ID"),
    variant: str = Query("doda", description="Tipo de alta: 'doda' o 'pita'"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
) -> Dict[str, Any]:
    """
    Verifica si el DODA cumple los requisitos para enviar el alta al servicio externo.
    Porta las validaciones del sistema legacy (campos requeridos, max 4 contenedores,
    gafete si DODA, patente vs agente, certificados DODA en VU).
    """
    tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    user_email = (
        current_user.get("email")
        or current_user.get("preferred_username")
        or ""
    )
    service = DodaAltaService(db)
    result = service.check_elegibilidad(
        doda_id=doda_id,
        tenant_id=int(tenant_id),
        company_id=company_id,
        variant=variant,
        user_email=user_email,
    )
    return {
        "can_alta": result.can_alta,
        "reasons": [
            {"field": r.field, "message": r.message, "solution": r.solution}
            for r in result.reasons
        ],
    }


@router.post(
    "/{doda_id}/alta",
    summary="Enviar Alta DODA al servicio externo (asíncrono)",
    tags=["doda-alta"],
)
async def post_doda_alta(
    doda_id: int,
    company_id: int = Query(..., description="Company ID"),
    variant: str = Query("doda", description="Tipo de alta: 'doda' o 'pita'"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
) -> Dict[str, Any]:
    """
    Verifica elegibilidad, construye el payload desde los datos del DODA y su VU,
    y envía el alta al servicio externo. Retorna {task_id, status, message} para polling.
    """
    tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
    user_email = (
        current_user.get("email")
        or current_user.get("preferred_username")
        or ""
    )
    service = DodaAltaService(db)

    elegibilidad = service.check_elegibilidad(
        doda_id=doda_id,
        tenant_id=int(tenant_id),
        company_id=company_id,
        variant=variant,
        user_email=user_email,
    )
    if not elegibilidad.can_alta:
        reasons = [
            {"field": r.field, "message": r.message, "solution": r.solution}
            for r in elegibilidad.reasons
        ]
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail={"message": "El DODA no cumple los requisitos para el alta.", "reasons": reasons},
        )

    try:
        payload = service.build_alta_payload(
            doda_id=doda_id,
            tenant_id=int(tenant_id),
            company_id=company_id,
            variant=variant,
            user_email=user_email,
        )
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=str(exc),
        ) from exc

    try:
        ext = DodaExternalService()
        result = ext.post_alta(payload)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=str(exc),
        ) from exc
    except Exception as exc:
        logger.exception("Error al enviar alta DODA al servicio externo: doda_id=%s", doda_id)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Error al contactar el servicio DODA externo: {exc}",
        ) from exc

    # Persistir el log del alta
    doda_record = DodaService.get_by_id(db, doda_id, tenant_id, company_id)
    if doda_record:
        try:
            DodaAltaLogService.create_from_alta_result(
                db=db,
                doda=doda_record,
                company_id=company_id,
                tenant_id=int(tenant_id),
                variant=variant,
                ext_result=result,
                action="alta",
            )
        except Exception:
            logger.exception("Error persistiendo DodaAltaLog para doda_id=%s", doda_id)

    return result


@router.get(
    "/alta-status/{task_id}",
    summary="Consultar estado de tarea de Alta DODA",
    tags=["doda-alta"],
)
async def get_doda_alta_status(
    task_id: str,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
) -> Any:
    """
    Proxy transparente al servicio externo para consultar el estado de una tarea de alta DODA.
    """
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    try:
        ext = DodaExternalService()
        return ext.get_status(task_id)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=str(exc),
        ) from exc
    except Exception as exc:
        logger.exception("Error consultando estado DODA task_id=%s", task_id)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Error al consultar el estado de la tarea DODA: {exc}",
        ) from exc


@router.post(
    "/{doda_id}/consulta",
    summary="Enviar Consulta DODA al servicio externo (asíncrono)",
    tags=["doda-alta"],
)
async def post_doda_consulta(
    doda_id: int,
    company_id: int = Query(..., description="Company ID"),
    variant: str = Query("doda", description="Tipo de alta: 'doda' o 'pita'"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
) -> Dict[str, Any]:
    tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
    user_email = (
        current_user.get("email")
        or current_user.get("preferred_username")
        or ""
    )
    service = DodaAltaService(db)
    try:
        payload = service.build_consulta_payload(
            doda_id=doda_id,
            tenant_id=int(tenant_id),
            company_id=company_id,
            variant=variant,
            user_email=user_email,
        )
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=str(exc),
        ) from exc

    ext_result: Dict[str, Any]
    try:
        ext = DodaExternalService()
        ext_result = ext.post_consulta(payload)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=str(exc),
        ) from exc
    except Exception as exc:
        logger.exception("Error al enviar consulta DODA al servicio externo: doda_id=%s", doda_id)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Error al contactar el servicio DODA externo (consulta): {exc}",
        ) from exc

    doda_record = DodaService.get_by_id(db, doda_id, int(tenant_id), company_id)
    if doda_record:
        try:
            DodaAltaLogService.create_from_alta_result(
                db=db,
                doda=doda_record,
                company_id=company_id,
                tenant_id=int(tenant_id),
                variant=variant,
                ext_result=ext_result,
                action="consulta",
            )
        except Exception:
            logger.exception("Error persistiendo DodaAltaLog(consulta) para doda_id=%s", doda_id)
    return ext_result


@router.get(
    "/consulta-status/{task_id}",
    summary="Consultar estado de tarea de Consulta DODA",
    tags=["doda-alta"],
)
async def get_doda_consulta_status(
    task_id: str,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
) -> Any:
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    try:
        ext = DodaExternalService()
        return ext.get_consulta_status(task_id)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=str(exc),
        ) from exc
    except Exception as exc:
        logger.exception("Error consultando consulta-status DODA task_id=%s", task_id)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Error al consultar el estado de la consulta DODA: {exc}",
        ) from exc


@router.post(
    "/{doda_id}/consulta-apply/{task_id}",
    summary="Aplicar resultado exitoso de consulta DODA al registro local",
    tags=["doda-alta"],
)
async def post_doda_consulta_apply(
    doda_id: int,
    task_id: str,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
) -> Dict[str, Any]:
    tenant_id = int(
        validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
    )
    doda_record = DodaService.get_by_id(db, doda_id, tenant_id, company_id)
    if not doda_record:
        raise HTTPException(status_code=404, detail="DODA no encontrado.")

    try:
        ext = DodaExternalService()
        status_payload = ext.get_consulta_status(task_id)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=str(exc),
        ) from exc
    except Exception as exc:
        logger.exception("Error al consultar consulta-status para aplicar: task_id=%s", task_id)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Error al consultar el estado de la consulta DODA: {exc}",
        ) from exc

    task_state = str(status_payload.get("state") or status_payload.get("status") or "").upper()
    if task_state != "SUCCESS":
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="La tarea de consulta aún no está en estado SUCCESS.",
        )

    try:
        _apply_consulta_success_to_doda(doda_record, status_payload)
        if not (doda_record.status or "").strip():
            doda_record.status = "VALIDADO"
        db.add(doda_record)
        db.commit()
        db.refresh(doda_record)
    except Exception as exc:
        db.rollback()
        logger.exception("Error aplicando consulta-status al DODA id=%s", doda_id)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"No se pudo aplicar el resultado de consulta al DODA: {exc}",
        ) from exc

    return {
        "message": "Resultado de consulta aplicado correctamente.",
        "doda_id": doda_id,
        "task_id": task_id,
        "state": task_state,
    }


@router.post(
    "/{doda_id}/eliminar",
    summary="Enviar Eliminación DODA al servicio externo (asíncrono)",
    tags=["doda-alta"],
)
async def post_doda_eliminar(
    doda_id: int,
    company_id: int = Query(..., description="Company ID"),
    variant: str = Query("doda", description="Tipo de alta: 'doda' o 'pita'"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
) -> Dict[str, Any]:
    tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
    user_email = (
        current_user.get("email")
        or current_user.get("preferred_username")
        or ""
    )
    service = DodaAltaService(db)
    try:
        payload = service.build_eliminar_payload(
            doda_id=doda_id,
            tenant_id=int(tenant_id),
            company_id=company_id,
            variant=variant,
            user_email=user_email,
        )
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=str(exc),
        ) from exc

    ext_result: Dict[str, Any]
    try:
        ext = DodaExternalService()
        ext_result = ext.post_eliminar(payload)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=str(exc),
        ) from exc
    except Exception as exc:
        logger.exception("Error al enviar eliminación DODA al servicio externo: doda_id=%s", doda_id)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Error al contactar el servicio DODA externo (eliminación): {exc}",
        ) from exc

    doda_record = DodaService.get_by_id(db, doda_id, int(tenant_id), company_id)
    if doda_record:
        try:
            DodaAltaLogService.create_from_alta_result(
                db=db,
                doda=doda_record,
                company_id=company_id,
                tenant_id=int(tenant_id),
                variant=variant,
                ext_result=ext_result,
                action="eliminar",
            )
        except Exception:
            logger.exception("Error persistiendo DodaAltaLog(eliminar) para doda_id=%s", doda_id)

        try:
            doda_record.integration_number = None
            doda_record.transaction_number = None
            doda_record.status = "PENDIENTE"
            doda_record.sat_digital_seal = None
            doda_record.sat_certificate = None
            doda_record.serial_number = None
            doda_record.electronic_signature = None
            doda_record.original_chain = None
            doda_record.sat_original_chain = None
            doda_record.linq_sat_qr = None
            doda_record.xml_doda_sent_path = None
            doda_record.xml_doda_response_path = None
            db.add(doda_record)
            db.commit()
        except Exception:
            db.rollback()
            logger.exception("Error desprocesando DODA local tras eliminación id=%s", doda_id)
    return ext_result


@router.get(
    "/eliminar-status/{task_id}",
    summary="Consultar estado de tarea de Eliminación DODA",
    tags=["doda-alta"],
)
async def get_doda_eliminar_status(
    task_id: str,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
) -> Any:
    validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    try:
        ext = DodaExternalService()
        return ext.get_eliminar_status(task_id)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=str(exc),
        ) from exc
    except Exception as exc:
        logger.exception("Error consultando eliminar-status DODA task_id=%s", task_id)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Error al consultar el estado de la eliminación DODA: {exc}",
        ) from exc


# ============ DODA ALTA LOG CRUD ============


@router.get(
    "/alta-logs",
    response_model=DodaAltaLogListResponse,
    summary="Listar registros de alta DODA",
    tags=["doda-alta"],
)
async def list_doda_alta_logs(
    company_id: int = Query(...),
    page: int = Query(1, ge=1),
    page_size: int = Query(50, ge=1, le=200),
    doda_id: int = Query(None),
    search: str = Query(None),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    return DodaAltaLogService.list(
        db, company_id, int(tenant_id), page, page_size, doda_id, search
    )


@router.get(
    "/alta-logs/{log_id}",
    response_model=DodaAltaLogResponseDTO,
    summary="Obtener registro de alta DODA",
    tags=["doda-alta"],
)
async def get_doda_alta_log(
    log_id: int,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])
    record = DodaAltaLogService.get(db, log_id, company_id, int(tenant_id))
    if not record:
        raise HTTPException(status_code=404, detail="Registro de alta DODA no encontrado.")
    return DodaAltaLogResponseDTO.model_validate(record)


@router.post(
    "/alta-logs",
    response_model=DodaAltaLogResponseDTO,
    status_code=status.HTTP_201_CREATED,
    summary="Crear registro de alta DODA manualmente",
    tags=["doda-alta"],
)
async def create_doda_alta_log(
    dto: DodaAltaLogCreateDTO,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.create"])
    record = DodaAltaLogService.create(db, dto, company_id, int(tenant_id))
    return DodaAltaLogResponseDTO.model_validate(record)


@router.put(
    "/alta-logs/{log_id}",
    response_model=DodaAltaLogResponseDTO,
    summary="Actualizar registro de alta DODA",
    tags=["doda-alta"],
)
async def update_doda_alta_log(
    log_id: int,
    dto: DodaAltaLogUpdateDTO,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"])
    record = DodaAltaLogService.get(db, log_id, company_id, int(tenant_id))
    if not record:
        raise HTTPException(status_code=404, detail="Registro de alta DODA no encontrado.")
    record = DodaAltaLogService.update(db, record, dto)
    return DodaAltaLogResponseDTO.model_validate(record)


@router.delete(
    "/alta-logs/{log_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Eliminar registro de alta DODA",
    tags=["doda-alta"],
)
async def delete_doda_alta_log(
    log_id: int,
    company_id: int = Query(...),
    db: Session = Depends(get_core_db),
    current_user: dict = Depends(get_current_user),
):
    tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"])
    record = DodaAltaLogService.get(db, log_id, company_id, int(tenant_id))
    if not record:
        raise HTTPException(status_code=404, detail="Registro de alta DODA no encontrado.")
    DodaAltaLogService.delete(db, record)
    return None
