"""
Production Capacity Repository
Repository for production capacity operations
"""

from typing import Optional, List, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, text, desc, func
from datetime import datetime, timedelta, date
from uuid import UUID
import structlog

from .base import ProductionBaseRepository
from app.models.production import ProductionCapacity
from shared.database.exceptions import DatabaseError, ValidationError


logger = structlog.get_logger()


class ProductionCapacityRepository(ProductionBaseRepository):
    """Repository for production capacity operations"""
    
    def __init__(self, session: AsyncSession, cache_ttl: Optional[int] = 600):
        # Capacity data changes moderately, medium cache time (10 minutes)
        super().__init__(ProductionCapacity, session, cache_ttl)
    
    async def create_capacity(self, capacity_data: Dict[str, Any]) -> ProductionCapacity:
        """Create a new production capacity entry with validation"""
        try:
            # Validate capacity data
            validation_result = self._validate_production_data(
                capacity_data, 
                ["tenant_id", "resource_type", "resource_id", "resource_name",
                 "date", "start_time", "end_time", "total_capacity_units"]
            )
            
            if not validation_result["is_valid"]:
                raise ValidationError(f"Invalid capacity data: {validation_result['errors']}")
            
            # Set default values
            if "allocated_capacity_units" not in capacity_data:
                capacity_data["allocated_capacity_units"] = 0.0
            if "remaining_capacity_units" not in capacity_data:
                capacity_data["remaining_capacity_units"] = capacity_data["total_capacity_units"]
            if "is_available" not in capacity_data:
                capacity_data["is_available"] = True
            if "is_maintenance" not in capacity_data:
                capacity_data["is_maintenance"] = False
            if "is_reserved" not in capacity_data:
                capacity_data["is_reserved"] = False
            
            # Create capacity entry
            capacity = await self.create(capacity_data)
            
            logger.info("Production capacity created successfully",
                       capacity_id=str(capacity.id),
                       resource_type=capacity.resource_type,
                       resource_id=capacity.resource_id,
                       tenant_id=str(capacity.tenant_id))
            
            return capacity
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error creating production capacity", error=str(e))
            raise DatabaseError(f"Failed to create production capacity: {str(e)}")
    
    async def get_capacity_by_resource(
        self, 
        tenant_id: str, 
        resource_id: str,
        date_filter: Optional[date] = None
    ) -> List[ProductionCapacity]:
        """Get capacity entries for a specific resource"""
        try:
            filters = {
                "tenant_id": tenant_id,
                "resource_id": resource_id
            }
            
            if date_filter:
                filters["date"] = date_filter
            
            capacities = await self.get_multi(
                filters=filters,
                order_by="start_time"
            )
            
            logger.info("Retrieved capacity by resource",
                       count=len(capacities),
                       resource_id=resource_id,
                       tenant_id=tenant_id)
            
            return capacities
            
        except Exception as e:
            logger.error("Error fetching capacity by resource", error=str(e))
            raise DatabaseError(f"Failed to fetch capacity by resource: {str(e)}")
    
    async def get_available_capacity(
        self, 
        tenant_id: str, 
        resource_type: str,
        target_date: date,
        required_capacity: float
    ) -> List[ProductionCapacity]:
        """Get available capacity for a specific date and capacity requirement"""
        try:
            capacities = await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "resource_type": resource_type,
                    "date": target_date,
                    "is_available": True,
                    "is_maintenance": False,
                    "remaining_capacity_units__gte": required_capacity
                },
                order_by="remaining_capacity_units",
                order_desc=True
            )
            
            logger.info("Retrieved available capacity",
                       count=len(capacities),
                       resource_type=resource_type,
                       required_capacity=required_capacity,
                       tenant_id=tenant_id)
            
            return capacities
            
        except Exception as e:
            logger.error("Error fetching available capacity", error=str(e))
            raise DatabaseError(f"Failed to fetch available capacity: {str(e)}")
    
    async def allocate_capacity(
        self, 
        capacity_id: UUID, 
        allocation_amount: float,
        allocation_notes: Optional[str] = None
    ) -> ProductionCapacity:
        """Allocate capacity units from a capacity entry"""
        try:
            capacity = await self.get(capacity_id)
            if not capacity:
                raise ValidationError(f"Capacity {capacity_id} not found")
            
            if allocation_amount > capacity.remaining_capacity_units:
                raise ValidationError(
                    f"Insufficient capacity: requested {allocation_amount}, "
                    f"available {capacity.remaining_capacity_units}"
                )
            
            new_allocated = capacity.allocated_capacity_units + allocation_amount
            new_remaining = capacity.remaining_capacity_units - allocation_amount
            
            update_data = {
                "allocated_capacity_units": new_allocated,
                "remaining_capacity_units": new_remaining,
                "updated_at": datetime.utcnow()
            }
            
            if allocation_notes:
                current_notes = capacity.notes or ""
                update_data["notes"] = f"{current_notes}\n{allocation_notes}".strip()
            
            capacity = await self.update(capacity_id, update_data)
            
            logger.info("Allocated capacity",
                       capacity_id=str(capacity_id),
                       allocation_amount=allocation_amount,
                       remaining_capacity=new_remaining)
            
            return capacity
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error allocating capacity", error=str(e))
            raise DatabaseError(f"Failed to allocate capacity: {str(e)}")
    
    async def release_capacity(
        self, 
        capacity_id: UUID, 
        release_amount: float,
        release_notes: Optional[str] = None
    ) -> ProductionCapacity:
        """Release allocated capacity units back to a capacity entry"""
        try:
            capacity = await self.get(capacity_id)
            if not capacity:
                raise ValidationError(f"Capacity {capacity_id} not found")
            
            if release_amount > capacity.allocated_capacity_units:
                raise ValidationError(
                    f"Cannot release more than allocated: requested {release_amount}, "
                    f"allocated {capacity.allocated_capacity_units}"
                )
            
            new_allocated = capacity.allocated_capacity_units - release_amount
            new_remaining = capacity.remaining_capacity_units + release_amount
            
            update_data = {
                "allocated_capacity_units": new_allocated,
                "remaining_capacity_units": new_remaining,
                "updated_at": datetime.utcnow()
            }
            
            if release_notes:
                current_notes = capacity.notes or ""
                update_data["notes"] = f"{current_notes}\n{release_notes}".strip()
            
            capacity = await self.update(capacity_id, update_data)
            
            logger.info("Released capacity",
                       capacity_id=str(capacity_id),
                       release_amount=release_amount,
                       remaining_capacity=new_remaining)
            
            return capacity
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error releasing capacity", error=str(e))
            raise DatabaseError(f"Failed to release capacity: {str(e)}")
    
    async def get_capacity_utilization_summary(
        self, 
        tenant_id: str, 
        start_date: date, 
        end_date: date,
        resource_type: Optional[str] = None
    ) -> Dict[str, Any]:
        """Get capacity utilization summary for a date range"""
        try:
            filters = {
                "tenant_id": tenant_id,
                "date__gte": start_date,
                "date__lte": end_date
            }
            
            if resource_type:
                filters["resource_type"] = resource_type
            
            capacities = await self.get_multi(filters=filters)
            
            total_capacity = sum(c.total_capacity_units for c in capacities)
            total_allocated = sum(c.allocated_capacity_units for c in capacities)
            total_available = sum(c.remaining_capacity_units for c in capacities)
            
            # Group by resource type
            by_resource_type = {}
            for capacity in capacities:
                rt = capacity.resource_type
                if rt not in by_resource_type:
                    by_resource_type[rt] = {
                        "total_capacity": 0,
                        "allocated_capacity": 0,
                        "available_capacity": 0,
                        "resource_count": 0
                    }
                
                by_resource_type[rt]["total_capacity"] += capacity.total_capacity_units
                by_resource_type[rt]["allocated_capacity"] += capacity.allocated_capacity_units
                by_resource_type[rt]["available_capacity"] += capacity.remaining_capacity_units
                by_resource_type[rt]["resource_count"] += 1
            
            # Calculate utilization percentages
            for rt_data in by_resource_type.values():
                if rt_data["total_capacity"] > 0:
                    rt_data["utilization_percentage"] = round(
                        (rt_data["allocated_capacity"] / rt_data["total_capacity"]) * 100, 2
                    )
                else:
                    rt_data["utilization_percentage"] = 0
            
            return {
                "period_start": start_date.isoformat(),
                "period_end": end_date.isoformat(),
                "total_capacity_units": total_capacity,
                "total_allocated_units": total_allocated,
                "total_available_units": total_available,
                "overall_utilization_percentage": round(
                    (total_allocated / total_capacity * 100) if total_capacity > 0 else 0, 2
                ),
                "by_resource_type": by_resource_type,
                "total_resources": len(capacities),
                "tenant_id": tenant_id
            }
            
        except Exception as e:
            logger.error("Error calculating capacity utilization summary", error=str(e))
            raise DatabaseError(f"Failed to calculate capacity utilization summary: {str(e)}")
    
    async def set_maintenance_mode(
        self, 
        capacity_id: UUID, 
        is_maintenance: bool,
        maintenance_notes: Optional[str] = None
    ) -> ProductionCapacity:
        """Set maintenance mode for a capacity entry"""
        try:
            capacity = await self.get(capacity_id)
            if not capacity:
                raise ValidationError(f"Capacity {capacity_id} not found")
            
            update_data = {
                "is_maintenance": is_maintenance,
                "is_available": not is_maintenance,  # Not available when in maintenance
                "updated_at": datetime.utcnow()
            }
            
            if is_maintenance:
                update_data["maintenance_status"] = "in_maintenance"
                if maintenance_notes:
                    update_data["notes"] = maintenance_notes
            else:
                update_data["maintenance_status"] = "operational"
                update_data["last_maintenance_date"] = datetime.utcnow()
            
            capacity = await self.update(capacity_id, update_data)
            
            logger.info("Set maintenance mode",
                       capacity_id=str(capacity_id),
                       is_maintenance=is_maintenance)
            
            return capacity
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error setting maintenance mode", error=str(e))
            raise DatabaseError(f"Failed to set maintenance mode: {str(e)}")

    async def get_capacity_with_filters(
        self,
        tenant_id: str,
        resource_type: Optional[str] = None,
        date_filter: Optional[date] = None,
        availability: Optional[bool] = None,
        page: int = 1,
        page_size: int = 50
    ) -> tuple[List[ProductionCapacity], int]:
        """Get production capacity with filters and pagination"""
        try:
            filters = {"tenant_id": tenant_id}

            if resource_type:
                filters["resource_type"] = resource_type
            if date_filter:
                filters["date"] = date_filter
            if availability is not None:
                filters["is_available"] = availability

            # Get total count
            total_count = await self.count(filters)

            # Get paginated results
            offset = (page - 1) * page_size
            capacities = await self.get_multi(
                filters=filters,
                order_by="date",
                order_desc=True,
                limit=page_size,
                offset=offset
            )

            logger.info("Retrieved capacity with filters",
                       count=len(capacities),
                       total_count=total_count,
                       page=page,
                       page_size=page_size,
                       tenant_id=tenant_id)

            return capacities, total_count

        except Exception as e:
            logger.error("Error fetching capacity with filters", error=str(e))
            raise DatabaseError(f"Failed to fetch capacity with filters: {str(e)}")

    async def get_capacity_by_date(self, tenant_id: str, target_date: date) -> List[ProductionCapacity]:
        """Get all capacity entries for a specific date"""
        try:
            capacities = await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "date": target_date
                },
                order_by="start_time"
            )

            logger.info("Retrieved capacity by date",
                       count=len(capacities),
                       date=target_date.isoformat(),
                       tenant_id=tenant_id)

            return capacities

        except Exception as e:
            logger.error("Error fetching capacity by date", error=str(e))
            raise DatabaseError(f"Failed to fetch capacity by date: {str(e)}")