"""
Linux BenchTools - Peripheral Service
Handles business logic and cross-database operations
"""

from typing import Optional, List, Dict, Any, Tuple
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, func, desc
from datetime import date, datetime, timedelta

from app.models.peripheral import (
    Peripheral, PeripheralPhoto, PeripheralDocument,
    PeripheralLink, PeripheralLoan
)
from app.models.location import Location
from app.models.peripheral_history import PeripheralLocationHistory
from app.schemas.peripheral import (
    PeripheralCreate, PeripheralUpdate, PeripheralSummary,
    PeripheralDetail, PeripheralListResponse,
    LoanCreate, LoanReturn
)


class PeripheralService:
    """Service for peripheral operations"""

    @staticmethod
    def create_peripheral(
        db: Session,
        peripheral_data: PeripheralCreate,
        user: Optional[str] = None
    ) -> Peripheral:
        """Create a new peripheral"""
        peripheral = Peripheral(**peripheral_data.model_dump())
        db.add(peripheral)
        db.commit()
        db.refresh(peripheral)

        # Create history entry
        if peripheral.location_id or peripheral.device_id:
            PeripheralService._create_history(
                db=db,
                peripheral_id=peripheral.id,
                action="created",
                to_location_id=peripheral.location_id,
                to_device_id=peripheral.device_id,
                user=user
            )

        return peripheral

    @staticmethod
    def get_peripheral(db: Session, peripheral_id: int) -> Optional[Peripheral]:
        """Get a peripheral by ID"""
        return db.query(Peripheral).filter(Peripheral.id == peripheral_id).first()

    @staticmethod
    def update_peripheral(
        db: Session,
        peripheral_id: int,
        peripheral_data: PeripheralUpdate,
        user: Optional[str] = None
    ) -> Optional[Peripheral]:
        """Update a peripheral"""
        peripheral = PeripheralService.get_peripheral(db, peripheral_id)
        if not peripheral:
            return None

        # Track location/device changes for history
        old_location_id = peripheral.location_id
        old_device_id = peripheral.device_id

        # Update fields
        update_data = peripheral_data.model_dump(exclude_unset=True)
        for key, value in update_data.items():
            setattr(peripheral, key, value)

        db.commit()
        db.refresh(peripheral)

        # Create history if location or device changed
        new_location_id = peripheral.location_id
        new_device_id = peripheral.device_id

        if old_location_id != new_location_id or old_device_id != new_device_id:
            action = "moved" if old_location_id != new_location_id else "assigned"
            PeripheralService._create_history(
                db=db,
                peripheral_id=peripheral.id,
                action=action,
                from_location_id=old_location_id,
                to_location_id=new_location_id,
                from_device_id=old_device_id,
                to_device_id=new_device_id,
                user=user
            )

        return peripheral

    @staticmethod
    def delete_peripheral(db: Session, peripheral_id: int) -> bool:
        """Delete a peripheral and all related data"""
        peripheral = PeripheralService.get_peripheral(db, peripheral_id)
        if not peripheral:
            return False

        # Delete related records
        db.query(PeripheralPhoto).filter(PeripheralPhoto.peripheral_id == peripheral_id).delete()
        db.query(PeripheralDocument).filter(PeripheralDocument.peripheral_id == peripheral_id).delete()
        db.query(PeripheralLink).filter(PeripheralLink.peripheral_id == peripheral_id).delete()
        db.query(PeripheralLoan).filter(PeripheralLoan.peripheral_id == peripheral_id).delete()
        db.query(PeripheralLocationHistory).filter(PeripheralLocationHistory.peripheral_id == peripheral_id).delete()

        # Delete peripheral
        db.delete(peripheral)
        db.commit()
        return True

    @staticmethod
    def list_peripherals(
        db: Session,
        page: int = 1,
        page_size: int = 50,
        type_filter: Optional[str] = None,
        search: Optional[str] = None,
        location_id: Optional[int] = None,
        device_id: Optional[int] = None,
        en_pret: Optional[bool] = None,
        is_complete_device: Optional[bool] = None,
        sort_by: str = "date_creation",
        sort_order: str = "desc"
    ) -> PeripheralListResponse:
        """List peripherals with pagination and filters"""

        # Base query
        query = db.query(Peripheral)

        # Apply filters
        if type_filter:
            query = query.filter(Peripheral.type_principal == type_filter)

        if search:
            search_pattern = f"%{search}%"
            query = query.filter(
                or_(
                    Peripheral.nom.ilike(search_pattern),
                    Peripheral.marque.ilike(search_pattern),
                    Peripheral.modele.ilike(search_pattern),
                    Peripheral.numero_serie.ilike(search_pattern)
                )
            )

        if location_id is not None:
            query = query.filter(Peripheral.location_id == location_id)

        if device_id is not None:
            query = query.filter(Peripheral.device_id == device_id)

        if en_pret is not None:
            query = query.filter(Peripheral.en_pret == en_pret)

        if is_complete_device is not None:
            query = query.filter(Peripheral.is_complete_device == is_complete_device)

        # Count total
        total = query.count()

        # Apply sorting
        sort_column = getattr(Peripheral, sort_by, Peripheral.date_creation)
        if sort_order == "desc":
            query = query.order_by(desc(sort_column))
        else:
            query = query.order_by(sort_column)

        # Apply pagination
        offset = (page - 1) * page_size
        peripherals = query.offset(offset).limit(page_size).all()

        # Import PeripheralPhoto here to avoid circular import
        from app.models.peripheral import PeripheralPhoto

        # Convert to summary
        items = []
        for p in peripherals:
            # Get primary photo thumbnail
            thumbnail_url = None
            primary_photo = db.query(PeripheralPhoto).filter(
                PeripheralPhoto.peripheral_id == p.id,
                PeripheralPhoto.is_primary == True
            ).first()

            if primary_photo and primary_photo.thumbnail_path:
                # Convert file path to URL
                thumbnail_url = primary_photo.thumbnail_path.replace('/app/uploads/', '/uploads/')

            items.append(PeripheralSummary(
                id=p.id,
                nom=p.nom,
                type_principal=p.type_principal,
                sous_type=p.sous_type,
                marque=p.marque,
                modele=p.modele,
                etat=p.etat or "Inconnu",
                rating=p.rating or 0.0,
                prix=p.prix,
                en_pret=p.en_pret or False,
                is_complete_device=p.is_complete_device or False,
                quantite_disponible=p.quantite_disponible or 0,
                thumbnail_url=thumbnail_url
            ))


        total_pages = (total + page_size - 1) // page_size

        return PeripheralListResponse(
            items=items,
            total=total,
            page=page,
            page_size=page_size,
            total_pages=total_pages
        )

    @staticmethod
    def get_peripherals_by_device(
        db: Session,
        device_id: int
    ) -> List[Peripheral]:
        """Get all peripherals assigned to a device (cross-database logical FK)"""
        return db.query(Peripheral).filter(Peripheral.device_id == device_id).all()

    @staticmethod
    def get_peripherals_by_linked_device(
        db: Session,
        linked_device_id: int
    ) -> List[Peripheral]:
        """Get all peripherals that are part of a complete device"""
        return db.query(Peripheral).filter(Peripheral.linked_device_id == linked_device_id).all()

    @staticmethod
    def assign_to_device(
        db: Session,
        peripheral_id: int,
        device_id: int,
        user: Optional[str] = None
    ) -> Optional[Peripheral]:
        """Assign a peripheral to a device"""
        peripheral = PeripheralService.get_peripheral(db, peripheral_id)
        if not peripheral:
            return None

        old_device_id = peripheral.device_id
        peripheral.device_id = device_id

        db.commit()
        db.refresh(peripheral)

        # Create history
        PeripheralService._create_history(
            db=db,
            peripheral_id=peripheral.id,
            action="assigned",
            from_device_id=old_device_id,
            to_device_id=device_id,
            user=user
        )

        return peripheral

    @staticmethod
    def unassign_from_device(
        db: Session,
        peripheral_id: int,
        user: Optional[str] = None
    ) -> Optional[Peripheral]:
        """Unassign a peripheral from a device"""
        peripheral = PeripheralService.get_peripheral(db, peripheral_id)
        if not peripheral:
            return None

        old_device_id = peripheral.device_id
        peripheral.device_id = None

        db.commit()
        db.refresh(peripheral)

        # Create history
        PeripheralService._create_history(
            db=db,
            peripheral_id=peripheral.id,
            action="unassigned",
            from_device_id=old_device_id,
            to_device_id=None,
            user=user
        )

        return peripheral

    @staticmethod
    def create_loan(
        db: Session,
        loan_data: LoanCreate,
        user: Optional[str] = None
    ) -> Optional[PeripheralLoan]:
        """Create a loan for a peripheral"""
        peripheral = PeripheralService.get_peripheral(db, loan_data.peripheral_id)
        if not peripheral or peripheral.en_pret:
            return None

        # Create loan
        loan = PeripheralLoan(
            **loan_data.model_dump(),
            statut="en_cours",
            created_by=user
        )
        db.add(loan)

        # Update peripheral
        peripheral.en_pret = True
        peripheral.pret_actuel_id = None  # Will be set after commit
        peripheral.prete_a = loan_data.emprunte_par

        db.commit()
        db.refresh(loan)

        # Update peripheral with loan ID
        peripheral.pret_actuel_id = loan.id
        db.commit()
        db.refresh(peripheral)

        return loan

    @staticmethod
    def return_loan(
        db: Session,
        loan_id: int,
        return_data: LoanReturn
    ) -> Optional[PeripheralLoan]:
        """Return a loan"""
        loan = db.query(PeripheralLoan).filter(PeripheralLoan.id == loan_id).first()
        if not loan or loan.statut != "en_cours":
            return None

        # Update loan
        loan.date_retour_effectif = return_data.date_retour_effectif
        loan.etat_retour = return_data.etat_retour
        loan.problemes_retour = return_data.problemes_retour
        loan.caution_rendue = return_data.caution_rendue
        loan.statut = "retourne"

        if return_data.notes:
            loan.notes = (loan.notes or "") + "\n" + return_data.notes

        # Update peripheral
        peripheral = PeripheralService.get_peripheral(db, loan.peripheral_id)
        if peripheral:
            peripheral.en_pret = False
            peripheral.pret_actuel_id = None
            peripheral.prete_a = None

        db.commit()
        db.refresh(loan)

        return loan

    @staticmethod
    def get_overdue_loans(db: Session) -> List[PeripheralLoan]:
        """Get all overdue loans"""
        today = date.today()
        return db.query(PeripheralLoan).filter(
            and_(
                PeripheralLoan.statut == "en_cours",
                PeripheralLoan.date_retour_prevue < today
            )
        ).all()

    @staticmethod
    def get_upcoming_returns(db: Session, days: int = 7) -> List[PeripheralLoan]:
        """Get loans due within specified days"""
        today = date.today()
        future = today + timedelta(days=days)
        return db.query(PeripheralLoan).filter(
            and_(
                PeripheralLoan.statut == "en_cours",
                PeripheralLoan.date_retour_prevue.between(today, future)
            )
        ).all()

    @staticmethod
    def get_statistics(db: Session) -> Dict[str, Any]:
        """Get peripheral statistics"""
        total = db.query(Peripheral).count()
        en_pret = db.query(Peripheral).filter(Peripheral.en_pret == True).count()
        complete_devices = db.query(Peripheral).filter(Peripheral.is_complete_device == True).count()

        # By type
        by_type = db.query(
            Peripheral.type_principal,
            func.count(Peripheral.id).label('count')
        ).group_by(Peripheral.type_principal).all()

        # By state
        by_etat = db.query(
            Peripheral.etat,
            func.count(Peripheral.id).label('count')
        ).group_by(Peripheral.etat).all()

        # Low stock
        low_stock = db.query(Peripheral).filter(
            Peripheral.quantite_disponible <= Peripheral.seuil_alerte
        ).count()

        return {
            "total_peripherals": total,
            "en_pret": en_pret,
            "disponible": total - en_pret,
            "complete_devices": complete_devices,
            "low_stock_count": low_stock,
            "by_type": [{"type": t, "count": c} for t, c in by_type],
            "by_etat": [{"etat": e or "Inconnu", "count": c} for e, c in by_etat]
        }

    @staticmethod
    def _create_history(
        db: Session,
        peripheral_id: int,
        action: str,
        from_location_id: Optional[int] = None,
        to_location_id: Optional[int] = None,
        from_device_id: Optional[int] = None,
        to_device_id: Optional[int] = None,
        user: Optional[str] = None,
        notes: Optional[str] = None
    ) -> PeripheralLocationHistory:
        """Create a history entry"""
        history = PeripheralLocationHistory(
            peripheral_id=peripheral_id,
            action=action,
            from_location_id=from_location_id,
            to_location_id=to_location_id,
            from_device_id=from_device_id,
            to_device_id=to_device_id,
            user=user,
            notes=notes
        )
        db.add(history)
        db.commit()
        return history


