from typing import List, Optional
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, func, desc
from datetime import date, datetime
import secrets
from app.models.ppr import PPRRecord, PPRStatus
from app.models.journal import EntityType
from app.schemas.ppr import PPRCreate, PPRUpdate
from app.crud.crud_journal import journal as crud_journal


class CRUDPPR:
    def get(self, db: Session, ppr_id: int) -> Optional[PPRRecord]:
        return db.query(PPRRecord).filter(PPRRecord.id == ppr_id).first()

    def get_by_public_token(self, db: Session, token: str) -> Optional[PPRRecord]:
        return db.query(PPRRecord).filter(PPRRecord.public_token == token).first()

    def get_multi(
        self, 
        db: Session, 
        skip: int = 0, 
        limit: int = 100,
        status: Optional[PPRStatus] = None,
        date_from: Optional[date] = None,
        date_to: Optional[date] = None
    ) -> List[PPRRecord]:
        query = db.query(PPRRecord)
        
        if status:
            query = query.filter(PPRRecord.status == status)
        
        if date_from:
            query = query.filter(
                or_(
                    func.date(PPRRecord.eta) >= date_from,
                    func.date(PPRRecord.etd) >= date_from
                )
            )
            
        if date_to:
            query = query.filter(
                or_(
                    func.date(PPRRecord.eta) <= date_to,
                    func.date(PPRRecord.etd) <= date_to
                )
            )
        
        return query.order_by(desc(PPRRecord.submitted_dt)).offset(skip).limit(limit).all()

    def get_arrivals_today(self, db: Session) -> List[PPRRecord]:
        """Get today's arrivals - includes aircraft that have arrived and may have departed"""
        today = date.today()
        return db.query(PPRRecord).filter(
            and_(
                func.date(PPRRecord.eta) == today,
                or_(
                    PPRRecord.status == PPRStatus.NEW,
                    PPRRecord.status == PPRStatus.CONFIRMED,
                    PPRRecord.status == PPRStatus.LANDED,
                    PPRRecord.status == PPRStatus.DEPARTED
                )
            )
        ).order_by(PPRRecord.eta).all()

    def get_departures_today(self, db: Session) -> List[PPRRecord]:
        """Get today's departures"""
        today = date.today()
        return db.query(PPRRecord).filter(
            and_(
                func.date(PPRRecord.etd) == today,
                or_(
                    PPRRecord.status == PPRStatus.LANDED,
                    PPRRecord.status == PPRStatus.DEPARTED
                )
            )
        ).order_by(PPRRecord.etd).all()

    def create(self, db: Session, obj_in: PPRCreate, created_by: str, user_ip: str = "127.0.0.1") -> PPRRecord:
        db_obj = PPRRecord(
            **obj_in.dict(),
            created_by=created_by,
            status=PPRStatus.NEW,
            public_token=secrets.token_urlsafe(64)
        )
        db.add(db_obj)
        db.commit()
        db.refresh(db_obj)
        
        # Log creation in journal
        crud_journal.log_change(
            db, 
            EntityType.PPR,
            db_obj.id, 
            f"PPR created for {db_obj.ac_reg}", 
            created_by, 
            user_ip
        )
        
        return db_obj

    def update(self, db: Session, db_obj: PPRRecord, obj_in: PPRUpdate, user: str = "system", user_ip: str = "127.0.0.1") -> PPRRecord:
        from datetime import datetime as dt
        
        update_data = obj_in.dict(exclude_unset=True)
        changes = []
        
        for field, value in update_data.items():
            old_value = getattr(db_obj, field)
            
            # Normalize datetime values for comparison (ignore timezone differences)
            if isinstance(old_value, dt) and isinstance(value, dt):
                # Compare only the date and time, ignoring timezone
                old_normalized = old_value.replace(tzinfo=None) if old_value.tzinfo else old_value
                new_normalized = value.replace(tzinfo=None) if value.tzinfo else value
                if old_normalized == new_normalized:
                    continue  # Skip if datetimes are the same
            
            if old_value != value:
                changes.append(f"{field} changed from '{old_value}' to '{value}'")
                setattr(db_obj, field, value)
        
        if changes:
            db.add(db_obj)
            db.commit()
            db.refresh(db_obj)
            
            # Log changes in journal
            for change in changes:
                crud_journal.log_ppr_change(db, db_obj.id, change, user, user_ip)
        
        return db_obj

    def update_status(
        self, 
        db: Session, 
        ppr_id: int, 
        status: PPRStatus,
        timestamp: Optional[datetime] = None,
        user: str = "system",
        user_ip: str = "127.0.0.1"
    ) -> Optional[PPRRecord]:
        db_obj = self.get(db, ppr_id)
        if not db_obj:
            return None
        
        old_status = db_obj.status
        db_obj.status = status
        
        # Set timestamps based on status - use provided timestamp or current time
        current_time = timestamp if timestamp is not None else datetime.utcnow()
        if status == PPRStatus.LANDED:
            db_obj.landed_dt = current_time
        elif status == PPRStatus.DEPARTED:
            db_obj.departed_dt = current_time
        
        db.add(db_obj)
        db.commit()
        db.refresh(db_obj)
        
        # Log status change in journal
        crud_journal.log_ppr_change(
            db, 
            db_obj.id, 
            f"Status changed from {old_status.value} to {status.value}", 
            user, 
            user_ip
        )
        
        return db_obj

    def delete(self, db: Session, ppr_id: int, user: str = "system", user_ip: str = "127.0.0.1") -> Optional[PPRRecord]:
        db_obj = self.get(db, ppr_id)
        if db_obj:
            old_status = db_obj.status
            # Soft delete by setting status
            db_obj.status = PPRStatus.DELETED
            db.add(db_obj)
            db.commit()
            db.refresh(db_obj)
            
            # Log the deletion in journal
            crud_journal.log_change(
                db, 
                db_obj.id, 
                f"PPR marked as DELETED (was {old_status.value})", 
                user, 
                user_ip
            )
        return db_obj


ppr = CRUDPPR()