# services/suppliers/app/services/delivery_service.py
"""
Delivery service for business logic operations
"""

import structlog
from typing import List, Optional, Dict, Any
from uuid import UUID
from datetime import datetime
from sqlalchemy.orm import Session

from app.repositories.delivery_repository import DeliveryRepository
from app.repositories.purchase_order_repository import PurchaseOrderRepository
from app.repositories.supplier_repository import SupplierRepository
from app.models.suppliers import (
    Delivery, DeliveryItem, DeliveryStatus, 
    PurchaseOrder, PurchaseOrderStatus, SupplierStatus
)
from app.schemas.suppliers import (
    DeliveryCreate, DeliveryUpdate, DeliverySearchParams
)
from app.core.config import settings

logger = structlog.get_logger()


class DeliveryService:
    """Service for delivery management operations"""
    
    def __init__(self, db: Session):
        self.db = db
        self.repository = DeliveryRepository(db)
        self.po_repository = PurchaseOrderRepository(db)
        self.supplier_repository = SupplierRepository(db)
    
    async def create_delivery(
        self,
        tenant_id: UUID,
        delivery_data: DeliveryCreate,
        created_by: UUID
    ) -> Delivery:
        """Create a new delivery"""
        logger.info(
            "Creating delivery",
            tenant_id=str(tenant_id),
            po_id=str(delivery_data.purchase_order_id)
        )
        
        # Validate purchase order exists and belongs to tenant
        po = self.po_repository.get_by_id(delivery_data.purchase_order_id)
        if not po:
            raise ValueError("Purchase order not found")
        if po.tenant_id != tenant_id:
            raise ValueError("Purchase order does not belong to this tenant")
        if po.status not in [
            PurchaseOrderStatus.CONFIRMED,
            PurchaseOrderStatus.PARTIALLY_RECEIVED
        ]:
            raise ValueError("Purchase order must be confirmed before creating deliveries")
        
        # Validate supplier
        supplier = self.supplier_repository.get_by_id(delivery_data.supplier_id)
        if not supplier:
            raise ValueError("Supplier not found")
        if supplier.id != po.supplier_id:
            raise ValueError("Supplier does not match purchase order supplier")
        
        # Generate delivery number
        delivery_number = self.repository.generate_delivery_number(tenant_id)
        
        # Create delivery
        delivery_create_data = delivery_data.model_dump(exclude={'items'})
        delivery_create_data.update({
            'tenant_id': tenant_id,
            'delivery_number': delivery_number,
            'status': DeliveryStatus.SCHEDULED,
            'created_by': created_by
        })
        
        # Set default scheduled date if not provided
        if not delivery_create_data.get('scheduled_date'):
            delivery_create_data['scheduled_date'] = datetime.utcnow()
        
        delivery = self.repository.create(delivery_create_data)
        
        # Create delivery items
        from app.repositories.purchase_order_item_repository import PurchaseOrderItemRepository
        item_repo = PurchaseOrderItemRepository(self.db)
        
        for item_data in delivery_data.items:
            # Validate purchase order item
            po_item = item_repo.get_by_id(item_data.purchase_order_item_id)
            if not po_item or po_item.purchase_order_id != po.id:
                raise ValueError("Invalid purchase order item")
            
            # Create delivery item
            from app.models.suppliers import DeliveryItem
            item_create_data = item_data.model_dump()
            item_create_data.update({
                'tenant_id': tenant_id,
                'delivery_id': delivery.id
            })
            
            delivery_item = DeliveryItem(**item_create_data)
            self.db.add(delivery_item)
        
        self.db.commit()
        
        logger.info(
            "Delivery created successfully",
            tenant_id=str(tenant_id),
            delivery_id=str(delivery.id),
            delivery_number=delivery_number
        )
        
        return delivery
    
    async def get_delivery(self, delivery_id: UUID) -> Optional[Delivery]:
        """Get delivery by ID with items"""
        return self.repository.get_with_items(delivery_id)
    
    async def update_delivery(
        self,
        delivery_id: UUID,
        delivery_data: DeliveryUpdate,
        updated_by: UUID
    ) -> Optional[Delivery]:
        """Update delivery information"""
        logger.info("Updating delivery", delivery_id=str(delivery_id))
        
        delivery = self.repository.get_by_id(delivery_id)
        if not delivery:
            return None
        
        # Check if delivery can be modified
        if delivery.status in [DeliveryStatus.DELIVERED, DeliveryStatus.FAILED_DELIVERY]:
            raise ValueError("Cannot modify completed deliveries")
        
        # Prepare update data
        update_data = delivery_data.model_dump(exclude_unset=True)
        update_data['created_by'] = updated_by  # Track who updated
        update_data['updated_at'] = datetime.utcnow()
        
        delivery = self.repository.update(delivery_id, update_data)
        
        logger.info("Delivery updated successfully", delivery_id=str(delivery_id))
        return delivery
    
    async 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"""
        logger.info("Updating delivery status", delivery_id=str(delivery_id), status=status.value)
        
        return self.repository.update_delivery_status(
            delivery_id=delivery_id,
            status=status,
            updated_by=updated_by,
            notes=notes,
            update_timestamps=update_timestamps
        )
    
    async 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,
        notes: Optional[str] = None
    ) -> Optional[Delivery]:
        """Mark delivery as received with inspection details"""
        logger.info("Marking delivery as received", delivery_id=str(delivery_id))
        
        delivery = self.repository.mark_as_received(
            delivery_id=delivery_id,
            received_by=received_by,
            inspection_passed=inspection_passed,
            inspection_notes=inspection_notes,
            quality_issues=quality_issues
        )
        
        if not delivery:
            return None
        
        # Add custom notes if provided
        if notes:
            existing_notes = delivery.notes or ""
            timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M")
            delivery.notes = f"{existing_notes}\n[{timestamp}] Receipt notes: {notes}".strip()
            self.repository.update(delivery_id, {'notes': delivery.notes})
        
        # Update purchase order item received quantities
        await self._update_purchase_order_received_quantities(delivery)
        
        # Check if purchase order is fully received
        await self._check_purchase_order_completion(delivery.purchase_order_id)
        
        logger.info("Delivery marked as received", delivery_id=str(delivery_id))
        return delivery
    
    async def search_deliveries(
        self,
        tenant_id: UUID,
        search_params: DeliverySearchParams
    ) -> List[Delivery]:
        """Search deliveries with filters"""
        return self.repository.search_deliveries(
            tenant_id=tenant_id,
            supplier_id=search_params.supplier_id,
            status=search_params.status,
            date_from=search_params.date_from,
            date_to=search_params.date_to,
            search_term=search_params.search_term,
            limit=search_params.limit,
            offset=search_params.offset
        )
    
    async def get_deliveries_by_purchase_order(self, po_id: UUID) -> List[Delivery]:
        """Get all deliveries for a purchase order"""
        return self.repository.get_by_purchase_order(po_id)
    
    async def get_todays_deliveries(self, tenant_id: UUID) -> List[Delivery]:
        """Get deliveries scheduled for today"""
        return self.repository.get_todays_deliveries(tenant_id)
    
    async def get_overdue_deliveries(self, tenant_id: UUID) -> List[Delivery]:
        """Get overdue deliveries"""
        return self.repository.get_overdue_deliveries(tenant_id)
    
    async def get_scheduled_deliveries(
        self,
        tenant_id: UUID,
        date_from: Optional[datetime] = None,
        date_to: Optional[datetime] = None
    ) -> List[Delivery]:
        """Get scheduled deliveries for date range"""
        return self.repository.get_scheduled_deliveries(tenant_id, date_from, date_to)
    
    async 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"""
        return self.repository.get_delivery_performance_stats(
            tenant_id, days_back, supplier_id
        )
    
    async def get_upcoming_deliveries_summary(self, tenant_id: UUID) -> Dict[str, Any]:
        """Get summary of upcoming deliveries"""
        return self.repository.get_upcoming_deliveries_summary(tenant_id)
    
    async def _update_purchase_order_received_quantities(self, delivery: Delivery):
        """Update purchase order item received quantities based on delivery"""
        from app.repositories.purchase_order_item_repository import PurchaseOrderItemRepository
        
        item_repo = PurchaseOrderItemRepository(self.db)
        
        # Get delivery items with accepted quantities
        delivery_with_items = self.repository.get_with_items(delivery.id)
        if not delivery_with_items or not delivery_with_items.items:
            return
        
        for delivery_item in delivery_with_items.items:
            # Update purchase order item received quantity
            item_repo.add_received_quantity(
                delivery_item.purchase_order_item_id,
                delivery_item.accepted_quantity
            )
    
    async def _check_purchase_order_completion(self, po_id: UUID):
        """Check if purchase order is fully received and update status"""
        from app.repositories.purchase_order_item_repository import PurchaseOrderItemRepository
        
        item_repo = PurchaseOrderItemRepository(self.db)
        po_items = item_repo.get_by_purchase_order(po_id)
        
        if not po_items:
            return
        
        # Check if all items are fully received
        fully_received = all(item.remaining_quantity == 0 for item in po_items)
        partially_received = any(item.received_quantity > 0 for item in po_items)
        
        if fully_received:
            # Mark purchase order as completed
            self.po_repository.update_order_status(
                po_id,
                PurchaseOrderStatus.COMPLETED,
                po_items[0].tenant_id,  # Use tenant_id as updated_by placeholder
                "All items received"
            )
        elif partially_received:
            # Mark as partially received if not already
            po = self.po_repository.get_by_id(po_id)
            if po and po.status == PurchaseOrderStatus.CONFIRMED:
                self.po_repository.update_order_status(
                    po_id,
                    PurchaseOrderStatus.PARTIALLY_RECEIVED,
                    po.tenant_id,  # Use tenant_id as updated_by placeholder
                    "Partial delivery received"
                )
    
    async def generate_delivery_tracking_info(self, delivery_id: UUID) -> Dict[str, Any]:
        """Generate delivery tracking information"""
        delivery = self.repository.get_with_items(delivery_id)
        if not delivery:
            return {}
        
        # Calculate delivery metrics
        total_items = len(delivery.items) if delivery.items else 0
        delivered_items = sum(
            1 for item in (delivery.items or [])
            if item.delivered_quantity > 0
        )
        accepted_items = sum(
            1 for item in (delivery.items or [])
            if item.accepted_quantity > 0
        )
        rejected_items = sum(
            1 for item in (delivery.items or [])
            if item.rejected_quantity > 0
        )
        
        # Calculate timing metrics
        on_time = False
        delay_hours = 0
        if delivery.scheduled_date and delivery.actual_arrival:
            delay_seconds = (delivery.actual_arrival - delivery.scheduled_date).total_seconds()
            delay_hours = delay_seconds / 3600
            on_time = delay_hours <= 0
        
        return {
            "delivery_id": str(delivery.id),
            "delivery_number": delivery.delivery_number,
            "status": delivery.status.value,
            "total_items": total_items,
            "delivered_items": delivered_items,
            "accepted_items": accepted_items,
            "rejected_items": rejected_items,
            "inspection_passed": delivery.inspection_passed,
            "on_time": on_time,
            "delay_hours": round(delay_hours, 1) if delay_hours > 0 else 0,
            "quality_issues": delivery.quality_issues or {},
            "scheduled_date": delivery.scheduled_date.isoformat() if delivery.scheduled_date else None,
            "actual_arrival": delivery.actual_arrival.isoformat() if delivery.actual_arrival else None,
            "completed_at": delivery.completed_at.isoformat() if delivery.completed_at else None
        }