"""
Capa de servicio para lógica de negocio de clientes y proveedores
"""

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

from fastapi import HTTPException
from sqlalchemy import or_
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, joinedload
from .models import ClientOrProviderEnum

from .dto import (
    ClientProviderBasicDTO,
    ClientProviderCreateDTO,
    ClientProviderListDTO,
    ClientProviderResponseDTO,
    ClientProviderUpdateDTO,
)
from .models import ClientProvider, ClientProviderAddress, ClientProviderPrograms

logger = logging.getLogger(__name__)


class ClientProviderService:
    """Servicio para gestión de clientes y proveedores"""

    def __init__(self, db: Session):
        self.db = db

    # Métodos para TenantCRUDRoutes
    @staticmethod
    def get_all(
        db: Session,
        tenant_id: int,
        company_id: Optional[int],
        skip: int = 0,
        limit: int = 50,
        filters: Optional[Dict[str, Any]] = None,
        sort_by: Optional[str] = None,
        sort_order: Optional[str] = "asc",
    ) -> Tuple[List[ClientProvider], int]:
        """Get all clients/providers for a tenant with pagination"""
        query = db.query(ClientProvider).filter(ClientProvider.tenant_id == tenant_id)

        if company_id is not None:
            query = query.filter(ClientProvider.company_id == company_id)

        # Apply filters if provided
        if filters:
            if filters.get("search"):
                search_pattern = f"%{filters['search']}%"
                query = query.filter(
                    or_(
                        ClientProvider.name.ilike(search_pattern),
                        ClientProvider.short_name.ilike(search_pattern),
                        ClientProvider.rfc.ilike(search_pattern),
                    )
                )
            if filters.get("client_or_provider"):

                query = query.filter(
                    or_(
                        ClientProvider.client_or_provider == filters["client_or_provider"],
                        ClientProvider.client_or_provider == ClientOrProviderEnum.BOTH
                    )
                )
            if filters.get("status"):
                enabled = 1 if filters["status"] == "enabled" else 0
                query = query.filter(ClientProvider.is_active == enabled)

        # Apply sorting
        if sort_by:
            column = getattr(ClientProvider, sort_by, None)
            if column:
                if sort_order == "desc":
                    query = query.order_by(column.desc())
                else:
                    query = query.order_by(column.asc())
        else:
            query = query.order_by(ClientProvider.id.desc())

        total = query.count()
        clients = (
            query.options(
                joinedload(ClientProvider.address), joinedload(ClientProvider.programs)
            )
            .offset(skip)
            .limit(limit)
            .all()
        )

        return clients, total

    @staticmethod
    def get_by_id(
        db: Session, client_id: int, tenant_id: int, company_id: int
    ) -> Optional[ClientProvider]:
        """Get client/provider by ID"""
        return (
            db.query(ClientProvider)
            .options(
                joinedload(ClientProvider.address), joinedload(ClientProvider.programs)
            )
            .filter(
                ClientProvider.id == client_id,
                ClientProvider.tenant_id == tenant_id,
                ClientProvider.company_id == company_id,
            )
            .first()
        )

    @staticmethod
    def create(
        db: Session,
        client_data: ClientProviderCreateDTO,
        tenant_id: int,
        company_id: int,
    ) -> ClientProvider:
        """Create a new client/provider"""
        try:
            # Create main client/provider
            data_dict = client_data.model_dump(exclude={"address", "programs"})
            db_client = ClientProvider(
                **data_dict, tenant_id=tenant_id, company_id=company_id
            )

            db.add(db_client)
            db.flush()

            # Create address if provided
            if client_data.address:
                db_address = ClientProviderAddress(
                    tenant_id=tenant_id,
                    company_id=company_id,
                    client_id=db_client.id,
                    **client_data.address.model_dump(exclude_unset=True),
                )
                db.add(db_address)

            # Create programs if provided
            if client_data.programs:
                db_programs = ClientProviderPrograms(
                    tenant_id=tenant_id,
                    company_id=company_id,
                    client_id=db_client.id,
                    **client_data.programs.model_dump(exclude_unset=True),
                )
                db.add(db_programs)

            db.commit()
            db.refresh(db_client)

            return db_client

        except IntegrityError as e:
            db.rollback()
            logger.error(f"IntegrityError creating client/provider: {str(e)}")
            raise HTTPException(
                status_code=400, detail="Client/Provider already exists"
            )
        except Exception as e:
            db.rollback()
            logger.error(f"Error creating client/provider: {str(e)}")
            raise HTTPException(
                status_code=500, detail="Error creating client/provider"
            )

    @staticmethod
    def update(
        db: Session,
        client_id: int,
        tenant_id: int,
        company_id: int,
        client_data: ClientProviderUpdateDTO,
    ) -> Optional[ClientProvider]:
        """Update a client/provider"""
        client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id)
        if not client:
            return None

        try:
            # Update main fields
            update_data = client_data.model_dump(
                exclude_unset=True, exclude={"address", "programs"}
            )
            for field, value in update_data.items():
                setattr(client, field, value)

            # Update address
            if client_data.address:
                if client.address:
                    address_data = client_data.address.model_dump(exclude_unset=True)
                    for field, value in address_data.items():
                        setattr(client.address, field, value)
                else:
                    db_address = ClientProviderAddress(
                        client_id=client.id,
                        tenant_id=tenant_id,
                        company_id=company_id,
                        **client_data.address.model_dump(exclude_unset=True),
                    )
                    db.add(db_address)

            # Update programs
            if client_data.programs:
                if client.programs:
                    programs_data = client_data.programs.model_dump(exclude_unset=True)
                    for field, value in programs_data.items():
                        setattr(client.programs, field, value)
                else:
                    db_programs = ClientProviderPrograms(
                        client_id=client.id,
                        tenant_id=tenant_id,
                        company_id=company_id,
                        **client_data.programs.model_dump(exclude_unset=True),
                    )
                    db.add(db_programs)

            db.commit()
            db.refresh(client)

            return client

        except Exception as e:
            db.rollback()
            logger.error(f"Error updating client/provider {client_id}: {str(e)}")
            raise HTTPException(
                status_code=500, detail="Error updating client/provider"
            )

    @staticmethod
    def delete(db: Session, client_id: int, tenant_id: int, company_id: int) -> bool:
        """Delete a client/provider"""
        client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id)
        if not client:
            return False

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

    # Legacy methods for custom endpoints

    def create_clients_and_providers_legacy(
        self, company_id: int, client_data: ClientProviderCreateDTO
    ) -> ClientProviderResponseDTO:
        """Legacy method for creating client/provider"""
        try:
            # Create main client/provider
            data_dict = client_data.model_dump(exclude={"address", "programs"})
            db_client = ClientProvider(**data_dict)
            self.db.add(db_client)
            self.db.flush()

            # Create address if provided
            if client_data.address:
                db_address = ClientProviderAddress(
                    company_id=company_id,
                    client_id=db_client.id,
                    **client_data.address.model_dump(exclude_unset=True),
                )
                self.db.add(db_address)

            # Create programs if provided
            if client_data.programs:
                db_programs = ClientProviderPrograms(
                    company_id=company_id,
                    client_id=db_client.id,
                    **client_data.programs.model_dump(exclude_unset=True),
                )
                self.db.add(db_programs)

            self.db.commit()

            return self._get_client_with_relations(db_client.id)

        except IntegrityError as e:
            self.db.rollback()
            logger.error(f"IntegrityError creating client/provider: {str(e)}")
            raise HTTPException(
                status_code=400, detail="Client/Provider with this ID already exists"
            )
        except HTTPException:
            raise
        except Exception as e:
            self.db.rollback()
            logger.error(f"Error creating client/provider: {str(e)}")
            raise HTTPException(
                status_code=500, detail="Error creating client/provider"
            )

    def get_clients_and_providers(
        self, client_id: str
    ) -> Optional[ClientProviderResponseDTO]:
        """
        Obtiene un cliente/proveedor por ID

        Args:
            client_id: ID del cliente/proveedor

        Returns:
            ClientProviderResponseDTO o None si no existe
        """
        return self._get_client_with_relations(client_id)

    def _get_client_with_relations(
        self, client_id: str
    ) -> Optional[ClientProviderResponseDTO]:
        """Método privado para obtener cliente con relaciones"""
        client = (
            self.db.query(ClientProvider)
            .options(
                joinedload(ClientProvider.address), joinedload(ClientProvider.programs)
            )
            .filter(ClientProvider.client_id == client_id)
            .first()
        )

        if not client:
            return None
        return ClientProviderResponseDTO.model_validate(client)

    def list_clients_providers(
        self,
        skip: int = 0,
        limit: int = 100,
        search: Optional[str] = None,
        client_or_provider: Optional[str] = None,
        enabled_only: bool = False,
    ) -> ClientProviderListDTO:
        """
        Lista clientes/proveedores con filtros

        Args:
            skip: Número de registros a omitir
            limit: Número máximo de registros a retornar
            search: Texto de búsqueda (nombre, RFC, ID)
            client_or_provider: Filtrar por tipo (client=Cliente, provider=Proveedor)
            enabled_only: Si True, solo retorna activos

        Returns:
            ClientProviderListDTO con la lista paginada
        """
        query = self.db.query(ClientProvider)

        # Aplicar filtros
        if search:
            search_pattern = f"%{search}%"
            query = query.filter(
                or_(
                    ClientProvider.name.ilike(search_pattern),
                    ClientProvider.short_name.ilike(search_pattern),
                    ClientProvider.rfc.ilike(search_pattern),
                    ClientProvider.client_id.ilike(search_pattern),
                )
            )

        if client_or_provider:
            query = query.filter(
                or_(
                    ClientProvider.client_or_provider == client_or_provider,
                    ClientProvider.client_or_provider == ClientOrProviderEnum.BOTH
                )
            )

        if enabled_only:
            query = query.filter(ClientProvider.is_active == 1)

        # Contar total
        total = query.count()

        # Aplicar paginación
        clients = query.offset(skip).limit(limit).all()

        # Convertir a DTOs básicos
        client_dtos = [
            ClientProviderBasicDTO.model_validate(client) for client in clients
        ]

        return ClientProviderListDTO(
            clients=client_dtos,
            total=total,
            page=(skip // limit) + 1 if limit > 0 else 1,
            size=len(client_dtos),
        )

    def update_clients_and_providers(
        self, client_id: str, client_data: ClientProviderUpdateDTO
    ) -> Optional[ClientProviderResponseDTO]:
        """
        Actualiza un cliente/proveedor

        Args:
            client_id: ID del cliente/proveedor a actualizar
            client_data: Datos a actualizar

        Returns:
            ClientProviderResponseDTO actualizado o None si no existe
        """
        client = (
            self.db.query(ClientProvider)
            .filter(ClientProvider.client_id == client_id)
            .first()
        )
        if not client:
            return None

        try:
            # Actualizar campos del cliente principal
            update_data = client_data.model_dump(
                exclude_unset=True, exclude={"address", "programs"}
            )
            for field, value in update_data.items():
                setattr(client, field, value)

            # Actualizar dirección
            if client_data.address:
                address = (
                    self.db.query(ClientProviderAddress)
                    .filter(ClientProviderAddress.client_id == client_id)
                    .first()
                )
                if address:
                    # Actualizar dirección existente
                    address_data = client_data.address.model_dump(exclude_unset=True)
                    for field, value in address_data.items():
                        setattr(address, field, value)
                else:
                    # Crear nueva dirección
                    address = ClientProviderAddress(
                        client_id=client_id,
                        **client_data.address.model_dump(exclude_unset=True),
                    )
                    self.db.add(address)

            # Actualizar programas
            if client_data.programs:
                programs = (
                    self.db.query(ClientProviderPrograms)
                    .filter(ClientProviderPrograms.client_id == client_id)
                    .first()
                )
                if programs:
                    # Actualizar programas existentes
                    programs_data = client_data.programs.model_dump(exclude_unset=True)
                    for field, value in programs_data.items():
                        setattr(programs, field, value)
                else:
                    # Crear nuevos programas
                    programs = ClientProviderPrograms(
                        client_id=client_id,
                        **client_data.programs.model_dump(exclude_unset=True),
                    )
                    self.db.add(programs)

            self.db.commit()

            return self._get_client_with_relations(client_id)

        except Exception as e:
            self.db.rollback()
            logger.error(f"Error updating client/provider {client_id}: {str(e)}")
            raise HTTPException(
                status_code=500, detail="Error updating client/provider"
            )

    def delete_clients_and_providers(self, client_id: str) -> bool:
        """
        Elimina un cliente/proveedor

        Args:
            client_id: ID del cliente/proveedor a eliminar

        Returns:
            True si se eliminó, False si no existe
        """
        client = (
            self.db.query(ClientProvider)
            .filter(ClientProvider.client_id == client_id)
            .first()
        )
        if not client:
            return False

        try:
            self.db.delete(client)  # Las relaciones se eliminan en cascada
            self.db.commit()
            return True
        except Exception as e:
            self.db.rollback()
            logger.error(f"Error deleting client/provider {client_id}: {str(e)}")
            raise HTTPException(
                status_code=500, detail="Error deleting client/provider"
            )