"""
Distribution Service for Enterprise Tier
Manages delivery routes and shipment tracking for parent-child tenant networks
"""

import asyncio
import logging
from typing import List, Dict, Any, Optional
from datetime import datetime, date, timedelta
import uuid
from decimal import Decimal
from shared.utils.demo_dates import BASE_REFERENCE_DATE

from app.models.distribution import DeliveryRoute, Shipment, DeliverySchedule, DeliveryRouteStatus, ShipmentStatus
from app.services.routing_optimizer import RoutingOptimizer
from shared.clients.tenant_client import TenantServiceClient
from shared.clients.inventory_client import InventoryServiceClient
from shared.clients.procurement_client import ProcurementServiceClient

logger = logging.getLogger(__name__)


class DistributionService:
    """
    Core business logic for distribution management
    """

    def __init__(
        self,
        route_repository,
        shipment_repository, 
        schedule_repository,
        procurement_client: ProcurementServiceClient,
        tenant_client: TenantServiceClient,
        inventory_client: InventoryServiceClient,
        routing_optimizer: RoutingOptimizer
    ):
        self.route_repository = route_repository
        self.shipment_repository = shipment_repository
        self.schedule_repository = schedule_repository
        self.procurement_client = procurement_client
        self.tenant_client = tenant_client
        self.inventory_client = inventory_client
        self.routing_optimizer = routing_optimizer

    async def generate_daily_distribution_plan(
        self,
        parent_tenant_id: str,
        target_date: date,
        vehicle_capacity_kg: float = 1000.0
    ) -> Dict[str, Any]:
        """
        Generate daily distribution plan for internal transfers between parent and children
        """
        logger.info(f"Generating distribution plan for parent tenant {parent_tenant_id} on {target_date}")
        
        try:
            # 1. Fetch all approved internal POs for target date from procurement service
            internal_pos = await self.procurement_client.get_approved_internal_purchase_orders(
                parent_tenant_id=parent_tenant_id,
                target_date=target_date
            )
            
            if not internal_pos:
                logger.info(f"No approved internal POs found for {parent_tenant_id} on {target_date}")
                return {
                    "parent_tenant_id": parent_tenant_id,
                    "target_date": target_date.isoformat(),
                    "routes": [],
                    "shipments": [],
                    "status": "no_deliveries_needed"
                }
            
            # 2. Group by child tenant and aggregate weights/volumes
            deliveries_by_child = {}
            for po in internal_pos:
                child_tenant_id = po.get('destination_tenant_id')
                if child_tenant_id not in deliveries_by_child:
                    deliveries_by_child[child_tenant_id] = {
                        'po_id': po.get('id'),
                        'weight_kg': 0,
                        'volume_m3': 0,
                        'items_count': 0
                    }
                
                # Calculate total weight and volume for this PO
                total_weight = 0
                total_volume = 0
                for item in po.get('items', []):
                    # In a real implementation, we'd have weight/volume per item
                    # For now, we'll estimate based on quantity
                    quantity = item.get('ordered_quantity', 0)
                    # Typical bakery item weight estimation (adjust as needed)
                    avg_item_weight_kg = 1.0  # Adjust based on actual products
                    total_weight += Decimal(str(quantity)) * Decimal(str(avg_item_weight_kg))
                
                deliveries_by_child[child_tenant_id]['weight_kg'] += float(total_weight)
                deliveries_by_child[child_tenant_id]['items_count'] += len(po.get('items', []))
            
            # 3. Fetch parent depot location and all child locations from tenant service
            parent_locations_response = await self.tenant_client.get_tenant_locations(parent_tenant_id)
            parent_locations = parent_locations_response.get("locations", []) if isinstance(parent_locations_response, dict) else parent_locations_response
            parent_depot = next((loc for loc in parent_locations if loc.get('location_type') == 'central_production'), None)

            if not parent_depot:
                logger.error(f"No central production location found for parent tenant {parent_tenant_id}")
                raise ValueError(f"No central production location found for parent tenant {parent_tenant_id}")

            depot_location = (float(parent_depot['latitude']), float(parent_depot['longitude']))

            # Fetch all child tenant locations
            deliveries_data = []
            for child_tenant_id, delivery_info in deliveries_by_child.items():
                child_locations_response = await self.tenant_client.get_tenant_locations(child_tenant_id)
                child_locations = child_locations_response.get("locations", []) if isinstance(child_locations_response, dict) else child_locations_response
                child_location = next((loc for loc in child_locations if loc.get('location_type') == 'retail_outlet'), None)

                if not child_location:
                    logger.warning(f"No retail outlet location found for child tenant {child_tenant_id}")
                    continue
                
                deliveries_data.append({
                    'id': f"delivery_{child_tenant_id}",
                    'child_tenant_id': child_tenant_id,
                    'location': (float(child_location['latitude']), float(child_location['longitude'])),
                    'weight_kg': delivery_info['weight_kg'],
                    'volume_m3': delivery_info['volume_m3'],
                    'po_id': delivery_info['po_id'],
                    'items_count': delivery_info['items_count']
                })
            
            if not deliveries_data:
                logger.info(f"No valid delivery locations found for distribution plan")
                return {
                    "parent_tenant_id": parent_tenant_id,
                    "target_date": target_date.isoformat(),
                    "routes": [],
                    "shipments": [],
                    "status": "no_valid_deliveries"
                }
            
            # 4. Call routing_optimizer.optimize_daily_routes()
            optimization_result = await self.routing_optimizer.optimize_daily_routes(
                deliveries=deliveries_data,
                depot_location=depot_location,
                vehicle_capacity_kg=vehicle_capacity_kg
            )
            
            # 5. Create DeliveryRoute and Shipment records
            created_routes = []
            created_shipments = []
            
            for route_idx, route_data in enumerate(optimization_result['routes']):
                # Create DeliveryRoute record
                route = await self.route_repository.create_route({
                    'tenant_id': parent_tenant_id,
                    'route_number': f"R{target_date.strftime('%Y%m%d')}{route_idx + 1:02d}",
                    'route_date': datetime.combine(target_date, datetime.min.time()),
                    'vehicle_id': route_data.get('vehicle_id'),
                    'driver_id': route_data.get('driver_id'),
                    'total_distance_km': route_data.get('total_distance_km', 0),
                    'estimated_duration_minutes': route_data.get('estimated_duration_minutes', 0),
                    'route_sequence': route_data.get('route_sequence', []),
                    'status': 'planned'
                })
                
                created_routes.append(route)
                
                # Create Shipment records for each stop (excluding depot stops)
                for stop in route_data.get('route_sequence', []):
                    if stop.get('is_depot', False) == False and 'child_tenant_id' in stop:
                        shipment = await self.shipment_repository.create_shipment({
                            'tenant_id': parent_tenant_id,
                            'parent_tenant_id': parent_tenant_id,
                            'child_tenant_id': stop['child_tenant_id'],
                            'purchase_order_id': stop.get('po_id'),
                            'delivery_route_id': route['id'],
                            'shipment_number': f"S{target_date.strftime('%Y%m%d')}{len(created_shipments) + 1:03d}",
                            'shipment_date': datetime.combine(target_date, datetime.min.time()),
                            'status': 'pending',
                            'total_weight_kg': stop.get('weight_kg', 0),
                            'total_volume_m3': stop.get('volume_m3', 0)
                        })
                        created_shipments.append(shipment)
            
            logger.info(f"Distribution plan generated: {len(created_routes)} routes, {len(created_shipments)} shipments")
            
            # 6. Publish distribution.plan.created event to message queue
            await self._publish_distribution_plan_created_event(
                parent_tenant_id=parent_tenant_id,
                target_date=target_date,
                routes=created_routes,
                shipments=created_shipments
            )
            
            return {
                "parent_tenant_id": parent_tenant_id,
                "target_date": target_date.isoformat(),
                "routes": [route for route in created_routes],
                "shipments": [shipment for shipment in created_shipments],
                "optimization_metadata": optimization_result,
                "status": "success"
            }
            
        except Exception as e:
            logger.error(f"Error generating distribution plan: {e}", exc_info=True)
            raise

    async def _publish_distribution_plan_created_event(
        self,
        parent_tenant_id: str,
        target_date: date,
        routes: List[Dict[str, Any]],
        shipments: List[Dict[str, Any]]
    ):
        """
        Publish distribution plan created event to message queue
        """
        # In a real implementation, this would publish to RabbitMQ
        logger.info(f"Distribution plan created event published for parent {parent_tenant_id}")

    # Legacy setup_demo_enterprise_distribution method removed
    # Distribution now uses standard cloning pattern via /internal/demo/clone endpoint

    async def get_delivery_routes_for_date(self, tenant_id: str, target_date: date) -> List[Dict[str, Any]]:
        """
        Get all delivery routes for a specific date and tenant
        """
        routes = await self.route_repository.get_routes_by_date(tenant_id, target_date)
        return routes

    async def get_shipments_for_date(self, tenant_id: str, target_date: date) -> List[Dict[str, Any]]:
        """
        Get all shipments for a specific date and tenant
        """
        shipments = await self.shipment_repository.get_shipments_by_date(tenant_id, target_date)
        return shipments

    async def update_shipment_status(self, shipment_id: str, new_status: str, user_id: str, metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """
        Update shipment status with audit trail
        """
        updated_shipment = await self.shipment_repository.update_shipment_status(
            shipment_id=shipment_id,
            new_status=new_status,
            user_id=user_id,
            metadata=metadata
        )
        return updated_shipment

    async def assign_shipments_to_route(self, route_id: str, shipment_ids: List[str], user_id: str) -> Dict[str, Any]:
        """
        Assign multiple shipments to a specific route
        """
        result = await self.shipment_repository.assign_shipments_to_route(
            route_id=route_id,
            shipment_ids=shipment_ids,
            user_id=user_id
        )
        return result

    async def create_delivery_schedule(self, schedule_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Create a delivery schedule for recurring deliveries between parent and child tenants

        Args:
            schedule_data: Dictionary containing schedule information:
                - parent_tenant_id: UUID of parent tenant
                - child_tenant_id: UUID of child tenant
                - schedule_name: Human-readable name for the schedule
                - delivery_days: Comma-separated days (e.g., "Mon,Wed,Fri")
                - delivery_time: Time of day for delivery (HH:MM format)
                - auto_generate_orders: Boolean, whether to auto-generate orders
                - lead_time_days: Number of days lead time for orders
                - is_active: Boolean, whether schedule is active

        Returns:
            Dictionary with created schedule information
        """
        # Create schedule using repository
        try:
            # Ensure required fields are present
            if "delivery_days" not in schedule_data:
                schedule_data["delivery_days"] = "Mon,Wed,Fri"
            if "delivery_time" not in schedule_data:
                schedule_data["delivery_time"] = "09:00"
            if "auto_generate_orders" not in schedule_data:
                schedule_data["auto_generate_orders"] = True
            if "lead_time_days" not in schedule_data:
                schedule_data["lead_time_days"] = 1
            if "is_active" not in schedule_data:
                schedule_data["is_active"] = True

            created_schedule = await self.schedule_repository.create_schedule(schedule_data)
            
            logger.info(
                f"Created delivery schedule {created_schedule.id} for parent {schedule_data.get('parent_tenant_id')} "
                f"to child {schedule_data.get('child_tenant_id')}"
            )
            
            return created_schedule
            
        except Exception as e:
            logger.error(f"Error creating delivery schedule: {e}")
            raise

    # VRP Optimization Service Methods
    async def get_route_by_id(self, route_id: str) -> Optional[Dict[str, Any]]:
        """
        Get a specific delivery route by ID
        """
        return await self.route_repository.get_route_by_id(route_id)

    async def update_route_vrp_metrics(self, route_id: str, vrp_metrics: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """
        Update VRP optimization metrics for a route
        """
        return await self.route_repository.update_route_vrp_metrics(route_id, vrp_metrics)

    async def get_routes_by_tenant(self, tenant_id: str, limit: int = None, offset: int = None, order_by: str = None) -> List[Dict[str, Any]]:
        """
        Get all routes for a specific tenant with pagination and ordering
        """
        return await self.route_repository.get_routes_by_tenant(tenant_id, limit, offset, order_by)