"""
Capa de servicio para lógica de negocio de DODA
"""

import logging
from typing import Any, Dict, List, Optional, Tuple

from fastapi import HTTPException
from sqlalchemy import func
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from .dto import (
    DodaCreateDTO,
    DodaResponseDTO,
    DodaUpdateDTO,
    DodaContainerCreateDTO,
    DodaContainerUpdateDTO,
    DodaContainerSealCreateDTO,
    DodaAmericanPedimentoCreateDTO,
    DodaAmericanPedimentoUpdateDTO,
    DodaPedimentoCreateDTO,
    DodaPedimentoUpdateDTO,
)
from .models import (
    Doda,
    DodaContainer,
    DodaContainerSeal,
    DodaAmericanPedimento,
    DodaPedimento,
)
from .print_cache import touch_invalidate_doda_report

logger = logging.getLogger(__name__)


class DodaService:
    """Servicio para gestión de DODA"""

    @staticmethod
    def _ensure_editable_doda(doda: Optional[Doda]) -> None:
        if not doda:
            return
        if (doda.integration_number or "").strip():
            raise HTTPException(
                status_code=422,
                detail=(
                    "El DODA ya fue generado (tiene número de integración). "
                    "Elimínelo primero para poder editarlo."
                ),
            )

    @staticmethod
    def _invalidate_report_after_mutation(
        db: Session,
        *,
        doda_id: int,
        tenant_id: int,
        company_id: int,
    ) -> None:
        """Limpia PDF de reporte y objeto S3 previo (patrón artefactos)."""
        touch_invalidate_doda_report(
            db, tenant_id=tenant_id, company_id=company_id, doda_id=doda_id
        )

    @staticmethod
    def get_all(
        db: Session,
        tenant_id: int,
        company_id: int,
        skip: int = 0,
        limit: int = 50,
        filters: Optional[Dict[str, Any]] = None,
    ) -> Tuple[List[Doda], int]:
        """Get all DODAs with pagination"""
        query = db.query(Doda).filter(
            Doda.tenant_id == tenant_id,
            Doda.company_id == company_id
        )

        if filters:
            if filters.get("integration_number"):
                query = query.filter(
                    Doda.integration_number.ilike(
                        f"%{filters['integration_number']}%")
                )
            if filters.get("status"):
                query = query.filter(
                    Doda.status.ilike(f"%{filters['status']}%"))
            if filters.get("patent"):
                query = query.filter(
                    Doda.patent.ilike(f"%{filters['patent']}%"))
            if filters.get("operation_type"):
                query = query.filter(
                    Doda.operation_type == filters["operation_type"])

        total = query.count()
        dodas = query.offset(skip).limit(limit).all()
        return dodas, total

    @staticmethod
    def get_by_id(
        db: Session, id: int, tenant_id: int, company_id: int
    ) -> Optional[Doda]:
        """Get DODA by ID"""
        return db.query(Doda).filter(
            Doda.id == id,
            Doda.tenant_id == tenant_id,
            Doda.company_id == company_id
        ).first()

    @staticmethod
    def create(
        db: Session, doda_data: DodaCreateDTO, tenant_id: int, company_id: int
    ) -> Doda:
        """Create a new DODA"""
        try:
            db_doda = Doda(
                **doda_data.model_dump(exclude_unset=True),
                tenant_id=tenant_id,
                company_id=company_id
            )
            db.add(db_doda)
            db.commit()
            db.refresh(db_doda)
            DodaService._invalidate_report_after_mutation(
                db, doda_id=db_doda.id, tenant_id=tenant_id, company_id=company_id
            )
            return db_doda
        except IntegrityError as e:
            db.rollback()
            logger.error(f"IntegrityError creating DODA: {str(e)}")
            raise HTTPException(status_code=400, detail="Error creating DODA")
        except Exception as e:
            db.rollback()
            logger.error(f"Error creating DODA: {str(e)}")
            raise HTTPException(status_code=500, detail="Error creating DODA")

    @staticmethod
    def update(
        db: Session, id: int, tenant_id: int, doda_data: DodaUpdateDTO, company_id: int
    ) -> Optional[Doda]:
        """Update a DODA"""
        try:
            db_doda = DodaService.get_by_id(db, id, tenant_id, company_id)
            if not db_doda:
                return None
            DodaService._ensure_editable_doda(db_doda)

            for key, value in doda_data.model_dump(exclude_unset=True).items():
                setattr(db_doda, key, value)

            db.commit()
            db.refresh(db_doda)
            DodaService._invalidate_report_after_mutation(
                db, doda_id=db_doda.id, tenant_id=tenant_id, company_id=company_id
            )
            return db_doda
        except IntegrityError as e:
            db.rollback()
            logger.error(f"IntegrityError updating DODA: {str(e)}")
            raise HTTPException(status_code=400, detail="Error updating DODA")
        except Exception as e:
            db.rollback()
            logger.error(f"Error updating DODA: {str(e)}")
            raise HTTPException(status_code=500, detail="Error updating DODA")

    @staticmethod
    def delete(
        db: Session, id: int, tenant_id: int, company_id: int
    ) -> bool:
        """Delete a DODA"""
        db_doda = DodaService.get_by_id(db, id, tenant_id, company_id)
        if not db_doda:
            return False

        try:
            tid = int(db_doda.tenant_id)
            cid = int(db_doda.company_id)
            did = int(db_doda.id)
            DodaService._invalidate_report_after_mutation(
                db, doda_id=did, tenant_id=tid, company_id=cid
            )
            to_delete = DodaService.get_by_id(db, id, tenant_id, company_id)
            if not to_delete:
                return True
            db.delete(to_delete)
            db.commit()
            return True
        except IntegrityError as e:
            db.rollback()
            logger.error(f"Error de integridad al eliminar DODA {id}: {str(e)}")
            raise HTTPException(
                status_code=400,
                detail="No se puede eliminar este DODA porque tiene registros relacionados. Primero debe eliminar o reasignar esos registros."
            )
        except Exception as e:
            db.rollback()
            logger.error(f"Error deleting DODA: {str(e)}")
            raise HTTPException(status_code=500, detail="Error deleting DODA")

    # ============ CONTAINERS ============
    @staticmethod
    def add_container(
        db: Session, doda_id: int, container_data: DodaContainerCreateDTO
    ) -> Optional[DodaContainer]:
        """Add a container to a DODA"""
        try:
            doda = db.query(Doda).filter(Doda.id == doda_id).first()
            if not doda:
                return None
            DodaService._ensure_editable_doda(doda)

            cv = (container_data.container_value or "").strip()
            if not cv:
                raise HTTPException(
                    status_code=400,
                    detail="El valor del contenedor no puede estar vacío.",
                )

            # Get max line number
            max_line = (
                db.query(DodaContainer)
                .filter(DodaContainer.doda_id == doda_id)
                .count()
            )

            dump = container_data.model_dump(exclude_unset=True)
            dump.pop("seals_detail", None)
            dump["container_value"] = cv

            db_container = DodaContainer(
                doda_id=doda_id,
                container_line=max_line + 1,
                tenant_id=doda.tenant_id,
                company_id=doda.company_id,
                **{k: v for k, v in dump.items() if k not in ("doda_id", "container_line")},
            )
            db.add(db_container)
            db.commit()
            db.refresh(db_container)
            DodaService._invalidate_report_after_mutation(
                db,
                doda_id=doda_id,
                tenant_id=int(doda.tenant_id),
                company_id=int(doda.company_id),
            )
            return db_container
        except HTTPException:
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"Error adding container: {str(e)}")
            raise HTTPException(
                status_code=500, detail="Error adding container")

    @staticmethod
    def update_container(
        db: Session,
        doda_id: int,
        container_line: int,
        container_data: DodaContainerUpdateDTO,
    ) -> Optional[DodaContainer]:
        """Update a container"""
        try:
            db_container = (
                db.query(DodaContainer)
                .filter(
                    DodaContainer.doda_id == doda_id,
                    DodaContainer.container_line == container_line,
                )
                .first()
            )
            if not db_container:
                return None
            doda = db.get(Doda, doda_id)
            DodaService._ensure_editable_doda(doda)

            for key, value in container_data.model_dump(exclude_unset=True).items():
                setattr(db_container, key, value)

            db.commit()
            db.refresh(db_container)
            doda = db.get(Doda, doda_id)
            if doda:
                DodaService._invalidate_report_after_mutation(
                    db,
                    doda_id=doda_id,
                    tenant_id=int(doda.tenant_id),
                    company_id=int(doda.company_id),
                )
            return db_container
        except Exception as e:
            db.rollback()
            logger.error(f"Error updating container: {str(e)}")
            raise HTTPException(
                status_code=500, detail="Error updating container")

    @staticmethod
    def get_containers(db: Session, doda_id: int) -> List[DodaContainer]:
        """Get all containers for a DODA"""
        return (
            db.query(DodaContainer)
            .filter(DodaContainer.doda_id == doda_id)
            .all()
        )

    @staticmethod
    def delete_container(
        db: Session, doda_id: int, container_line: int
    ) -> None:
        """
        Delete a container from a DODA.
        Raises HTTP 409 if the container has seals assigned (Clarion: validación precintos).
        Raises HTTP 404 if not found.
        """
        db_container = (
            db.query(DodaContainer)
            .filter(
                DodaContainer.doda_id == doda_id,
                DodaContainer.container_line == container_line,
            )
            .first()
        )
        if not db_container:
            raise HTTPException(status_code=404, detail="Contenedor no encontrado.")
        doda = db.get(Doda, doda_id)
        DodaService._ensure_editable_doda(doda)

        has_seals = bool(db_container.seals_detail)
        if not has_seals and db_container.seals:
            has_seals = any(s.strip() for s in db_container.seals.split(","))

        if has_seals:
            raise HTTPException(
                status_code=409,
                detail=(
                    "El contenedor tiene uno o más precintos asignados. "
                    "No se puede borrar el contenedor."
                ),
            )

        try:
            doda = db.get(Doda, doda_id)
            tid = int(doda.tenant_id) if doda else 0
            cid = int(doda.company_id) if doda else 0
            db.delete(db_container)
            db.commit()
            if doda:
                DodaService._invalidate_report_after_mutation(
                    db, doda_id=doda_id, tenant_id=tid, company_id=cid, doda=None
                )
        except Exception as e:
            db.rollback()
            logger.error(f"Error deleting container doda_id={doda_id} line={container_line}: {e}")
            raise HTTPException(status_code=500, detail="Error al eliminar el contenedor.")

    # ============ CONTAINER SEALS (PRECINTOS) ============
    MAX_SEALS_PER_DODA = 8

    @staticmethod
    def get_seals_for_container(
        db: Session, doda_id: int, container_line: int
    ) -> List[DodaContainerSeal]:
        """Precintos de un contenedor (por línea de contenedor dentro del DODA)."""
        db_container = (
            db.query(DodaContainer)
            .filter(
                DodaContainer.doda_id == doda_id,
                DodaContainer.container_line == container_line,
            )
            .first()
        )
        if not db_container:
            return []
        return (
            db.query(DodaContainerSeal)
            .filter(DodaContainerSeal.container_id == db_container.id)
            .order_by(DodaContainerSeal.seal_line.asc())
            .all()
        )

    @staticmethod
    def add_seal(
        db: Session,
        doda_id: int,
        container_line: int,
        seal_data: DodaContainerSealCreateDTO,
    ) -> DodaContainerSeal:
        """
        Añade un precinto (candado) a un contenedor.
        Máximo 8 precintos en total por DODA (legacy Clarion: gDoda_Contenedores_Candados).
        """
        doda = db.query(Doda).filter(Doda.id == doda_id).first()
        if not doda:
            raise HTTPException(status_code=404, detail="DODA no encontrado.")
            DodaService._ensure_editable_doda(doda)

        container = (
            db.query(DodaContainer)
            .filter(
                DodaContainer.doda_id == doda_id,
                DodaContainer.container_line == container_line,
            )
            .first()
        )
        if not container:
            raise HTTPException(status_code=404, detail="Contenedor no encontrado.")
        doda = db.get(Doda, doda_id)
        DodaService._ensure_editable_doda(doda)

        raw_value = (seal_data.seal_value or "").strip()
        if not raw_value:
            raise HTTPException(
                status_code=400,
                detail="El campo precinto no puede estar vacío.",
            )

        total_seals = (
            db.query(DodaContainerSeal)
            .filter(DodaContainerSeal.doda_id == doda_id)
            .count()
        )
        if total_seals >= DodaService.MAX_SEALS_PER_DODA:
            raise HTTPException(
                status_code=400,
                detail=(
                    "El DODA supera el máximo de precintos (8). "
                    "Revise los precintos registrados."
                ),
            )

        max_line = (
            db.query(func.max(DodaContainerSeal.seal_line))
            .filter(DodaContainerSeal.container_id == container.id)
            .scalar()
        )
        next_line = (max_line or 0) + 1

        try:
            db_seal = DodaContainerSeal(
                doda_id=doda_id,
                container_id=container.id,
                seal_line=next_line,
                seal_value=raw_value,
                tenant_id=doda.tenant_id,
                company_id=doda.company_id,
            )
            db.add(db_seal)
            db.commit()
            db.refresh(db_seal)
            DodaService._invalidate_report_after_mutation(
                db,
                doda_id=doda_id,
                tenant_id=int(doda.tenant_id),
                company_id=int(doda.company_id),
            )
            return db_seal
        except Exception as e:
            db.rollback()
            logger.error(
                "Error adding seal doda_id=%s container_line=%s: %s",
                doda_id,
                container_line,
                e,
            )
            raise HTTPException(status_code=500, detail="Error al agregar el precinto.")

    @staticmethod
    def delete_seal(
        db: Session, doda_id: int, container_line: int, seal_line: int
    ) -> None:
        """Elimina un precinto por línea de contenedor y línea de candado."""
        container = (
            db.query(DodaContainer)
            .filter(
                DodaContainer.doda_id == doda_id,
                DodaContainer.container_line == container_line,
            )
            .first()
        )
        if not container:
            raise HTTPException(status_code=404, detail="Contenedor no encontrado.")

        seal = (
            db.query(DodaContainerSeal)
            .filter(
                DodaContainerSeal.container_id == container.id,
                DodaContainerSeal.seal_line == seal_line,
            )
            .first()
        )
        if not seal:
            raise HTTPException(status_code=404, detail="Precinto no encontrado.")

        try:
            doda = db.get(Doda, doda_id)
            db.delete(seal)
            db.commit()
            if doda:
                DodaService._invalidate_report_after_mutation(
                    db,
                    doda_id=doda_id,
                    tenant_id=int(doda.tenant_id),
                    company_id=int(doda.company_id),
                )
        except Exception as e:
            db.rollback()
            logger.error(
                "Error deleting seal doda_id=%s line=%s seal_line=%s: %s",
                doda_id,
                container_line,
                seal_line,
                e,
            )
            raise HTTPException(status_code=500, detail="Error al eliminar el precinto.")

    # ============ AMERICAN PEDIMENTOS ============
    @staticmethod
    def add_american_pedimento(
        db: Session, doda_id: int, pedimento_data: DodaAmericanPedimentoCreateDTO
    ) -> Optional[DodaAmericanPedimento]:
        """Add an American pedimento to a DODA"""
        try:
            doda = db.query(Doda).filter(Doda.id == doda_id).first()
            if not doda:
                return None
            DodaService._ensure_editable_doda(doda)

            tipo = (pedimento_data.american_pedimento_type or "").strip()
            valor = (pedimento_data.american_pedimento_value or "").strip()
            # Legacy: IF DODA:DespachoAduanero = '3' (PITA) → sin validación de tipo; web usa customs_clearance=1 para PITA
            clearance = getattr(doda, "customs_clearance", None)
            if clearance != 1:
                if not tipo:
                    raise HTTPException(
                        status_code=400,
                        detail="El tipo de pedimento americano es obligatorio.",
                    )
                op = (doda.operation_type or "").strip().upper()
                if op in ("I", "1"):
                    allowed = {"1", "2", "3", "4", "5"}
                elif op in ("E", "2"):
                    allowed = {"6", "7", "8"}
                else:
                    raise HTTPException(
                        status_code=400,
                        detail="El tipo de operación del DODA no permite validar el pedimento americano.",
                    )
                if tipo not in allowed:
                    raise HTTPException(
                        status_code=400,
                        detail="El tipo de pedimento americano no es correcto para el tipo de operación.",
                    )

            max_line = (
                db.query(DodaAmericanPedimento)
                .filter(DodaAmericanPedimento.doda_id == doda_id)
                .count()
            )

            dump = pedimento_data.model_dump(exclude_unset=True)
            if clearance == 1:
                dump["american_pedimento_type"] = tipo or None

            db_pedimento = DodaAmericanPedimento(
                doda_id=doda_id,
                american_pedimento_line=max_line + 1,
                tenant_id=doda.tenant_id,
                company_id=doda.company_id,
                **dump,
            )
            db.add(db_pedimento)
            db.commit()
            db.refresh(db_pedimento)
            DodaService._invalidate_report_after_mutation(
                db,
                doda_id=doda_id,
                tenant_id=int(doda.tenant_id),
                company_id=int(doda.company_id),
            )
            return db_pedimento
        except HTTPException:
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"Error adding American pedimento: {str(e)}")
            raise HTTPException(
                status_code=500, detail="Error adding American pedimento"
            )

    @staticmethod
    def get_american_pedimentos(db: Session, doda_id: int) -> List[DodaAmericanPedimento]:
        """Get all American pedimentos for a DODA"""
        return (
            db.query(DodaAmericanPedimento)
            .filter(DodaAmericanPedimento.doda_id == doda_id)
            .all()
        )

    # ============ AMERICAN PEDIMENTOS (DELETE) ============
    @staticmethod
    def delete_american_pedimento(
        db: Session, doda_id: int, pedimento_line: int
    ) -> None:
        """Delete an American pedimento from a DODA."""
        db_pedimento = (
            db.query(DodaAmericanPedimento)
            .filter(
                DodaAmericanPedimento.doda_id == doda_id,
                DodaAmericanPedimento.american_pedimento_line == pedimento_line,
            )
            .first()
        )
        if not db_pedimento:
            raise HTTPException(
                status_code=404, detail="Pedimento americano no encontrado."
            )
        doda = db.get(Doda, doda_id)
        DodaService._ensure_editable_doda(doda)
        try:
            db.delete(db_pedimento)
            db.commit()
            if doda:
                DodaService._invalidate_report_after_mutation(
                    db,
                    doda_id=doda_id,
                    tenant_id=int(doda.tenant_id),
                    company_id=int(doda.company_id),
                )
        except Exception as e:
            db.rollback()
            logger.error(
                f"Error deleting american pedimento doda_id={doda_id} line={pedimento_line}: {e}"
            )
            raise HTTPException(
                status_code=500, detail="Error al eliminar el pedimento americano."
            )

    # ============ PEDIMENTOS ============
    @staticmethod
    def add_pedimento(
        db: Session, doda_id: int, pedimento_data: DodaPedimentoCreateDTO
    ) -> Optional[DodaPedimento]:
        """Add a pedimento to a DODA"""
        try:
            doda = db.query(Doda).filter(Doda.id == doda_id).first()
            if not doda:
                return None
            DodaService._ensure_editable_doda(doda)

            max_line = (
                db.query(DodaPedimento)
                .filter(DodaPedimento.doda_id == doda_id)
                .count()
            )

            db_pedimento = DodaPedimento(
                doda_id=doda_id,
                pedimento_line=max_line + 1,
                tenant_id=doda.tenant_id,
                company_id=doda.company_id,
                **pedimento_data.model_dump(exclude_unset=True),
            )
            db.add(db_pedimento)
            db.commit()
            db.refresh(db_pedimento)
            DodaService._invalidate_report_after_mutation(
                db,
                doda_id=doda_id,
                tenant_id=int(doda.tenant_id),
                company_id=int(doda.company_id),
            )
            return db_pedimento
        except HTTPException:
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"Error adding pedimento: {str(e)}")
            raise HTTPException(
                status_code=500, detail="Error adding pedimento")

    @staticmethod
    def get_pedimentos(db: Session, doda_id: int) -> List[DodaPedimento]:
        """Get all pedimentos for a DODA"""
        return (
            db.query(DodaPedimento).filter(
                DodaPedimento.doda_id == doda_id).all()
        )

    @staticmethod
    def delete_pedimento(
        db: Session, doda_id: int, pedimento_line: int
    ) -> None:
        """Delete a pedimento from a DODA."""
        db_pedimento = (
            db.query(DodaPedimento)
            .filter(
                DodaPedimento.doda_id == doda_id,
                DodaPedimento.pedimento_line == pedimento_line,
            )
            .first()
        )
        if not db_pedimento:
            raise HTTPException(status_code=404, detail="Pedimento no encontrado.")
        doda = db.get(Doda, doda_id)
        DodaService._ensure_editable_doda(doda)
        try:
            db.delete(db_pedimento)
            db.commit()
            if doda:
                DodaService._invalidate_report_after_mutation(
                    db,
                    doda_id=doda_id,
                    tenant_id=int(doda.tenant_id),
                    company_id=int(doda.company_id),
                )
        except Exception as e:
            db.rollback()
            logger.error(
                f"Error deleting pedimento doda_id={doda_id} line={pedimento_line}: {e}"
            )
            raise HTTPException(status_code=500, detail="Error al eliminar el pedimento.")
