"""
Delivery Tracking Service - With Leader Election

Tracks purchase order deliveries and generates appropriate alerts using EventPublisher:
- DELIVERY_ARRIVING_SOON: 2 hours before delivery window
- DELIVERY_OVERDUE: 30 minutes after expected delivery time
- STOCK_RECEIPT_INCOMPLETE: If delivery not marked as received

Runs as internal scheduler with leader election for horizontal scaling.
Domain ownership: Procurement service owns all PO and delivery tracking.
"""

import structlog
from datetime import datetime, timedelta, timezone
from typing import Dict, Any, Optional, List
from uuid import UUID, uuid4
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from sqlalchemy import select
from sqlalchemy.orm import selectinload

from shared.messaging import UnifiedEventPublisher, EVENT_TYPES
from app.models.purchase_order import PurchaseOrder, PurchaseOrderStatus

logger = structlog.get_logger()


class DeliveryTrackingService:
    """
    Monitors PO deliveries and generates time-based alerts using EventPublisher.

    Uses APScheduler with leader election to run hourly checks.
    Only one pod executes checks - leader election ensures no duplicate alerts.
    """

    def __init__(self, event_publisher: UnifiedEventPublisher, config, database_manager=None):
        self.publisher = event_publisher
        self.config = config
        self.database_manager = database_manager
        self.scheduler = AsyncIOScheduler()
        self._leader_election = None
        self._redis_manager = None
        self._redis_client = None
        self._scheduler_started = False
        self.instance_id = str(uuid4())[:8]  # Short instance ID for logging

    async def start(self):
        """Start the delivery tracking scheduler with leader election"""
        try:
            # Initialize leader election
            await self._setup_leader_election()
        except Exception as e:
            logger.error("Failed to setup leader election, starting in standalone mode",
                        error=str(e))
            # Fallback: start scheduler without leader election
            await self._start_scheduler()

    async def _setup_leader_election(self):
        """Setup Redis-based leader election for horizontal scaling"""
        from shared.leader_election import LeaderElectionService
        from shared.redis_utils import RedisConnectionManager

        # Build Redis URL from config
        redis_url = getattr(self.config, 'REDIS_URL', None)
        if not redis_url:
            redis_password = getattr(self.config, 'REDIS_PASSWORD', '')
            redis_host = getattr(self.config, 'REDIS_HOST', 'localhost')
            redis_port = getattr(self.config, 'REDIS_PORT', 6379)
            redis_db = getattr(self.config, 'REDIS_DB', 0)
            # Use rediss:// for TLS connections (matches the shared config base)
            redis_protocol = "rediss" if getattr(self.config, 'REDIS_TLS_ENABLED', 'true').lower() == 'true' else "redis"
            redis_url = f"{redis_protocol}://:{redis_password}@{redis_host}:{redis_port}/{redis_db}"

        # Create Redis connection using shared manager (handles SSL, pooling, health checks)
        self._redis_manager = await RedisConnectionManager.create(redis_url, decode_responses=False)
        self._redis_client = self._redis_manager.get_client()

        # Create leader election service
        self._leader_election = LeaderElectionService(
            self._redis_client,
            service_name="procurement-delivery-tracking"
        )

        # Start leader election with callbacks
        await self._leader_election.start(
            on_become_leader=self._on_become_leader,
            on_lose_leader=self._on_lose_leader
        )

        logger.info("Leader election initialized for delivery tracking",
                   is_leader=self._leader_election.is_leader,
                   instance_id=self.instance_id)

    async def _on_become_leader(self):
        """Called when this instance becomes the leader"""
        logger.info("Became leader for delivery tracking - starting scheduler",
                   instance_id=self.instance_id)
        await self._start_scheduler()

    async def _on_lose_leader(self):
        """Called when this instance loses leadership"""
        logger.warning("Lost leadership for delivery tracking - stopping scheduler",
                      instance_id=self.instance_id)
        await self._stop_scheduler()

    async def _start_scheduler(self):
        """Start the APScheduler with delivery tracking jobs"""
        if self._scheduler_started:
            logger.debug("Scheduler already started", instance_id=self.instance_id)
            return

        if not self.scheduler.running:
            # Add hourly job to check deliveries
            self.scheduler.add_job(
                self._check_all_tenants,
                trigger=CronTrigger(minute=30),  # Run every hour at :30
                id='hourly_delivery_check',
                name='Hourly Delivery Tracking',
                replace_existing=True,
                max_instances=1,
                coalesce=True
            )

            self.scheduler.start()
            self._scheduler_started = True

            next_run = self.scheduler.get_job('hourly_delivery_check').next_run_time
            logger.info("Delivery tracking scheduler started",
                       instance_id=self.instance_id,
                       next_run=next_run.isoformat() if next_run else None)

    async def _stop_scheduler(self):
        """Stop the APScheduler"""
        if not self._scheduler_started:
            return

        if self.scheduler.running:
            self.scheduler.shutdown(wait=False)
            self._scheduler_started = False
            logger.info("Delivery tracking scheduler stopped", instance_id=self.instance_id)

    async def stop(self):
        """Stop the scheduler and leader election"""
        # Stop leader election first
        if self._leader_election:
            await self._leader_election.stop()
            logger.info("Leader election stopped", instance_id=self.instance_id)

        # Stop scheduler
        await self._stop_scheduler()

        # Close Redis manager (handles client and pool cleanup)
        if self._redis_manager:
            await self._redis_manager.close()

    @property
    def is_leader(self) -> bool:
        """Check if this instance is the leader"""
        return self._leader_election.is_leader if self._leader_election else True

    async def _check_all_tenants(self):
        """
        Check deliveries for all active tenants.

        This method is only called by the leader pod (via APScheduler).
        Leader election is handled at the scheduler level, not here.
        """
        logger.info("Starting delivery checks", instance_id=self.instance_id)

        try:
            # Get all active tenants from database
            tenants = await self._get_active_tenants()

            total_alerts = 0
            for tenant_id in tenants:
                try:
                    result = await self.check_expected_deliveries(tenant_id)
                    total_alerts += sum(result.values())
                except Exception as e:
                    logger.error(
                        "Delivery check failed for tenant",
                        tenant_id=str(tenant_id),
                        error=str(e),
                        exc_info=True
                    )

            logger.info(
                "Delivery checks completed",
                instance_id=self.instance_id,
                tenants_checked=len(tenants),
                total_alerts=total_alerts
            )

        except Exception as e:
            logger.error("Delivery checks failed", error=str(e), exc_info=True)

    async def _get_active_tenants(self) -> List[UUID]:
        """
        Get all active tenants from database.

        Returns list of tenant UUIDs that have purchase orders.
        """
        try:
            async with self.database_manager.get_session() as session:
                # Get distinct tenant_ids that have purchase orders
                query = select(PurchaseOrder.tenant_id).distinct()
                result = await session.execute(query)
                tenant_ids = [row[0] for row in result.all()]

                logger.debug("Active tenants retrieved", count=len(tenant_ids))
                return tenant_ids

        except Exception as e:
            logger.error("Failed to get active tenants", error=str(e))
            return []

    async def check_expected_deliveries(self, tenant_id: UUID) -> Dict[str, int]:
        """
        Check all expected deliveries for a tenant and generate appropriate alerts.

        DIRECT DATABASE ACCESS - No API calls needed!

        Called by:
        - Scheduled job (hourly at :30)
        - Manual trigger endpoint (demo cloning)

        Returns:
            Dict with counts: {
                'arriving_soon': int,
                'overdue': int,
                'receipt_incomplete': int,
                'total_alerts': int
            }
        """
        logger.info("Checking expected deliveries", tenant_id=str(tenant_id))

        counts = {
            'arriving_soon': 0,
            'overdue': 0,
            'receipt_incomplete': 0
        }

        try:
            # Get expected deliveries directly from database
            deliveries = await self._get_expected_deliveries_from_db(tenant_id)

            now = datetime.now(timezone.utc)

            for delivery in deliveries:
                po_id = delivery.get('po_id')
                expected_date = delivery.get('expected_delivery_date')
                delivery_window_hours = delivery.get('delivery_window_hours', 4)
                status = delivery.get('status')

                if not expected_date:
                    continue

                # Parse expected date
                if isinstance(expected_date, str):
                    expected_date = datetime.fromisoformat(expected_date)

                # Make timezone-aware
                if expected_date.tzinfo is None:
                    expected_date = expected_date.replace(tzinfo=timezone.utc)

                # Calculate delivery window
                window_start = expected_date
                window_end = expected_date + timedelta(hours=delivery_window_hours)

                # Check if arriving soon (2 hours before window)
                arriving_soon_time = window_start - timedelta(hours=2)
                if arriving_soon_time <= now < window_start and status in ['approved', 'sent_to_supplier']:
                    if await self._send_arriving_soon_alert(tenant_id, delivery):
                        counts['arriving_soon'] += 1

                # Check if overdue (30 min after window end)
                overdue_time = window_end + timedelta(minutes=30)
                if now >= overdue_time and status in ['approved', 'sent_to_supplier']:
                    if await self._send_overdue_alert(tenant_id, delivery):
                        counts['overdue'] += 1

                # Check if receipt incomplete (delivery window passed, not marked received)
                if now > window_end and status in ['approved', 'sent_to_supplier']:
                    if await self._send_receipt_incomplete_alert(tenant_id, delivery):
                        counts['receipt_incomplete'] += 1

            counts['total_alerts'] = sum([counts['arriving_soon'], counts['overdue'], counts['receipt_incomplete']])

            logger.info(
                "Delivery check completed",
                tenant_id=str(tenant_id),
                **counts
            )

        except Exception as e:
            logger.error(
                "Error checking deliveries",
                tenant_id=str(tenant_id),
                error=str(e),
                exc_info=True
            )

        return counts

    async def _get_expected_deliveries_from_db(
        self,
        tenant_id: UUID,
        days_ahead: int = 1,
        include_overdue: bool = True
    ) -> List[Dict[str, Any]]:
        """
        Query expected deliveries DIRECTLY from database (no HTTP call).

        This replaces the HTTP call to /api/internal/expected-deliveries.

        Returns:
            List of delivery dicts with same structure as API endpoint
        """
        try:
            async with self.database_manager.get_session() as session:
                # Calculate date range
                now = datetime.now(timezone.utc)
                end_date = now + timedelta(days=days_ahead)

                # Build query for purchase orders with expected delivery dates
                query = select(PurchaseOrder).options(
                    selectinload(PurchaseOrder.items)
                ).where(
                    PurchaseOrder.tenant_id == tenant_id,
                    PurchaseOrder.expected_delivery_date.isnot(None),
                    PurchaseOrder.status.in_([
                        PurchaseOrderStatus.approved,
                        PurchaseOrderStatus.sent_to_supplier,
                        PurchaseOrderStatus.confirmed
                    ])
                )

                # Add date filters
                if include_overdue:
                    query = query.where(PurchaseOrder.expected_delivery_date <= end_date)
                else:
                    query = query.where(
                        PurchaseOrder.expected_delivery_date >= now,
                        PurchaseOrder.expected_delivery_date <= end_date
                    )

                # Order by delivery date
                query = query.order_by(PurchaseOrder.expected_delivery_date.asc())

                # Execute query
                result = await session.execute(query)
                purchase_orders = result.scalars().all()

                logger.info(
                    "Expected deliveries query executed",
                    tenant_id=str(tenant_id),
                    po_count=len(purchase_orders),
                    days_ahead=days_ahead,
                    include_overdue=include_overdue,
                    now=now.isoformat(),
                    end_date=end_date.isoformat()
                )

                # Format deliveries (same structure as API endpoint)
                deliveries = []

                for po in purchase_orders:
                    # Simple supplier name extraction
                    supplier_name = f"Supplier-{str(po.supplier_id)[:8]}"
                    supplier_phone = None

                    # Extract from notes if available
                    if po.notes:
                        if "Molinos San José" in po.notes:
                            supplier_name = "Molinos San José S.L."
                            supplier_phone = "+34 915 234 567"
                        elif "Lácteos del Valle" in po.notes:
                            supplier_name = "Lácteos del Valle S.A."
                            supplier_phone = "+34 913 456 789"
                        elif "Chocolates Valor" in po.notes:
                            supplier_name = "Chocolates Valor"
                            supplier_phone = "+34 965 510 062"

                    # Format line items
                    line_items = []
                    for item in po.items[:5]:
                        line_items.append({
                            "product_name": item.product_name,
                            "quantity": float(item.ordered_quantity) if item.ordered_quantity else 0,
                            "unit": item.unit_of_measure or "unit"
                        })

                    delivery_dict = {
                        "po_id": str(po.id),
                        "po_number": po.po_number,
                        "supplier_id": str(po.supplier_id),
                        "supplier_name": supplier_name,
                        "supplier_phone": supplier_phone,
                        "expected_delivery_date": po.expected_delivery_date.isoformat() if po.expected_delivery_date else None,
                        "delivery_window_hours": 4,  # Default
                        "status": po.status.value,
                        "line_items": line_items,
                        "total_amount": float(po.total_amount) if po.total_amount else 0.0,
                        "currency": po.currency
                    }

                    deliveries.append(delivery_dict)

                return deliveries

        except Exception as e:
            logger.error(
                "Error fetching expected deliveries from database",
                tenant_id=str(tenant_id),
                error=str(e),
                exc_info=True
            )
            return []

    async def _send_arriving_soon_alert(
        self,
        tenant_id: UUID,
        delivery: Dict[str, Any]
    ) -> bool:
        """
        Send DELIVERY_ARRIVING_SOON alert (2h before delivery window).

        This appears in the action queue with "Mark as Received" action.
        """
        po_number = delivery.get('po_number', 'N/A')
        supplier_name = delivery.get('supplier_name', 'Supplier')
        expected_date = delivery.get('expected_delivery_date')
        line_items = delivery.get('line_items', [])

        # Format product list
        products = [item['product_name'] for item in line_items[:3]]
        product_list = ", ".join(products)
        if len(line_items) > 3:
            product_list += f" (+{len(line_items) - 3} more)"

        # Calculate time until arrival
        if isinstance(expected_date, str):
            expected_date = datetime.fromisoformat(expected_date)
        if expected_date.tzinfo is None:
            expected_date = expected_date.replace(tzinfo=timezone.utc)

        hours_until = (expected_date - datetime.now(timezone.utc)).total_seconds() / 3600

        metadata = {
            "po_id": delivery['po_id'],
            "po_number": po_number,
            "supplier_id": delivery.get('supplier_id'),
            "supplier_name": supplier_name,
            "supplier_phone": delivery.get('supplier_phone'),
            "expected_delivery_date": expected_date.isoformat(),
            "line_items": line_items,
            "hours_until_arrival": hours_until,
        }

        # Send alert using UnifiedEventPublisher
        success = await self.publisher.publish_alert(
            event_type="supply_chain.delivery_arriving_soon",
            tenant_id=tenant_id,
            severity="medium",
            data=metadata
        )

        if success:
            logger.info(
                "Sent arriving soon alert",
                po_number=po_number,
                supplier=supplier_name
            )

        return success

    async def _send_overdue_alert(
        self,
        tenant_id: UUID,
        delivery: Dict[str, Any]
    ) -> bool:
        """
        Send DELIVERY_OVERDUE alert (30min after expected window).

        Critical priority - needs immediate action (call supplier).
        """
        po_number = delivery.get('po_number', 'N/A')
        supplier_name = delivery.get('supplier_name', 'Supplier')
        expected_date = delivery.get('expected_delivery_date')

        # Calculate how late
        if isinstance(expected_date, str):
            expected_date = datetime.fromisoformat(expected_date)
        if expected_date.tzinfo is None:
            expected_date = expected_date.replace(tzinfo=timezone.utc)

        hours_late = (datetime.now(timezone.utc) - expected_date).total_seconds() / 3600

        metadata = {
            "po_id": delivery['po_id'],
            "po_number": po_number,
            "supplier_id": delivery.get('supplier_id'),
            "supplier_name": supplier_name,
            "supplier_phone": delivery.get('supplier_phone'),
            "expected_delivery_date": expected_date.isoformat(),
            "hours_late": hours_late,
            "financial_impact": delivery.get('total_amount', 0),
            "affected_orders": len(delivery.get('affected_production_batches', [])),
        }

        # Send alert with high severity
        success = await self.publisher.publish_alert(
            event_type="supply_chain.delivery_overdue",
            tenant_id=tenant_id,
            severity="high",
            data=metadata
        )

        if success:
            logger.warning(
                "Sent overdue delivery alert",
                po_number=po_number,
                supplier=supplier_name,
                hours_late=hours_late
            )

        return success

    async def _send_receipt_incomplete_alert(
        self,
        tenant_id: UUID,
        delivery: Dict[str, Any]
    ) -> bool:
        """
        Send STOCK_RECEIPT_INCOMPLETE alert.

        Delivery window has passed but stock not marked as received.
        """
        po_number = delivery.get('po_number', 'N/A')
        supplier_name = delivery.get('supplier_name', 'Supplier')

        metadata = {
            "po_id": delivery['po_id'],
            "po_number": po_number,
            "supplier_id": delivery.get('supplier_id'),
            "supplier_name": supplier_name,
            "expected_delivery_date": delivery.get('expected_delivery_date'),
        }

        # Send alert using UnifiedEventPublisher
        success = await self.publisher.publish_alert(
            event_type="supply_chain.stock_receipt_incomplete",
            tenant_id=tenant_id,
            severity="medium",
            data=metadata
        )

        if success:
            logger.info(
                "Sent receipt incomplete alert",
                po_number=po_number
            )

        return success