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

from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError

from .models import Location, FaLocationExt
from .dto import LocationCreate, LocationUpdate


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

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

        if filters:
            if filters.get("clave_localizacion"):
                query = query.filter(
                    Location.clave_localizacion.ilike(
                        f"%{filters['clave_localizacion']}%"
                    )
                )
            if filters.get("localizacion"):
                query = query.filter(
                    Location.localizacion.ilike(f"%{filters['localizacion']}%")
                )
            if filters.get("system"):
                query = query.filter(Location.system == filters["system"])

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

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

    @staticmethod
    def get_by_clave(
        db: Session,
        clave_localizacion: str,
        tenant_id: int,
        company_id: int,
        system: Optional[str] = None,
    ) -> Optional[Location]:
        query = db.query(Location).filter(
            Location.clave_localizacion == clave_localizacion,
            Location.tenant_id == tenant_id,
            Location.company_id == company_id,
        )
        if system is not None:
            query = query.filter(Location.system == system)
        return query.first()

    @staticmethod
    def create(
        db: Session,
        data: LocationCreate,
        tenant_id: int,
        company_id: int,
    ) -> Location:
        db_obj = Location(
            clave_localizacion=data.clave_localizacion,
            localizacion=data.localizacion,
            system=data.system.value,
            tenant_id=tenant_id,
            company_id=company_id,
        )
        db.add(db_obj)
        try:
            db.flush()  # get db_obj.id without committing
            if data.system.value == "fixed_asset" and (
                data.department is not None
                or data.responsible is not None
                or data.observations is not None
            ):
                ext = FaLocationExt(
                    location_id=db_obj.id,
                    department=data.department,
                    responsible=data.responsible,
                    observations=data.observations,
                    tenant_id=tenant_id,
                    company_id=company_id,
                )
                db.add(ext)
            db.commit()
            db.refresh(db_obj)
            return db_obj
        except IntegrityError:
            db.rollback()
            raise ValueError("Ya existe una localización con esa clave y system")

    @staticmethod
    def update(
        db: Session,
        id: int,
        tenant_id: int,
        data: LocationUpdate,
        company_id: int,
    ) -> Optional[Location]:
        db_obj = LocationService.get_by_id(db, id, tenant_id, company_id)
        if not db_obj:
            return None

        update_dict = data.model_dump(exclude_unset=True)
        for key, value in update_dict.items():
            if key == "system" and value is not None:
                setattr(db_obj, key, value.value)
            else:
                setattr(db_obj, key, value)

        try:
            db.commit()
            db.refresh(db_obj)
            return db_obj
        except IntegrityError:
            db.rollback()
            raise ValueError("Ya existe una localización con esa clave y system")

    @staticmethod
    def delete(
        db: Session,
        id: int,
        tenant_id: int,
        company_id: int,
    ) -> bool:
        db_obj = LocationService.get_by_id(db, id, tenant_id, company_id)
        if not db_obj:
            return False
        db.delete(db_obj)
        db.commit()
        return True
