# ================================================================
# services/procurement/app/api/purchase_orders.py
# ================================================================
"""
Purchase Orders API - Endpoints for purchase order management
"""

import uuid
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Path, Query
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.database import get_db
from app.core.config import settings
from app.services.purchase_order_service import PurchaseOrderService
from app.services.overdue_po_detector import OverduePODetector
from app.schemas.purchase_order_schemas import (
    PurchaseOrderCreate,
    PurchaseOrderUpdate,
    PurchaseOrderResponse,
    PurchaseOrderWithSupplierResponse,
    PurchaseOrderApproval,
    DeliveryCreate,
    DeliveryResponse,
    SupplierInvoiceCreate,
    SupplierInvoiceResponse,
)
from shared.routing import RouteBuilder
from shared.auth.decorators import get_current_user_dep
from shared.redis_utils import get_value, set_with_ttl
import structlog

logger = structlog.get_logger()

# Create route builder for consistent URL structure
route_builder = RouteBuilder('procurement')
router = APIRouter(tags=["purchase-orders"])


def get_po_service(db: AsyncSession = Depends(get_db)) -> PurchaseOrderService:
    """Dependency to get purchase order service"""
    return PurchaseOrderService(db, settings)


# ================================================================
# PURCHASE ORDER CRUD
# ================================================================

@router.post(
    route_builder.build_base_route("purchase-orders"),
    response_model=PurchaseOrderResponse,
    status_code=201
)
async def create_purchase_order(
    po_data: PurchaseOrderCreate,
    tenant_id: str = Path(..., description="Tenant ID"),
    service: PurchaseOrderService = Depends(get_po_service)
):
    """
    Create a new purchase order with items

    Creates a PO with automatic approval rules evaluation.
    Links to procurement plan if procurement_plan_id is provided.

    Args:
        tenant_id: Tenant UUID
        po_data: Purchase order creation data

    Returns:
        PurchaseOrderResponse with created PO details
    """
    try:
        logger.info("Create PO endpoint called", tenant_id=tenant_id)

        po = await service.create_purchase_order(
            tenant_id=uuid.UUID(tenant_id),
            po_data=po_data
        )

        return PurchaseOrderResponse.model_validate(po)

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error("Error creating purchase order", error=str(e), tenant_id=tenant_id)
        raise HTTPException(status_code=500, detail=str(e))


@router.get(
    route_builder.build_resource_detail_route("purchase-orders", "po_id"),
    response_model=PurchaseOrderWithSupplierResponse
)
async def get_purchase_order(
    po_id: str,
    tenant_id: str = Path(..., description="Tenant ID"),
    service: PurchaseOrderService = Depends(get_po_service)
):
    """Get purchase order by ID with items"""
    try:
        po = await service.get_purchase_order(
            tenant_id=uuid.UUID(tenant_id),
            po_id=uuid.UUID(po_id)
        )

        if not po:
            raise HTTPException(status_code=404, detail="Purchase order not found")

        return PurchaseOrderWithSupplierResponse.model_validate(po)

    except HTTPException:
        raise
    except Exception as e:
        logger.error("Error getting purchase order", error=str(e), po_id=po_id)
        raise HTTPException(status_code=500, detail=str(e))


