# services/suppliers/app/repositories/purchase_order_repository.py
"""
Purchase Order repository for database operations
"""

from typing import List, Optional, Dict, Any, Tuple
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 (
    PurchaseOrder, PurchaseOrderItem, PurchaseOrderStatus, 
    Supplier, SupplierPriceList
)
from app.repositories.base import BaseRepository


class PurchaseOrderRepository(BaseRepository[PurchaseOrder]):
    """Repository for purchase order management operations"""
    
    def __init__(self, db: Session):
        super().__init__(PurchaseOrder, db)
    
    def get_by_po_number(self, tenant_id: UUID, po_number: str) -> Optional[PurchaseOrder]:
        """Get purchase order by PO number within tenant"""
        return (
            self.db.query(self.model)
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.po_number == po_number
                )
            )
            .first()
        )
    
    def get_with_items(self, po_id: UUID) -> Optional[PurchaseOrder]:
        """Get purchase order with all items loaded"""
        return (
            self.db.query(self.model)
            .options(joinedload(self.model.items))
            .filter(self.model.id == po_id)
            .first()
        )
    
    def search_purchase_orders(
        self,
        tenant_id: UUID,
        supplier_id: Optional[UUID] = None,
        status: Optional[PurchaseOrderStatus] = None,
        priority: Optional[str] = None,
        date_from: Optional[datetime] = None,
        date_to: Optional[datetime] = None,
        search_term: Optional[str] = None,
        limit: int = 50,
        offset: int = 0
    ) -> List[PurchaseOrder]:
        """Search purchase orders with comprehensive filters"""
        query = (
            self.db.query(self.model)
            .options(joinedload(self.model.supplier))
            .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)
        
        # Priority filter
        if priority:
            query = query.filter(self.model.priority == priority)
        
        # Date range filter
        if date_from:
            query = query.filter(self.model.order_date >= date_from)
        if date_to:
            query = query.filter(self.model.order_date <= date_to)
        
        # Search term filter (PO number, reference, supplier name)
        if search_term:
            search_filter = or_(
                self.model.po_number.ilike(f"%{search_term}%"),
                self.model.reference_number.ilike(f"%{search_term}%"),
                self.model.supplier.has(Supplier.name.ilike(f"%{search_term}%"))
            )
            query = query.filter(search_filter)
        
        return (
            query.order_by(desc(self.model.order_date))
            .limit(limit)
            .offset(offset)
            .all()
        )
    
    def get_orders_by_status(
        self, 
        tenant_id: UUID, 
        status: PurchaseOrderStatus
    ) -> List[PurchaseOrder]:
        """Get all orders with specific status"""
        return (
            self.db.query(self.model)
            .options(joinedload(self.model.supplier))
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.status == status
                )
            )
            .order_by(self.model.order_date.desc())
            .all()
        )
    
    def get_orders_requiring_approval(self, tenant_id: UUID) -> List[PurchaseOrder]:
        """Get orders pending approval"""
        return (
            self.db.query(self.model)
            .options(joinedload(self.model.supplier))
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.status == PurchaseOrderStatus.PENDING_APPROVAL,
                    self.model.requires_approval == True
                )
            )
            .order_by(self.model.order_date.asc())
            .all()
        )
    
    def get_overdue_orders(self, tenant_id: UUID) -> List[PurchaseOrder]:
        """Get orders that are overdue for delivery"""
        today = datetime.utcnow().date()
        
        return (
            self.db.query(self.model)
            .options(joinedload(self.model.supplier))
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.status.in_([
                        PurchaseOrderStatus.CONFIRMED,
                        PurchaseOrderStatus.SENT_TO_SUPPLIER,
                        PurchaseOrderStatus.PARTIALLY_RECEIVED
                    ]),
                    self.model.required_delivery_date < today
                )
            )
            .order_by(self.model.required_delivery_date.asc())
            .all()
        )
    
    def get_orders_by_supplier(
        self, 
        tenant_id: UUID, 
        supplier_id: UUID,
        limit: int = 20
    ) -> List[PurchaseOrder]:
        """Get recent orders for a specific supplier"""
        return (
            self.db.query(self.model)
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.supplier_id == supplier_id
                )
            )
            .order_by(self.model.order_date.desc())
            .limit(limit)
            .all()
        )
    
    def generate_po_number(self, tenant_id: UUID) -> str:
        """Generate next PO number for tenant"""
        # Get current year
        current_year = datetime.utcnow().year
        
        # Find highest PO number for current year
        year_prefix = f"PO{current_year}"
        
        latest_po = (
            self.db.query(self.model.po_number)
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.po_number.like(f"{year_prefix}%")
                )
            )
            .order_by(self.model.po_number.desc())
            .first()
        )
        
        if latest_po:
            # Extract number and increment
            try:
                last_number = int(latest_po.po_number.replace(year_prefix, ""))
                new_number = last_number + 1
            except ValueError:
                new_number = 1
        else:
            new_number = 1
        
        return f"{year_prefix}{new_number:04d}"
    
    def update_order_status(
        self,
        po_id: UUID,
        status: PurchaseOrderStatus,
        updated_by: UUID,
        notes: Optional[str] = None
    ) -> Optional[PurchaseOrder]:
        """Update purchase order status with audit trail"""
        po = self.get_by_id(po_id)
        if not po:
            return None
        
        po.status = status
        po.updated_by = updated_by
        po.updated_at = datetime.utcnow()
        
        if notes:
            existing_notes = po.internal_notes or ""
            timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M")
            po.internal_notes = f"{existing_notes}\n[{timestamp}] Status changed to {status.value}: {notes}".strip()
        
        # Set specific timestamps based on status
        if status == PurchaseOrderStatus.APPROVED:
            po.approved_at = datetime.utcnow()
        elif status == PurchaseOrderStatus.SENT_TO_SUPPLIER:
            po.sent_to_supplier_at = datetime.utcnow()
        elif status == PurchaseOrderStatus.CONFIRMED:
            po.supplier_confirmation_date = datetime.utcnow()
        
        self.db.commit()
        self.db.refresh(po)
        return po
    
    def approve_order(
        self,
        po_id: UUID,
        approved_by: UUID,
        approval_notes: Optional[str] = None
    ) -> Optional[PurchaseOrder]:
        """Approve a purchase order"""
        po = self.get_by_id(po_id)
        if not po or po.status != PurchaseOrderStatus.PENDING_APPROVAL:
            return None
        
        po.status = PurchaseOrderStatus.APPROVED
        po.approved_by = approved_by
        po.approved_at = datetime.utcnow()
        po.updated_by = approved_by
        po.updated_at = datetime.utcnow()
        
        if approval_notes:
            po.internal_notes = (po.internal_notes or "") + f"\nApproval notes: {approval_notes}"
        
        self.db.commit()
        self.db.refresh(po)
        return po
    
    def calculate_order_totals(self, po_id: UUID) -> Optional[PurchaseOrder]:
        """Recalculate order totals based on line items"""
        po = self.get_with_items(po_id)
        if not po:
            return None
        
        # Calculate subtotal from items
        subtotal = sum(item.line_total for item in po.items)
        
        # Keep existing tax, shipping, and discount
        tax_amount = po.tax_amount or 0
        shipping_cost = po.shipping_cost or 0
        discount_amount = po.discount_amount or 0
        
        # Calculate total
        total_amount = subtotal + tax_amount + shipping_cost - discount_amount
        
        # Update PO
        po.subtotal = subtotal
        po.total_amount = max(0, total_amount)  # Ensure non-negative
        po.updated_at = datetime.utcnow()
        
        self.db.commit()
        self.db.refresh(po)
        return po
    
    def get_purchase_order_statistics(self, tenant_id: UUID) -> Dict[str, Any]:
        """Get purchase order statistics for dashboard"""
        # Total orders
        total_orders = self.count_by_tenant(tenant_id)
        
        # Orders by status
        status_counts = (
            self.db.query(
                self.model.status,
                func.count(self.model.id).label('count')
            )
            .filter(self.model.tenant_id == tenant_id)
            .group_by(self.model.status)
            .all()
        )
        
        status_dict = {status.value: 0 for status in PurchaseOrderStatus}
        for status, count in status_counts:
            status_dict[status.value] = count
        
        # This month's orders
        first_day_month = datetime.utcnow().replace(day=1, hour=0, minute=0, second=0, microsecond=0)
        this_month_orders = (
            self.db.query(self.model)
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.order_date >= first_day_month
                )
            )
            .count()
        )
        
        # Total spend this month
        this_month_spend = (
            self.db.query(func.sum(self.model.total_amount))
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.order_date >= first_day_month,
                    self.model.status != PurchaseOrderStatus.CANCELLED
                )
            )
            .scalar()
        ) or 0.0
        
        # Average order value
        avg_order_value = (
            self.db.query(func.avg(self.model.total_amount))
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.status != PurchaseOrderStatus.CANCELLED
                )
            )
            .scalar()
        ) or 0.0
        
        # Overdue orders count
        today = datetime.utcnow().date()
        overdue_count = (
            self.db.query(self.model)
            .filter(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.status.in_([
                        PurchaseOrderStatus.CONFIRMED,
                        PurchaseOrderStatus.SENT_TO_SUPPLIER,
                        PurchaseOrderStatus.PARTIALLY_RECEIVED
                    ]),
                    self.model.required_delivery_date < today
                )
            )
            .count()
        )
        
        return {
            "total_orders": total_orders,
            "status_counts": status_dict,
            "this_month_orders": this_month_orders,
            "this_month_spend": float(this_month_spend),
            "avg_order_value": round(float(avg_order_value), 2),
            "overdue_count": overdue_count,
            "pending_approval": status_dict.get(PurchaseOrderStatus.PENDING_APPROVAL.value, 0)
        }