# services/suppliers/app/repositories/delivery_repository.py
"""
Delivery repository for database operations
"""

from typing import List, Optional, Dict, Any
from sqlalchemy.orm import Session, joinedload
from sqlalchemy import and_, or_, func, desc
from uuid import UUID
from datetime import datetime, timedelta

from app.models.suppliers import (
    Delivery, DeliveryItem, DeliveryStatus, 
    PurchaseOrder, Supplier
)
from app.repositories.base import BaseRepository


class DeliveryRepository(BaseRepository[Delivery]):
    """Repository for delivery tracking operations"""
    
    def __init__(self, db: Session):
        super().__init__(Delivery, db)
    
    def get_by_delivery_number(
        self, 
        tenant_id: UUID, 
        delivery_number: str
    ) -> Optional[Delivery]:
        """Get delivery by delivery number within tenant"""
        return (
            self.db.query(self.model)
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.delivery_number == delivery_number
                )
            )
            .first()
        )
    
    def get_with_items(self, delivery_id: UUID) -> Optional[Delivery]:
        """Get delivery with all items loaded"""
        return (
            self.db.query(self.model)
            .options(
                joinedload(self.model.items),
                joinedload(self.model.purchase_order),
                joinedload(self.model.supplier)
            )
            .filter(self.model.id == delivery_id)
            .first()
        )
    
    def get_by_purchase_order(self, po_id: UUID) -> List[Delivery]:
        """Get all deliveries for a purchase order"""
        return (
            self.db.query(self.model)
            .options(joinedload(self.model.items))
            .filter(self.model.purchase_order_id == po_id)
            .order_by(self.model.scheduled_date.desc())
            .all()
        )
    
    def search_deliveries(
        self,
        tenant_id: UUID,
        supplier_id: Optional[UUID] = None,
        status: Optional[DeliveryStatus] = None,
        date_from: Optional[datetime] = None,
        date_to: Optional[datetime] = None,
        search_term: Optional[str] = None,
        limit: int = 50,
        offset: int = 0
    ) -> List[Delivery]:
        """Search deliveries with comprehensive filters"""
        query = (
            self.db.query(self.model)
            .options(
                joinedload(self.model.supplier),
                joinedload(self.model.purchase_order)
            )
            .filter(self.model.tenant_id == tenant_id)
        )
        
        # Supplier filter
        if supplier_id:
            query = query.filter(self.model.supplier_id == supplier_id)
        
        # Status filter
        if status:
            query = query.filter(self.model.status == status)
        
        # Date range filter (scheduled date)
        if date_from:
            query = query.filter(self.model.scheduled_date >= date_from)
        if date_to:
            query = query.filter(self.model.scheduled_date <= date_to)
        
        # Search term filter
        if search_term:
            search_filter = or_(
                self.model.delivery_number.ilike(f"%{search_term}%"),
                self.model.supplier_delivery_note.ilike(f"%{search_term}%"),
                self.model.tracking_number.ilike(f"%{search_term}%"),
                self.model.purchase_order.has(PurchaseOrder.po_number.ilike(f"%{search_term}%"))
            )
            query = query.filter(search_filter)
        
        return (
            query.order_by(desc(self.model.scheduled_date))
            .limit(limit)
            .offset(offset)
            .all()
        )
    
    def get_scheduled_deliveries(
        self, 
        tenant_id: UUID,
        date_from: Optional[datetime] = None,
        date_to: Optional[datetime] = None
    ) -> List[Delivery]:
        """Get scheduled deliveries for a date range"""
        if not date_from:
            date_from = datetime.utcnow()
        if not date_to:
            date_to = date_from + timedelta(days=7)  # Next week
        
        return (
            self.db.query(self.model)
            .options(
                joinedload(self.model.supplier),
                joinedload(self.model.purchase_order)
            )
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.status.in_([
                        DeliveryStatus.SCHEDULED,
                        DeliveryStatus.IN_TRANSIT,
                        DeliveryStatus.OUT_FOR_DELIVERY
                    ]),
                    self.model.scheduled_date >= date_from,
                    self.model.scheduled_date <= date_to
                )
            )
            .order_by(self.model.scheduled_date)
            .all()
        )
    
    def get_todays_deliveries(self, tenant_id: UUID) -> List[Delivery]:
        """Get deliveries scheduled for today"""
        today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
        today_end = today_start + timedelta(days=1)
        
        return self.get_scheduled_deliveries(tenant_id, today_start, today_end)
    
    def get_overdue_deliveries(self, tenant_id: UUID) -> List[Delivery]:
        """Get deliveries that are overdue (scheduled in the past but not completed)"""
        now = datetime.utcnow()
        
        return (
            self.db.query(self.model)
            .options(
                joinedload(self.model.supplier),
                joinedload(self.model.purchase_order)
            )
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.status.in_([
                        DeliveryStatus.SCHEDULED,
                        DeliveryStatus.IN_TRANSIT,
                        DeliveryStatus.OUT_FOR_DELIVERY
                    ]),
                    self.model.scheduled_date < now
                )
            )
            .order_by(self.model.scheduled_date)
            .all()
        )
    
    def generate_delivery_number(self, tenant_id: UUID) -> str:
        """Generate next delivery number for tenant"""
        # Get current date
        today = datetime.utcnow()
        date_prefix = f"DEL{today.strftime('%Y%m%d')}"
        
        # Find highest delivery number for today
        latest_delivery = (
            self.db.query(self.model.delivery_number)
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.delivery_number.like(f"{date_prefix}%")
                )
            )
            .order_by(self.model.delivery_number.desc())
            .first()
        )
        
        if latest_delivery:
            try:
                last_number = int(latest_delivery.delivery_number.replace(date_prefix, ""))
                new_number = last_number + 1
            except ValueError:
                new_number = 1
        else:
            new_number = 1
        
        return f"{date_prefix}{new_number:03d}"
    
    def update_delivery_status(
        self,
        delivery_id: UUID,
        status: DeliveryStatus,
        updated_by: UUID,
        notes: Optional[str] = None,
        update_timestamps: bool = True
    ) -> Optional[Delivery]:
        """Update delivery status with appropriate timestamps"""
        delivery = self.get_by_id(delivery_id)
        if not delivery:
            return None
        
        old_status = delivery.status
        delivery.status = status
        delivery.created_by = updated_by  # Track who updated
        
        if update_timestamps:
            now = datetime.utcnow()
            
            if status == DeliveryStatus.IN_TRANSIT and not delivery.estimated_arrival:
                # Set estimated arrival if not already set
                delivery.estimated_arrival = now + timedelta(hours=4)  # Default 4 hours
            elif status == DeliveryStatus.DELIVERED:
                delivery.actual_arrival = now
                delivery.completed_at = now
            elif status == DeliveryStatus.PARTIALLY_DELIVERED:
                delivery.actual_arrival = now
            elif status == DeliveryStatus.FAILED_DELIVERY:
                delivery.actual_arrival = now
        
        # Add status change note
        if notes:
            existing_notes = delivery.notes or ""
            timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M")
            status_note = f"[{timestamp}] Status: {old_status.value} → {status.value}: {notes}"
            delivery.notes = f"{existing_notes}\n{status_note}".strip()
        
        delivery.updated_at = datetime.utcnow()
        
        self.db.commit()
        self.db.refresh(delivery)
        return delivery
    
    def mark_as_received(
        self,
        delivery_id: UUID,
        received_by: UUID,
        inspection_passed: bool = True,
        inspection_notes: Optional[str] = None,
        quality_issues: Optional[Dict[str, Any]] = None
    ) -> Optional[Delivery]:
        """Mark delivery as received with inspection details"""
        delivery = self.get_by_id(delivery_id)
        if not delivery:
            return None
        
        delivery.status = DeliveryStatus.DELIVERED
        delivery.received_by = received_by
        delivery.received_at = datetime.utcnow()
        delivery.completed_at = datetime.utcnow()
        delivery.inspection_passed = inspection_passed
        
        if inspection_notes:
            delivery.inspection_notes = inspection_notes
        
        if quality_issues:
            delivery.quality_issues = quality_issues
        
        if not delivery.actual_arrival:
            delivery.actual_arrival = datetime.utcnow()
        
        delivery.updated_at = datetime.utcnow()
        
        self.db.commit()
        self.db.refresh(delivery)
        return delivery
    
    def get_delivery_performance_stats(
        self,
        tenant_id: UUID,
        days_back: int = 30,
        supplier_id: Optional[UUID] = None
    ) -> Dict[str, Any]:
        """Get delivery performance statistics"""
        cutoff_date = datetime.utcnow() - timedelta(days=days_back)
        
        query = (
            self.db.query(self.model)
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.created_at >= cutoff_date
                )
            )
        )
        
        if supplier_id:
            query = query.filter(self.model.supplier_id == supplier_id)
        
        deliveries = query.all()
        
        if not deliveries:
            return {
                "total_deliveries": 0,
                "on_time_deliveries": 0,
                "late_deliveries": 0,
                "failed_deliveries": 0,
                "on_time_percentage": 0.0,
                "avg_delay_hours": 0.0,
                "quality_pass_rate": 0.0
            }
        
        total_count = len(deliveries)
        on_time_count = 0
        late_count = 0
        failed_count = 0
        total_delay_hours = 0
        quality_pass_count = 0
        quality_total = 0
        
        for delivery in deliveries:
            if delivery.status == DeliveryStatus.FAILED_DELIVERY:
                failed_count += 1
                continue
            
            # Check if delivery was on time
            if delivery.scheduled_date and delivery.actual_arrival:
                if delivery.actual_arrival <= delivery.scheduled_date:
                    on_time_count += 1
                else:
                    late_count += 1
                    delay = delivery.actual_arrival - delivery.scheduled_date
                    total_delay_hours += delay.total_seconds() / 3600
            
            # Check quality inspection
            if delivery.inspection_passed is not None:
                quality_total += 1
                if delivery.inspection_passed:
                    quality_pass_count += 1
        
        on_time_percentage = (on_time_count / total_count * 100) if total_count > 0 else 0
        avg_delay_hours = total_delay_hours / late_count if late_count > 0 else 0
        quality_pass_rate = (quality_pass_count / quality_total * 100) if quality_total > 0 else 0
        
        return {
            "total_deliveries": total_count,
            "on_time_deliveries": on_time_count,
            "late_deliveries": late_count,
            "failed_deliveries": failed_count,
            "on_time_percentage": round(on_time_percentage, 1),
            "avg_delay_hours": round(avg_delay_hours, 1),
            "quality_pass_rate": round(quality_pass_rate, 1)
        }
    
    def get_upcoming_deliveries_summary(self, tenant_id: UUID) -> Dict[str, Any]:
        """Get summary of upcoming deliveries"""
        today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
        next_week = today + timedelta(days=7)
        
        # Today's deliveries
        todays_deliveries = len(self.get_todays_deliveries(tenant_id))
        
        # This week's deliveries
        this_week_deliveries = (
            self.db.query(self.model)
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.scheduled_date >= today,
                    self.model.scheduled_date <= next_week,
                    self.model.status.in_([
                        DeliveryStatus.SCHEDULED,
                        DeliveryStatus.IN_TRANSIT,
                        DeliveryStatus.OUT_FOR_DELIVERY
                    ])
                )
            )
            .count()
        )
        
        # Overdue deliveries
        overdue_count = len(self.get_overdue_deliveries(tenant_id))
        
        # In transit deliveries
        in_transit_count = (
            self.db.query(self.model)
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.status == DeliveryStatus.IN_TRANSIT
                )
            )
            .count()
        )
        
        return {
            "todays_deliveries": todays_deliveries,
            "this_week_deliveries": this_week_deliveries,
            "overdue_deliveries": overdue_count,
            "in_transit_deliveries": in_transit_count
        }