@router.get(
    route_builder.build_base_route("purchase-orders"),
    response_model=List[PurchaseOrderResponse]
)
async def list_purchase_orders(
    tenant_id: str = Path(..., description="Tenant ID"),
    skip: int = Query(default=0, ge=0),
    limit: int = Query(default=50, ge=1, le=100),
    supplier_id: Optional[str] = Query(default=None),
    status: Optional[str] = Query(default=None),
    enrich_supplier: bool = Query(default=True, description="Include supplier details (slower)"),
    service: PurchaseOrderService = Depends(get_po_service)
):
    """
    List purchase orders with filters and caching (30s TTL)

    Args:
        tenant_id: Tenant UUID
        skip: Number of records to skip (pagination)
        limit: Maximum number of records to return
        supplier_id: Filter by supplier ID (optional)
        status: Filter by status (optional)
        enrich_supplier: Whether to enrich with supplier data (default: True)

    Returns:
        List of purchase orders
    """
    try:
        # PERFORMANCE OPTIMIZATION: Cache even with status filter for dashboard queries
        # Only skip cache for supplier_id filter and pagination (skip > 0)
        cache_key = None
        if skip == 0 and supplier_id is None:
            cache_key = f"purchase_orders:{tenant_id}:limit:{limit}:status:{status}:enrich:{enrich_supplier}"
            try:
                cached_result = await get_value(cache_key)
                if cached_result is not None:
                    logger.debug("Cache hit for purchase orders", cache_key=cache_key, tenant_id=tenant_id, status=status)
                    return [PurchaseOrderResponse(**po) for po in cached_result]
            except Exception as e:
                logger.warning("Cache read failed, continuing without cache", cache_key=cache_key, error=str(e))

        # Cache miss - fetch from database
        pos = await service.list_purchase_orders(
            tenant_id=uuid.UUID(tenant_id),
            skip=skip,
            limit=limit,
            supplier_id=uuid.UUID(supplier_id) if supplier_id else None,
            status=status,
            enrich_supplier=enrich_supplier
        )

        result = [PurchaseOrderResponse.model_validate(po) for po in pos]

        # PERFORMANCE OPTIMIZATION: Cache the result (20s TTL for purchase orders)
        if cache_key:
            try:
                import json
                await set_with_ttl(cache_key, json.dumps([po.model_dump() for po in result]), ttl=20)
                logger.debug("Cached purchase orders", cache_key=cache_key, ttl=20, tenant_id=tenant_id, status=status)
            except Exception as e:
                logger.warning("Cache write failed, continuing without caching", cache_key=cache_key, error=str(e))

        return result

    except Exception as e:
        logger.error("Error listing purchase orders", error=str(e), tenant_id=tenant_id)
        raise HTTPException(status_code=500, detail=str(e))


@router.patch(
    route_builder.build_resource_detail_route("purchase-orders", "po_id"),
    response_model=PurchaseOrderResponse
)
async def update_purchase_order(
    po_id: str,
    po_data: PurchaseOrderUpdate,
    tenant_id: str = Path(..., description="Tenant ID"),
    service: PurchaseOrderService = Depends(get_po_service)
):
    """
    Update purchase order information

    Only draft or pending_approval orders can be modified.
    Financial field changes trigger automatic total recalculation.

    Args:
        tenant_id: Tenant UUID
        po_id: Purchase order UUID
        po_data: Update data

    Returns:
        Updated purchase order
    """
    try:
        po = await service.update_purchase_order(
            tenant_id=uuid.UUID(tenant_id),
            po_id=uuid.UUID(po_id),
            po_data=po_data
        )

        if not po:
            raise HTTPException(status_code=404, detail="Purchase order not found")

        return PurchaseOrderResponse.model_validate(po)

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except HTTPException:
        raise
    except Exception as e:
        logger.error("Error updating purchase order", error=str(e), po_id=po_id)
        raise HTTPException(status_code=500, detail=str(e))


@router.patch(
    route_builder.build_resource_action_route("purchase-orders", "po_id", "status")
)
async def update_order_status(
    po_id: str,
    status: str = Query(..., description="New status"),
    tenant_id: str = Path(..., description="Tenant ID"),
    notes: Optional[str] = Query(default=None),
    service: PurchaseOrderService = Depends(get_po_service)
):
    """
    Update purchase order status

    Validates status transitions to prevent invalid state changes.

    Valid transitions:
    - draft -> pending_approval, approved, cancelled
    - pending_approval -> approved, rejected, cancelled
    - approved -> sent_to_supplier, cancelled
    - sent_to_supplier -> confirmed, cancelled
    - confirmed -> in_production, cancelled
    - in_production -> shipped, cancelled
    - shipped -> delivered, cancelled
    - delivered -> completed

    Args:
        tenant_id: Tenant UUID
        po_id: Purchase order UUID
        status: New status
        notes: Optional status change notes

    Returns:
        Updated purchase order
    """
    try:
        po = await service.update_order_status(
            tenant_id=uuid.UUID(tenant_id),
            po_id=uuid.UUID(po_id),
            status=status,
            notes=notes
        )

        if not po:
            raise HTTPException(status_code=404, detail="Purchase order not found")

        return PurchaseOrderResponse.model_validate(po)

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except HTTPException:
        raise
    except Exception as e:
        logger.error("Error updating PO status", error=str(e), po_id=po_id)
        raise HTTPException(status_code=500, detail=str(e))


# ================================================================
# APPROVAL WORKFLOW
# ================================================================

