"""
Overdue Purchase Order Detector

Detects POs that are past their estimated delivery date and triggers alerts.
"""

import uuid
from datetime import datetime, timezone
from typing import List, Dict, Any, Optional
import structlog
from sqlalchemy import select, and_
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.purchase_order import PurchaseOrder, PurchaseOrderStatus
from app.core.database import database_manager

logger = structlog.get_logger()


class OverduePODetector:
    """
    Detects and reports overdue purchase orders.

    A PO is considered overdue if:
    - Status is 'approved' or 'sent_to_supplier' (not yet delivered)
    - estimated_delivery_date is in the past
    - Has not been marked as completed or cancelled
    """

    def __init__(self):
        """Initialize overdue PO detector"""
        self.overdue_threshold_hours = 24  # Grace period before marking overdue

    async def detect_overdue_pos(
        self,
        tenant_id: Optional[uuid.UUID] = None
    ) -> List[Dict[str, Any]]:
        """
        Detect all overdue POs.

        Args:
            tenant_id: Optional tenant filter. If None, checks all tenants.

        Returns:
            List of overdue PO summaries
        """
        try:
            now = datetime.now(timezone.utc)

            async with database_manager.get_session() as session:
                # Build query for overdue POs
                query = select(PurchaseOrder).where(
                    and_(
                        # Only check POs that are in-flight (approved or sent to supplier)
                        PurchaseOrder.status.in_([
                            PurchaseOrderStatus.approved,
                            PurchaseOrderStatus.sent_to_supplier,
                            PurchaseOrderStatus.confirmed
                        ]),
                        # Must have an estimated delivery date
                        PurchaseOrder.estimated_delivery_date.isnot(None),
                        # Delivery date is in the past
                        PurchaseOrder.estimated_delivery_date < now
                    )
                )

                # Add tenant filter if provided
                if tenant_id:
                    query = query.where(PurchaseOrder.tenant_id == tenant_id)

                result = await session.execute(query)
                overdue_pos = result.scalars().all()

                # Calculate days overdue for each PO
                overdue_summaries = []
                for po in overdue_pos:
                    days_overdue = (now - po.estimated_delivery_date).days
                    hours_overdue = (now - po.estimated_delivery_date).total_seconds() / 3600

                    overdue_summaries.append({
                        'po_id': str(po.id),
                        'tenant_id': str(po.tenant_id),
                        'po_number': po.po_number,
                        'supplier_id': str(po.supplier_id),
                        'status': po.status.value,
                        'total_amount': float(po.total_amount),
                        'currency': po.currency,
                        'approved_at': po.approved_at.isoformat() if po.approved_at else None,
                        'estimated_delivery_date': po.estimated_delivery_date.isoformat(),
                        'days_overdue': days_overdue,
                        'hours_overdue': round(hours_overdue, 1),
                        'severity': self._calculate_severity(days_overdue),
                        'priority': po.priority
                    })

                if overdue_summaries:
                    logger.warning(
                        "Detected overdue purchase orders",
                        count=len(overdue_summaries),
                        tenant_id=str(tenant_id) if tenant_id else "all"
                    )
                else:
                    logger.info(
                        "No overdue purchase orders detected",
                        tenant_id=str(tenant_id) if tenant_id else "all"
                    )

                return overdue_summaries

        except Exception as e:
            logger.error(
                "Error detecting overdue POs",
                error=str(e),
                tenant_id=str(tenant_id) if tenant_id else "all",
                exc_info=True
            )
            return []

    async def get_overdue_count_by_tenant(self) -> Dict[str, int]:
        """
        Get count of overdue POs grouped by tenant.

        Returns:
            Dictionary mapping tenant_id to overdue count
        """
        try:
            now = datetime.now(timezone.utc)

            async with database_manager.get_session() as session:
                query = select(
                    PurchaseOrder.tenant_id,
                    PurchaseOrder.id
                ).where(
                    and_(
                        PurchaseOrder.status.in_([
                            PurchaseOrderStatus.approved,
                            PurchaseOrderStatus.sent_to_supplier,
                            PurchaseOrderStatus.confirmed
                        ]),
                        PurchaseOrder.estimated_delivery_date.isnot(None),
                        PurchaseOrder.estimated_delivery_date < now
                    )
                )

                result = await session.execute(query)
                rows = result.all()

                # Count by tenant
                tenant_counts: Dict[str, int] = {}
                for tenant_id, _ in rows:
                    tenant_id_str = str(tenant_id)
                    tenant_counts[tenant_id_str] = tenant_counts.get(tenant_id_str, 0) + 1

                return tenant_counts

        except Exception as e:
            logger.error("Error getting overdue counts", error=str(e), exc_info=True)
            return {}

    def _calculate_severity(self, days_overdue: int) -> str:
        """
        Calculate severity level based on days overdue.

        Args:
            days_overdue: Number of days past delivery date

        Returns:
            Severity level: 'low', 'medium', 'high', 'critical'
        """
        if days_overdue <= 1:
            return 'low'
        elif days_overdue <= 3:
            return 'medium'
        elif days_overdue <= 7:
            return 'high'
        else:
            return 'critical'

    async def get_overdue_pos_for_dashboard(
        self,
        tenant_id: uuid.UUID,
        limit: int = 10
    ) -> List[Dict[str, Any]]:
        """
        Get overdue POs formatted for dashboard display.

        Args:
            tenant_id: Tenant ID
            limit: Max number of results

        Returns:
            List of overdue POs with dashboard-friendly format
        """
        overdue_pos = await self.detect_overdue_pos(tenant_id)

        # Sort by severity and days overdue (most critical first)
        severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3}
        overdue_pos.sort(
            key=lambda x: (severity_order.get(x['severity'], 999), -x['days_overdue'])
        )

        # Limit results
        return overdue_pos[:limit]

    async def check_single_po_overdue(
        self,
        po_id: uuid.UUID,
        tenant_id: uuid.UUID
    ) -> Optional[Dict[str, Any]]:
        """
        Check if a single PO is overdue.

        Args:
            po_id: PO ID
            tenant_id: Tenant ID

        Returns:
            Overdue info if PO is overdue, None otherwise
        """
        try:
            now = datetime.now(timezone.utc)

            async with database_manager.get_session() as session:
                query = select(PurchaseOrder).where(
                    and_(
                        PurchaseOrder.id == po_id,
                        PurchaseOrder.tenant_id == tenant_id
                    )
                )

                result = await session.execute(query)
                po = result.scalar_one_or_none()

                if not po:
                    return None

                # Check if overdue
                if (
                    po.status in [
                        PurchaseOrderStatus.approved,
                        PurchaseOrderStatus.sent_to_supplier,
                        PurchaseOrderStatus.confirmed
                    ] and
                    po.estimated_delivery_date and
                    po.estimated_delivery_date < now
                ):
                    days_overdue = (now - po.estimated_delivery_date).days

                    return {
                        'po_id': str(po.id),
                        'po_number': po.po_number,
                        'days_overdue': days_overdue,
                        'severity': self._calculate_severity(days_overdue),
                        'estimated_delivery_date': po.estimated_delivery_date.isoformat()
                    }

                return None

        except Exception as e:
            logger.error(
                "Error checking single PO overdue status",
                error=str(e),
                po_id=str(po_id),
                exc_info=True
            )
            return None
