# ================================================================
# services/procurement/app/repositories/purchase_order_repository.py
# ================================================================
"""
Purchase Order Repository - Database operations for purchase orders
Migrated from Suppliers Service
"""

import uuid
from datetime import datetime, date
from typing import List, Optional, Dict, Any
from sqlalchemy import select, and_, or_, desc, func
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.models.purchase_order import (
    PurchaseOrder,
    PurchaseOrderItem,
    PurchaseOrderStatus,
    Delivery,
    DeliveryStatus,
    SupplierInvoice,
)
from app.repositories.base_repository import BaseRepository


class PurchaseOrderRepository(BaseRepository):
    """Repository for purchase order operations"""

    def __init__(self, db: AsyncSession):
        super().__init__(PurchaseOrder)
        self.db = db

    async def create_po(self, po_data: Dict[str, Any]) -> PurchaseOrder:
        """Create a new purchase order"""
        po = PurchaseOrder(**po_data)
        self.db.add(po)
        await self.db.flush()
        return po

    async def get_po_by_id(self, po_id: uuid.UUID, tenant_id: uuid.UUID) -> Optional[PurchaseOrder]:
        """Get purchase order by ID with items loaded"""
        stmt = select(PurchaseOrder).where(
            and_(
                PurchaseOrder.id == po_id,
                PurchaseOrder.tenant_id == tenant_id
            )
        ).options(
            selectinload(PurchaseOrder.items),
            selectinload(PurchaseOrder.deliveries),
            selectinload(PurchaseOrder.invoices)
        )

        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    async def get_po_by_number(self, po_number: str, tenant_id: uuid.UUID) -> Optional[PurchaseOrder]:
        """Get purchase order by PO number"""
        stmt = select(PurchaseOrder).where(
            and_(
                PurchaseOrder.po_number == po_number,
                PurchaseOrder.tenant_id == tenant_id
            )
        ).options(selectinload(PurchaseOrder.items))

        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    async def list_purchase_orders(
        self,
        tenant_id: uuid.UUID,
        status: Optional[PurchaseOrderStatus] = None,
        supplier_id: Optional[uuid.UUID] = None,
        priority: Optional[str] = None,
        start_date: Optional[date] = None,
        end_date: Optional[date] = None,
        limit: int = 50,
        offset: int = 0
    ) -> List[PurchaseOrder]:
        """List purchase orders with filters"""
        conditions = [PurchaseOrder.tenant_id == tenant_id]

        if status:
            conditions.append(PurchaseOrder.status == status)
        if supplier_id:
            conditions.append(PurchaseOrder.supplier_id == supplier_id)
        if priority:
            conditions.append(PurchaseOrder.priority == priority)
        if start_date:
            conditions.append(PurchaseOrder.order_date >= start_date)
        if end_date:
            conditions.append(PurchaseOrder.order_date <= end_date)

        stmt = (
            select(PurchaseOrder)
            .where(and_(*conditions))
            .order_by(desc(PurchaseOrder.order_date))
            .limit(limit)
            .offset(offset)
            .options(selectinload(PurchaseOrder.items))
        )

        result = await self.db.execute(stmt)
        return result.scalars().all()

    async def get_pending_approval(self, tenant_id: uuid.UUID) -> List[PurchaseOrder]:
        """Get purchase orders pending approval"""
        stmt = select(PurchaseOrder).where(
            and_(
                PurchaseOrder.tenant_id == tenant_id,
                PurchaseOrder.status == PurchaseOrderStatus.pending_approval
            )
        ).order_by(PurchaseOrder.total_amount.desc())

        result = await self.db.execute(stmt)
        return result.scalars().all()

    async def update_po(self, po_id: uuid.UUID, tenant_id: uuid.UUID, updates: Dict[str, Any]) -> Optional[PurchaseOrder]:
        """Update purchase order"""
        po = await self.get_po_by_id(po_id, tenant_id)
        if not po:
            return None

        for key, value in updates.items():
            if hasattr(po, key):
                setattr(po, key, value)

        po.updated_at = datetime.utcnow()
        await self.db.flush()
        return po

    async def generate_po_number(self, tenant_id: uuid.UUID) -> str:
        """Generate unique PO number"""
        today = date.today()
        date_str = today.strftime("%Y%m%d")

        # Count existing POs for today
        stmt = select(func.count(PurchaseOrder.id)).where(
            and_(
                PurchaseOrder.tenant_id == tenant_id,
                func.date(PurchaseOrder.order_date) == today
            )
        )
        result = await self.db.execute(stmt)
        count = result.scalar() or 0

        return f"PO-{date_str}-{count + 1:04d}"