@router.post(
    route_builder.build_resource_action_route("purchase-orders", "po_id", "approve"),
    response_model=PurchaseOrderResponse
)
async def approve_purchase_order(
    po_id: str,
    approval_data: PurchaseOrderApproval,
    tenant_id: str = Path(..., description="Tenant ID"),
    service: PurchaseOrderService = Depends(get_po_service)
):
    """
    Approve or reject a purchase order

    Args:
        tenant_id: Tenant UUID
        po_id: Purchase order UUID
        approval_data: Approval or rejection data

    Returns:
        Updated purchase order
    """
    try:
        if approval_data.action == "approve":
            po = await service.approve_purchase_order(
                tenant_id=uuid.UUID(tenant_id),
                po_id=uuid.UUID(po_id),
                approved_by=approval_data.approved_by,
                approval_notes=approval_data.notes
            )
        elif approval_data.action == "reject":
            po = await service.reject_purchase_order(
                tenant_id=uuid.UUID(tenant_id),
                po_id=uuid.UUID(po_id),
                rejected_by=approval_data.approved_by,
                rejection_reason=approval_data.notes or "No reason provided"
            )
        else:
            raise ValueError("Invalid action. Must be 'approve' or 'reject'")

        if not po:
            raise HTTPException(status_code=404, detail="Purchase order not found")

        return PurchaseOrderResponse.model_validate(po)

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except HTTPException:
        raise
    except Exception as e:
        logger.error("Error in PO approval workflow", error=str(e), po_id=po_id)
        raise HTTPException(status_code=500, detail=str(e))


@router.post(
    route_builder.build_resource_action_route("purchase-orders", "po_id", "cancel"),
    response_model=PurchaseOrderResponse
)
async def cancel_purchase_order(
    po_id: str,
    reason: str = Query(..., description="Cancellation reason"),
    cancelled_by: Optional[str] = Query(default=None),
    tenant_id: str = Path(..., description="Tenant ID"),
    service: PurchaseOrderService = Depends(get_po_service)
):
    """
    Cancel a purchase order

    Args:
        tenant_id: Tenant UUID
        po_id: Purchase order UUID
        reason: Cancellation reason
        cancelled_by: User ID performing cancellation

    Returns:
        Cancelled purchase order
    """
    try:
        po = await service.cancel_purchase_order(
            tenant_id=uuid.UUID(tenant_id),
            po_id=uuid.UUID(po_id),
            cancelled_by=uuid.UUID(cancelled_by) if cancelled_by else None,
            cancellation_reason=reason
        )

        if not po:
            raise HTTPException(status_code=404, detail="Purchase order not found")

        return PurchaseOrderResponse.model_validate(po)

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except HTTPException:
        raise
    except Exception as e:
        logger.error("Error cancelling purchase order", error=str(e), po_id=po_id)
        raise HTTPException(status_code=500, detail=str(e))


# ================================================================
# DELIVERY MANAGEMENT
# ================================================================

@router.post(
    route_builder.build_nested_resource_route("purchase-orders", "po_id", "deliveries"),
    response_model=DeliveryResponse,
    status_code=201
)
async def create_delivery(
    po_id: str,
    delivery_data: DeliveryCreate,
    tenant_id: str = Path(..., description="Tenant ID"),
    current_user: dict = Depends(get_current_user_dep),
    service: PurchaseOrderService = Depends(get_po_service)
):
    """
    Create a delivery record for a purchase order

    Tracks delivery scheduling, items, quality inspection, and receipt.

    Args:
        tenant_id: Tenant UUID
        po_id: Purchase order UUID
        delivery_data: Delivery creation data

    Returns:
        DeliveryResponse with created delivery details
    """
    try:
        # Validate PO ID matches
        if str(delivery_data.purchase_order_id) != po_id:
            raise ValueError("Purchase order ID mismatch")

        delivery = await service.create_delivery(
            tenant_id=uuid.UUID(tenant_id),
            delivery_data=delivery_data,
            created_by=uuid.UUID(current_user.get("user_id"))
        )

        return DeliveryResponse.model_validate(delivery)

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error("Error creating delivery", error=str(e), po_id=po_id)
        raise HTTPException(status_code=500, detail=str(e))


