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

from fastapi import HTTPException
from sqlalchemy import or_
from sqlalchemy.orm import Session

from .models import Manifest
from .dtos import ManifestCreateDTO, ManifestUpdateDTO, ManifestResponseDTO

logger = logging.getLogger(__name__)


class ManifestService:
    @staticmethod
    def get_all(
        db: Session,
        tenant_id: int,
        company_id: int,
        skip: int = 0,
        limit: int = 100,
        filters: Optional[Dict[str, Any]] = None,
    ) -> Tuple[List[Manifest], int]:
        query = db.query(Manifest).filter(
            Manifest.tenant_id == tenant_id,
            Manifest.company_id == company_id,
        )

        if filters:
            if filters.get("search"):
                search_pattern = f"%{filters['search']}%"
                query = query.filter(
                    or_(
                        Manifest.manifest_number.ilike(search_pattern),
                        Manifest.description.ilike(search_pattern),
                    )
                )

            if filters.get("manifest_number"):
                query = query.filter(
                    Manifest.manifest_number.ilike(f"%{filters['manifest_number']}%")
                )

            if filters.get("start_date"):
                query = query.filter(Manifest.entry_date >= filters["start_date"])

            if filters.get("end_date"):
                query = query.filter(Manifest.entry_date <= filters["end_date"])

        total = query.count()
        items = query.offset(skip).limit(limit).all()

        return items, total

    @staticmethod
    def get_by_id(
        db: Session, manifest_id: int, tenant_id: int, company_id: int
    ) -> Optional[Manifest]:
        return (
            db.query(Manifest)
            .filter(
                Manifest.id == manifest_id,
                Manifest.tenant_id == tenant_id,
                Manifest.company_id == company_id,
            )
            .first()
        )

    @staticmethod
    def create(
        db: Session,
        manifest_data: ManifestCreateDTO,
        tenant_id: int,
        company_id: int,
    ) -> Manifest:
        try:
            db_manifest = Manifest(
                **manifest_data.model_dump(exclude_unset=True),
                tenant_id=tenant_id,
                company_id=company_id,
            )
            db.add(db_manifest)
            db.commit()
            db.refresh(db_manifest)
            return db_manifest
        except Exception as e:
            db.rollback()
            logger.error(f"Error creating manifest: {str(e)}")
            raise HTTPException(status_code=500, detail="Error creating manifest")

    @staticmethod
    def update(
        db: Session,
        manifest_id: int,
        tenant_id: int,
        company_id: int,
        manifest_data: ManifestUpdateDTO,
    ) -> Optional[Manifest]:
        manifest = ManifestService.get_by_id(db, manifest_id, tenant_id, company_id)
        if not manifest:
            return None

        try:
            update_data = manifest_data.model_dump(exclude_unset=True)
            for field, value in update_data.items():
                setattr(manifest, field, value)

            db.commit()
            db.refresh(manifest)
            return manifest
        except Exception as e:
            db.rollback()
            logger.error(f"Error updating manifest {manifest_id}: {str(e)}")
            raise HTTPException(status_code=500, detail="Error updating manifest")

    @staticmethod
    def delete(
        db: Session, manifest_id: int, tenant_id: int, company_id: int
    ) -> bool:
        manifest = ManifestService.get_by_id(db, manifest_id, tenant_id, company_id)
        if not manifest:
            return False

        try:
            db.delete(manifest)
            db.commit()
            return True
        except Exception as e:
            db.rollback()
            logger.error(f"Error deleting manifest {manifest_id}: {str(e)}")
            raise HTTPException(status_code=500, detail="Error deleting manifest")

    @staticmethod
    def update_invoice_manifest(
        db: Session,
        invoice_id: int,
        tenant_id: int,
        company_id: int,
        manifest_number: Optional[str],
    ) -> bool:
        """
        Update ONLY the manifest_number of an invoice.
        Creates compliance_mx record if it doesn't exist.
        """
        # Import models here to avoid circular dependencies if any
        from api.v1.modules.a76.invoices.models import (
            InvoiceHeader,
            InvoiceComplianceMx,
        )

        invoice = (
            db.query(InvoiceHeader)
            .filter(
                InvoiceHeader.id == invoice_id,
                InvoiceHeader.tenant_id == tenant_id,
                InvoiceHeader.company_id == company_id,
            )
            .first()
        )

        if not invoice:
            return False

        try:
            if invoice.compliance_mx:
                invoice.compliance_mx.manifest_number = manifest_number
            else:
                # Create new compliance record with defaults for required fields
                new_compliance = InvoiceComplianceMx(
                    invoice_id=invoice.id,
                    tenant_id=tenant_id,
                    company_id=company_id,
                    manifest_number=manifest_number,
                    # Required fields defaults
                    is_pedimento_pending=False,
                    is_regime_change=False,
                    is_owner_of_goods=False,
                    generate_balances=False,
                    is_mixed=False,
                )
                db.add(new_compliance)

            db.commit()
            return True
        except Exception as e:
            db.rollback()
            logger.error(
                f"Error linking invoice {invoice_id} to manifest {manifest_number}: {str(e)}"
            )
            raise HTTPException(
                status_code=500, detail="Error linking invoice to manifest"
            )
