"""
Production Schedule Repository
Repository for production schedule 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 ProductionSchedule
from shared.database.exceptions import DatabaseError, ValidationError


logger = structlog.get_logger()


class ProductionScheduleRepository(ProductionBaseRepository):
    """Repository for production schedule operations"""
    
    def __init__(self, session: AsyncSession, cache_ttl: Optional[int] = 600):
        # Schedules are more stable, medium cache time (10 minutes)
        super().__init__(ProductionSchedule, session, cache_ttl)
    
    async def create_schedule(self, schedule_data: Dict[str, Any]) -> ProductionSchedule:
        """Create a new production schedule with validation"""
        try:
            # Validate schedule data
            validation_result = self._validate_production_data(
                schedule_data, 
                ["tenant_id", "schedule_date", "shift_start", "shift_end", 
                 "total_capacity_hours", "planned_capacity_hours", "staff_count"]
            )
            
            if not validation_result["is_valid"]:
                raise ValidationError(f"Invalid schedule data: {validation_result['errors']}")
            
            # Set default values
            if "is_finalized" not in schedule_data:
                schedule_data["is_finalized"] = False
            if "is_active" not in schedule_data:
                schedule_data["is_active"] = True
            if "overtime_hours" not in schedule_data:
                schedule_data["overtime_hours"] = 0.0
            
            # Validate date uniqueness
            existing_schedule = await self.get_schedule_by_date(
                schedule_data["tenant_id"], 
                schedule_data["schedule_date"]
            )
            if existing_schedule:
                raise ValidationError(f"Schedule for date {schedule_data['schedule_date']} already exists")
            
            # Create schedule
            schedule = await self.create(schedule_data)
            
            logger.info("Production schedule created successfully",
                       schedule_id=str(schedule.id),
                       schedule_date=schedule.schedule_date.isoformat(),
                       tenant_id=str(schedule.tenant_id))
            
            return schedule
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error creating production schedule", error=str(e))
            raise DatabaseError(f"Failed to create production schedule: {str(e)}")
    
    async def get_schedule_by_date(
        self, 
        tenant_id: str, 
        schedule_date: date
    ) -> Optional[ProductionSchedule]:
        """Get production schedule for a specific date"""
        try:
            schedules = await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "schedule_date": schedule_date
                },
                limit=1
            )
            
            schedule = schedules[0] if schedules else None
            
            if schedule:
                logger.info("Retrieved production schedule by date",
                           schedule_id=str(schedule.id),
                           schedule_date=schedule_date.isoformat(),
                           tenant_id=tenant_id)
            
            return schedule
            
        except Exception as e:
            logger.error("Error fetching schedule by date", error=str(e))
            raise DatabaseError(f"Failed to fetch schedule by date: {str(e)}")
    
    async def get_schedules_by_date_range(
        self, 
        tenant_id: str, 
        start_date: date, 
        end_date: date
    ) -> List[ProductionSchedule]:
        """Get production schedules within a date range"""
        try:
            schedules = await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "schedule_date__gte": start_date,
                    "schedule_date__lte": end_date
                },
                order_by="schedule_date"
            )
            
            logger.info("Retrieved schedules by date range",
                       count=len(schedules),
                       start_date=start_date.isoformat(),
                       end_date=end_date.isoformat(),
                       tenant_id=tenant_id)
            
            return schedules
            
        except Exception as e:
            logger.error("Error fetching schedules by date range", error=str(e))
            raise DatabaseError(f"Failed to fetch schedules by date range: {str(e)}")
    
    async def get_active_schedules(self, tenant_id: str) -> List[ProductionSchedule]:
        """Get active production schedules for a tenant"""
        try:
            schedules = await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "is_active": True
                },
                order_by="schedule_date"
            )
            
            logger.info("Retrieved active production schedules",
                       count=len(schedules),
                       tenant_id=tenant_id)
            
            return schedules
            
        except Exception as e:
            logger.error("Error fetching active schedules", error=str(e))
            raise DatabaseError(f"Failed to fetch active schedules: {str(e)}")
    
    async def finalize_schedule(
        self, 
        schedule_id: UUID, 
        finalized_by: str
    ) -> ProductionSchedule:
        """Finalize a production schedule"""
        try:
            schedule = await self.get(schedule_id)
            if not schedule:
                raise ValidationError(f"Schedule {schedule_id} not found")
            
            if schedule.is_finalized:
                raise ValidationError("Schedule is already finalized")
            
            update_data = {
                "is_finalized": True,
                "finalized_at": datetime.utcnow(),
                "updated_at": datetime.utcnow()
            }
            
            schedule = await self.update(schedule_id, update_data)
            
            logger.info("Production schedule finalized",
                       schedule_id=str(schedule_id),
                       finalized_by=finalized_by)
            
            return schedule
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error finalizing schedule", error=str(e))
            raise DatabaseError(f"Failed to finalize schedule: {str(e)}")
    
    async def update_schedule_metrics(
        self, 
        schedule_id: UUID, 
        metrics: Dict[str, Any]
    ) -> ProductionSchedule:
        """Update production schedule metrics"""
        try:
            schedule = await self.get(schedule_id)
            if not schedule:
                raise ValidationError(f"Schedule {schedule_id} not found")
            
            # Validate metrics
            valid_metrics = [
                "actual_capacity_hours", "total_batches_completed", 
                "total_quantity_produced", "efficiency_percentage",
                "utilization_percentage", "on_time_completion_rate"
            ]
            
            update_data = {"updated_at": datetime.utcnow()}
            
            for metric, value in metrics.items():
                if metric in valid_metrics:
                    update_data[metric] = value
            
            schedule = await self.update(schedule_id, update_data)
            
            logger.info("Updated schedule metrics",
                       schedule_id=str(schedule_id),
                       metrics=list(metrics.keys()))
            
            return schedule
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error updating schedule metrics", error=str(e))
            raise DatabaseError(f"Failed to update schedule metrics: {str(e)}")
    
    async def get_schedule_performance_summary(
        self, 
        tenant_id: str, 
        start_date: date, 
        end_date: date
    ) -> Dict[str, Any]:
        """Get schedule performance summary for a date range"""
        try:
            schedules = await self.get_schedules_by_date_range(tenant_id, start_date, end_date)
            
            total_schedules = len(schedules)
            finalized_schedules = len([s for s in schedules if s.is_finalized])
            
            # Calculate averages
            total_planned_hours = sum(s.planned_capacity_hours for s in schedules)
            total_actual_hours = sum(s.actual_capacity_hours or 0 for s in schedules)
            total_overtime = sum(s.overtime_hours or 0 for s in schedules)
            
            # Calculate efficiency metrics
            schedules_with_efficiency = [s for s in schedules if s.efficiency_percentage is not None]
            avg_efficiency = (
                sum(s.efficiency_percentage for s in schedules_with_efficiency) / len(schedules_with_efficiency)
                if schedules_with_efficiency else 0
            )
            
            schedules_with_utilization = [s for s in schedules if s.utilization_percentage is not None]
            avg_utilization = (
                sum(s.utilization_percentage for s in schedules_with_utilization) / len(schedules_with_utilization)
                if schedules_with_utilization else 0
            )
            
            return {
                "period_start": start_date.isoformat(),
                "period_end": end_date.isoformat(),
                "total_schedules": total_schedules,
                "finalized_schedules": finalized_schedules,
                "finalization_rate": (finalized_schedules / total_schedules * 100) if total_schedules > 0 else 0,
                "total_planned_hours": total_planned_hours,
                "total_actual_hours": total_actual_hours,
                "total_overtime_hours": total_overtime,
                "capacity_utilization": (total_actual_hours / total_planned_hours * 100) if total_planned_hours > 0 else 0,
                "average_efficiency_percentage": round(avg_efficiency, 2),
                "average_utilization_percentage": round(avg_utilization, 2),
                "tenant_id": tenant_id
            }
            
        except Exception as e:
            logger.error("Error calculating schedule performance summary", error=str(e))
            raise DatabaseError(f"Failed to calculate schedule performance summary: {str(e)}")

    async def get_schedules_with_filters(
        self,
        tenant_id: str,
        start_date: Optional[date] = None,
        end_date: Optional[date] = None,
        is_finalized: Optional[bool] = None,
        page: int = 1,
        page_size: int = 50
    ) -> tuple[List[ProductionSchedule], int]:
        """Get production schedules with filters and pagination"""
        try:
            filters = {"tenant_id": tenant_id}

            if start_date:
                filters["schedule_date__gte"] = start_date
            if end_date:
                filters["schedule_date__lte"] = end_date
            if is_finalized is not None:
                filters["is_finalized"] = is_finalized

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

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

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

            return schedules, total_count

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

    async def update_schedule(self, schedule_id: UUID, update_data: Dict[str, Any]) -> ProductionSchedule:
        """Update a production schedule"""
        try:
            schedule = await self.get(schedule_id)
            if not schedule:
                raise ValidationError(f"Schedule {schedule_id} not found")

            # Add updated timestamp
            update_data["updated_at"] = datetime.utcnow()

            # Update the schedule
            schedule = await self.update(schedule_id, update_data)

            logger.info("Updated production schedule",
                       schedule_id=str(schedule_id),
                       update_fields=list(update_data.keys()))

            return schedule

        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error updating production schedule", error=str(e))
            raise DatabaseError(f"Failed to update production schedule: {str(e)}")

    async def delete_schedule(self, schedule_id: UUID) -> bool:
        """Delete a production schedule"""
        try:
            schedule = await self.get(schedule_id)
            if not schedule:
                raise ValidationError(f"Schedule {schedule_id} not found")

            # Check if schedule can be deleted (not finalized)
            if schedule.is_finalized:
                raise ValidationError("Cannot delete finalized schedule")

            success = await self.delete(schedule_id)

            logger.info("Deleted production schedule",
                       schedule_id=str(schedule_id),
                       success=success)

            return success

        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error deleting production schedule", error=str(e))
            raise DatabaseError(f"Failed to delete production schedule: {str(e)}")

    async def get_todays_schedule(self, tenant_id: str) -> Optional[ProductionSchedule]:
        """Get today's production schedule for a tenant"""
        try:
            today = datetime.utcnow().date()
            return await self.get_schedule_by_date(tenant_id, today)

        except Exception as e:
            logger.error("Error fetching today's schedule", error=str(e))
            raise DatabaseError(f"Failed to fetch today's schedule: {str(e)}")

    async def get_all_schedules_for_tenant(self, tenant_id: UUID) -> List[ProductionSchedule]:
        """Get all production schedules for a specific tenant"""
        try:
            from sqlalchemy import select
            from app.models.production import ProductionSchedule

            result = await self.session.execute(
                select(ProductionSchedule).where(
                    ProductionSchedule.tenant_id == tenant_id
                )
            )
            schedules = result.scalars().all()

            logger.info("Retrieved all schedules for tenant",
                       tenant_id=str(tenant_id),
                       count=len(schedules))

            return list(schedules)

        except Exception as e:
            logger.error("Error fetching all tenant schedules", error=str(e), tenant_id=str(tenant_id))
            raise DatabaseError(f"Failed to fetch all tenant schedules: {str(e)}")

    async def archive_schedule(self, schedule: ProductionSchedule) -> None:
        """Archive a production schedule"""
        try:
            schedule.archived = True
            await self.session.commit()
            logger.info("Archived schedule", schedule_id=str(schedule.id))

        except Exception as e:
            logger.error("Error archiving schedule", error=str(e), schedule_id=str(schedule.id))
            raise DatabaseError(f"Failed to archive schedule: {str(e)}")

    async def cancel_schedule(self, schedule: ProductionSchedule, reason: str = None) -> None:
        """Cancel a production schedule"""
        try:
            schedule.status = "cancelled"
            if reason:
                schedule.notes = (schedule.notes or "") + f"\n{reason}"
            await self.session.commit()
            logger.info("Cancelled schedule", schedule_id=str(schedule.id))

        except Exception as e:
            logger.error("Error cancelling schedule", error=str(e), schedule_id=str(schedule.id))
            raise DatabaseError(f"Failed to cancel schedule: {str(e)}")