class PurchaseOrderItemRepository(BaseRepository):
    """Repository for purchase order item operations"""

    def __init__(self, db: AsyncSession):
        super().__init__(PurchaseOrderItem)
        self.db = db

    async def create_item(self, item_data: Dict[str, Any]) -> PurchaseOrderItem:
        """Create a purchase order item"""
        item = PurchaseOrderItem(**item_data)
        self.db.add(item)
        await self.db.flush()
        return item

    async def create_items_batch(self, items_data: List[Dict[str, Any]]) -> List[PurchaseOrderItem]:
        """Create multiple purchase order items"""
        items = [PurchaseOrderItem(**data) for data in items_data]
        self.db.add_all(items)
        await self.db.flush()
        return items

    async def get_items_by_po(self, po_id: uuid.UUID) -> List[PurchaseOrderItem]:
        """Get all items for a purchase order"""
        stmt = select(PurchaseOrderItem).where(
            PurchaseOrderItem.purchase_order_id == po_id
        )

        result = await self.db.execute(stmt)
        return result.scalars().all()


class DeliveryRepository(BaseRepository):
    """Repository for delivery operations"""

    def __init__(self, db: AsyncSession):
        super().__init__(Delivery)
        self.db = db

    async def create_delivery(self, delivery_data: Dict[str, Any]) -> Delivery:
        """Create a new delivery"""
        delivery = Delivery(**delivery_data)
        self.db.add(delivery)
        await self.db.flush()
        return delivery

    async def get_delivery_by_id(self, delivery_id: uuid.UUID, tenant_id: uuid.UUID) -> Optional[Delivery]:
        """Get delivery by ID with items loaded"""
        stmt = select(Delivery).where(
            and_(
                Delivery.id == delivery_id,
                Delivery.tenant_id == tenant_id
            )
        ).options(selectinload(Delivery.items))

        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    async def get_deliveries_by_po(self, po_id: uuid.UUID) -> List[Delivery]:
        """Get all deliveries for a purchase order"""
        stmt = select(Delivery).where(
            Delivery.purchase_order_id == po_id
        ).options(selectinload(Delivery.items))

        result = await self.db.execute(stmt)
        return result.scalars().all()

    async def create_delivery_item(self, item_data: Dict[str, Any]):
        """Create a delivery item"""
        from app.models.purchase_order import DeliveryItem
        item = DeliveryItem(**item_data)
        self.db.add(item)
        await self.db.flush()
        return item

    async def update_delivery(
        self,
        delivery_id: uuid.UUID,
        tenant_id: uuid.UUID,
        updates: Dict[str, Any]
    ) -> Optional[Delivery]:
        """Update delivery"""
        delivery = await self.get_delivery_by_id(delivery_id, tenant_id)
        if not delivery:
            return None

        for key, value in updates.items():
            if hasattr(delivery, key):
                setattr(delivery, key, value)

        delivery.updated_at = datetime.utcnow()
        await self.db.flush()
        return delivery

    async def generate_delivery_number(self, tenant_id: uuid.UUID) -> str:
        """Generate unique delivery number"""
        today = date.today()
        date_str = today.strftime("%Y%m%d")

        stmt = select(func.count(Delivery.id)).where(
            and_(
                Delivery.tenant_id == tenant_id,
                func.date(Delivery.created_at) == today
            )
        )
        result = await self.db.execute(stmt)
        count = result.scalar() or 0

        return f"DEL-{date_str}-{count + 1:04d}"


class SupplierInvoiceRepository(BaseRepository):
    """Repository for supplier invoice operations"""

    def __init__(self, db: AsyncSession):
        super().__init__(SupplierInvoice)
        self.db = db

    async def create_invoice(self, invoice_data: Dict[str, Any]) -> SupplierInvoice:
        """Create a new supplier invoice"""
        invoice = SupplierInvoice(**invoice_data)
        self.db.add(invoice)
        await self.db.flush()
        return invoice

    async def get_invoice_by_id(self, invoice_id: uuid.UUID, tenant_id: uuid.UUID) -> Optional[SupplierInvoice]:
        """Get invoice by ID"""
        stmt = select(SupplierInvoice).where(
            and_(
                SupplierInvoice.id == invoice_id,
                SupplierInvoice.tenant_id == tenant_id
            )
        )
        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    async def get_invoices_by_po(self, po_id: uuid.UUID) -> List[SupplierInvoice]:
        """Get all invoices for a purchase order"""
        stmt = select(SupplierInvoice).where(
            SupplierInvoice.purchase_order_id == po_id
        )
        result = await self.db.execute(stmt)
        return result.scalars().all()

    async def get_invoices_by_supplier(self, supplier_id: uuid.UUID, tenant_id: uuid.UUID) -> List[SupplierInvoice]:
        """Get all invoices for a supplier"""
        stmt = select(SupplierInvoice).where(
            and_(
                SupplierInvoice.supplier_id == supplier_id,
                SupplierInvoice.tenant_id == tenant_id
            )
        ).order_by(SupplierInvoice.invoice_date.desc())
        result = await self.db.execute(stmt)
        return result.scalars().all()

    async def generate_invoice_number(self, tenant_id: uuid.UUID) -> str:
        """Generate unique invoice number"""
        today = date.today()
        date_str = today.strftime("%Y%m%d")

        stmt = select(func.count(SupplierInvoice.id)).where(
            and_(
                SupplierInvoice.tenant_id == tenant_id,
                func.date(SupplierInvoice.created_at) == today
            )
        )
        result = await self.db.execute(stmt)
        count = result.scalar() or 0

        return f"INV-{date_str}-{count + 1:04d}"