@router.patch(
    route_builder.build_nested_resource_route("purchase-orders", "po_id", "deliveries") + "/{delivery_id}/status"
)
async def update_delivery_status(
    po_id: str,
    delivery_id: str,
    status: str = Query(..., description="New delivery status"),
    tenant_id: str = Path(..., description="Tenant ID"),
    current_user: dict = Depends(get_current_user_dep),
    service: PurchaseOrderService = Depends(get_po_service)
):
    """
    Update delivery status

    Valid statuses: scheduled, in_transit, delivered, completed, cancelled

    Args:
        tenant_id: Tenant UUID
        delivery_id: Delivery UUID
        status: New status

    Returns:
        Updated delivery
    """
    try:
        delivery = await service.update_delivery_status(
            tenant_id=uuid.UUID(tenant_id),
            delivery_id=uuid.UUID(delivery_id),
            status=status,
            updated_by=uuid.UUID(current_user.get("user_id"))
        )

        if not delivery:
            raise HTTPException(status_code=404, detail="Delivery not found")

        return DeliveryResponse.model_validate(delivery)

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except HTTPException:
        raise
    except Exception as e:
        logger.error("Error updating delivery status", error=str(e), delivery_id=delivery_id)
        raise HTTPException(status_code=500, detail=str(e))


# ================================================================
# INVOICE MANAGEMENT
# ================================================================

@router.post(
    route_builder.build_nested_resource_route("purchase-orders", "po_id", "invoices"),
    response_model=SupplierInvoiceResponse,
    status_code=201
)
async def create_invoice(
    po_id: str,
    invoice_data: SupplierInvoiceCreate,
    tenant_id: str = Path(..., description="Tenant ID"),
    current_user: dict = Depends(get_current_user_dep),
    service: PurchaseOrderService = Depends(get_po_service)
):
    """
    Create a supplier invoice for a purchase order

    Args:
        tenant_id: Tenant UUID
        po_id: Purchase order UUID
        invoice_data: Invoice creation data

    Returns:
        SupplierInvoiceResponse with created invoice details
    """
    try:
        # Validate PO ID matches
        if str(invoice_data.purchase_order_id) != po_id:
            raise ValueError("Purchase order ID mismatch")

        invoice = await service.create_invoice(
            tenant_id=uuid.UUID(tenant_id),
            invoice_data=invoice_data,
            created_by=uuid.UUID(current_user.get("user_id"))
        )

        return SupplierInvoiceResponse.model_validate(invoice)

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error("Error creating invoice", error=str(e), po_id=po_id)
        raise HTTPException(status_code=500, detail=str(e))


# ================================================================
# OVERDUE PO DETECTION
# ================================================================

@router.get(
    route_builder.build_base_route("purchase-orders/overdue"),
    response_model=List[dict]
)
async def get_overdue_purchase_orders(
    tenant_id: str = Path(..., description="Tenant ID"),
    limit: int = Query(10, ge=1, le=100, description="Max results")
):
    """
    Get overdue purchase orders for dashboard display.

    Returns POs that are past their estimated delivery date
    but not yet marked as delivered.

    Args:
        tenant_id: Tenant UUID
        limit: Maximum number of results (default: 10)

    Returns:
        List of overdue PO summaries with severity and days overdue
    """
    try:
        detector = OverduePODetector()
        overdue_pos = await detector.get_overdue_pos_for_dashboard(
            tenant_id=uuid.UUID(tenant_id),
            limit=limit
        )

        return overdue_pos

    except Exception as e:
        logger.error("Error getting overdue POs", error=str(e), tenant_id=tenant_id)
        raise HTTPException(status_code=500, detail=str(e))


@router.get(
    route_builder.build_resource_action_route("purchase-orders", "po_id", "overdue-status"),
    response_model=dict
)
async def check_po_overdue_status(
    po_id: str,
    tenant_id: str = Path(..., description="Tenant ID")
):
    """
    Check if a specific PO is overdue.

    Args:
        tenant_id: Tenant UUID
        po_id: Purchase order UUID

    Returns:
        Overdue status info or null if not overdue
    """
    try:
        detector = OverduePODetector()
        overdue_info = await detector.check_single_po_overdue(
            po_id=uuid.UUID(po_id),
            tenant_id=uuid.UUID(tenant_id)
        )

        if overdue_info:
            return overdue_info
        else:
            return {"overdue": False}

    except Exception as e:
        logger.error("Error checking PO overdue status", error=str(e), po_id=po_id)
        raise HTTPException(status_code=500, detail=str(e))