class LocationService:
    """Service for location operations"""

    @staticmethod
    def get_location_tree(db: Session) -> List[Dict[str, Any]]:
        """Get hierarchical location tree"""
        def build_tree(parent_id: Optional[int] = None) -> List[Dict[str, Any]]:
            locations = db.query(Location).filter(
                Location.parent_id == parent_id
            ).order_by(Location.ordre_affichage, Location.nom).all()

            return [
                {
                    "id": loc.id,
                    "nom": loc.nom,
                    "type": loc.type,
                    "description": loc.description,
                    "image_path": loc.image_path,
                    "qr_code_path": loc.qr_code_path,
                    "children": build_tree(loc.id)
                }
                for loc in locations
            ]

        return build_tree(None)

    @staticmethod
    def get_location_path(db: Session, location_id: int) -> List[Location]:
        """Get full path from root to location"""
        path = []
        current_id = location_id

        while current_id:
            location = db.query(Location).filter(Location.id == current_id).first()
            if not location:
                break
            path.insert(0, location)
            current_id = location.parent_id

        return path

    @staticmethod
    def count_peripherals_in_location(
        db: Session,
        location_id: int,
        recursive: bool = False
    ) -> int:
        """Count peripherals in a location (optionally recursive)"""
        if not recursive:
            return db.query(Peripheral).filter(Peripheral.location_id == location_id).count()

        # Get all child locations
        def get_children(parent_id: int) -> List[int]:
            children = db.query(Location.id).filter(Location.parent_id == parent_id).all()
            child_ids = [c[0] for c in children]
            for child_id in child_ids[:]:
                child_ids.extend(get_children(child_id))
            return child_ids

        location_ids = [location_id] + get_children(location_id)
        return db.query(Peripheral).filter(Peripheral.location_id.in_(location_ids)).count()
