"""
Production Batch Repository
Repository for production batch operations
"""

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

from .base import ProductionBaseRepository
from app.models.production import ProductionBatch, ProductionStatus, ProductionPriority
from shared.database.exceptions import DatabaseError, ValidationError
from shared.database.transactions import transactional
from shared.utils.batch_generator import BatchCountProvider, BatchNumberGenerator, create_fallback_batch_number

logger = structlog.get_logger()


class ProductionBatchRepository(ProductionBaseRepository, BatchCountProvider):
    """Repository for production batch operations"""
    
    def __init__(self, session: AsyncSession, cache_ttl: Optional[int] = 300):
        # Production batches are dynamic, short cache time (5 minutes)
        super().__init__(ProductionBatch, session, cache_ttl)
    
    async def create_batch(self, batch_data: Dict[str, Any]) -> ProductionBatch:
        """Create a new production batch with validation"""
        try:
            # Validate batch data
            validation_result = self._validate_production_data(
                batch_data, 
                ["tenant_id", "product_id", "product_name", "planned_start_time", 
                 "planned_end_time", "planned_quantity", "planned_duration_minutes"]
            )
            
            if not validation_result["is_valid"]:
                raise ValidationError(f"Invalid batch data: {validation_result['errors']}")
            
            # Generate batch number if not provided
            if "batch_number" not in batch_data or not batch_data["batch_number"]:
                try:
                    batch_generator = BatchNumberGenerator(self)
                    batch_data["batch_number"] = await batch_generator.generate_batch_number(
                        tenant_id=batch_data["tenant_id"],
                        prefix="PROD"
                    )
                    logger.info("Generated production batch number", batch_number=batch_data["batch_number"])
                except Exception as e:
                    # Fallback to a simple batch number if generation fails
                    batch_data["batch_number"] = create_fallback_batch_number("PROD")
                    logger.warning("Used fallback batch number", batch_number=batch_data["batch_number"], error=str(e))
            
            # Set default values
            if "status" not in batch_data:
                batch_data["status"] = ProductionStatus.PENDING
            if "priority" not in batch_data:
                batch_data["priority"] = ProductionPriority.MEDIUM
            if "is_rush_order" not in batch_data:
                batch_data["is_rush_order"] = False
            if "is_special_recipe" not in batch_data:
                batch_data["is_special_recipe"] = False
            
            # Check for duplicate batch number
            if await self.check_duplicate(batch_data["tenant_id"], {"batch_number": batch_data["batch_number"]}):
                raise ValidationError(f"Batch number {batch_data['batch_number']} already exists")
            
            # Create batch
            batch = await self.create(batch_data)
            
            logger.info("Production batch created successfully",
                       batch_id=str(batch.id),
                       batch_number=batch.batch_number,
                       tenant_id=str(batch.tenant_id))
            
            return batch
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error creating production batch", error=str(e))
            raise DatabaseError(f"Failed to create production batch: {str(e)}")
    
    async def get_active_batches(self, tenant_id: str) -> List[ProductionBatch]:
        """Get active production batches for a tenant"""
        try:
            active_statuses = [
                ProductionStatus.PENDING,
                ProductionStatus.IN_PROGRESS,
                ProductionStatus.QUALITY_CHECK,
                ProductionStatus.ON_HOLD
            ]
            
            batches = await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "status__in": active_statuses
                },
                order_by="planned_start_time"
            )
            
            logger.info("Retrieved active production batches",
                       count=len(batches),
                       tenant_id=tenant_id)
            
            return batches
            
        except Exception as e:
            logger.error("Error fetching active batches", error=str(e))
            raise DatabaseError(f"Failed to fetch active batches: {str(e)}")
    
    async def get_batches_by_date_range(
        self, 
        tenant_id: str, 
        start_date: date, 
        end_date: date,
        status: Optional[ProductionStatus] = None
    ) -> List[ProductionBatch]:
        """Get production batches within a date range"""
        try:
            start_datetime = datetime.combine(start_date, datetime.min.time())
            end_datetime = datetime.combine(end_date, datetime.max.time())
            
            filters = {
                "tenant_id": tenant_id,
                "planned_start_time__gte": start_datetime,
                "planned_start_time__lte": end_datetime
            }
            
            if status:
                filters["status"] = status
            
            batches = await self.get_multi(
                filters=filters,
                order_by="planned_start_time"
            )
            
            logger.info("Retrieved batches by date range",
                       count=len(batches),
                       start_date=start_date.isoformat(),
                       end_date=end_date.isoformat(),
                       tenant_id=tenant_id)
            
            return batches
            
        except Exception as e:
            logger.error("Error fetching batches by date range", error=str(e))
            raise DatabaseError(f"Failed to fetch batches by date range: {str(e)}")
    
    async def get_batches_by_product(
        self, 
        tenant_id: str, 
        product_id: str,
        limit: int = 50
    ) -> List[ProductionBatch]:
        """Get production batches for a specific product"""
        try:
            batches = await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "product_id": product_id
                },
                order_by="created_at",
                order_desc=True,
                limit=limit
            )
            
            logger.info("Retrieved batches by product",
                       count=len(batches),
                       product_id=product_id,
                       tenant_id=tenant_id)
            
            return batches
            
        except Exception as e:
            logger.error("Error fetching batches by product", error=str(e))
            raise DatabaseError(f"Failed to fetch batches by product: {str(e)}")
    
    async def update_batch_status(
        self, 
        batch_id: UUID, 
        status: ProductionStatus,
        actual_quantity: Optional[float] = None,
        notes: Optional[str] = None
    ) -> ProductionBatch:
        """Update production batch status"""
        try:
            batch = await self.get(batch_id)
            if not batch:
                raise ValidationError(f"Batch {batch_id} not found")
            
            update_data = {
                "status": status,
                "updated_at": datetime.utcnow()
            }
            
            # Set completion time if completed
            if status == ProductionStatus.COMPLETED:
                update_data["completed_at"] = datetime.utcnow()
                update_data["actual_end_time"] = datetime.utcnow()
                
                if actual_quantity is not None:
                    update_data["actual_quantity"] = actual_quantity
                    # Calculate yield percentage
                    if batch.planned_quantity > 0:
                        update_data["yield_percentage"] = (actual_quantity / batch.planned_quantity) * 100
            
            # Set start time if starting production
            if status == ProductionStatus.IN_PROGRESS and not batch.actual_start_time:
                update_data["actual_start_time"] = datetime.utcnow()
            
            # Add notes
            if notes:
                if status == ProductionStatus.CANCELLED:
                    update_data["cancellation_reason"] = notes
                elif status == ProductionStatus.ON_HOLD:
                    update_data["delay_reason"] = notes
                else:
                    update_data["production_notes"] = notes
            
            batch = await self.update(batch_id, update_data)
            
            logger.info("Updated batch status",
                       batch_id=str(batch_id),
                       new_status=status.value,
                       actual_quantity=actual_quantity)
            
            return batch
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Error updating batch status", error=str(e))
            raise DatabaseError(f"Failed to update batch status: {str(e)}")
    
    async def get_production_metrics(
        self, 
        tenant_id: str, 
        start_date: date, 
        end_date: date
    ) -> Dict[str, Any]:
        """Get production metrics for a tenant and date range"""
        try:
            batches = await self.get_batches_by_date_range(tenant_id, start_date, end_date)
            
            total_batches = len(batches)
            completed_batches = len([b for b in batches if b.status == ProductionStatus.COMPLETED])
            in_progress_batches = len([b for b in batches if b.status == ProductionStatus.IN_PROGRESS])
            cancelled_batches = len([b for b in batches if b.status == ProductionStatus.CANCELLED])
            
            # Calculate totals
            total_planned_quantity = sum(b.planned_quantity for b in batches)
            total_actual_quantity = sum(b.actual_quantity or 0 for b in batches if b.actual_quantity)
            
            # Calculate average yield
            completed_with_yield = [b for b in batches if b.yield_percentage is not None]
            avg_yield = (
                sum(b.yield_percentage for b in completed_with_yield) / len(completed_with_yield)
                if completed_with_yield else 0
            )
            
            # Calculate on-time completion rate
            on_time_completed = len([
                b for b in batches 
                if b.status == ProductionStatus.COMPLETED 
                and b.actual_end_time 
                and b.planned_end_time
                and b.actual_end_time <= b.planned_end_time
            ])
            
            on_time_rate = (on_time_completed / completed_batches * 100) if completed_batches > 0 else 0
            
            return {
                "period_start": start_date.isoformat(),
                "period_end": end_date.isoformat(),
                "total_batches": total_batches,
                "completed_batches": completed_batches,
                "in_progress_batches": in_progress_batches,
                "cancelled_batches": cancelled_batches,
                "completion_rate": (completed_batches / total_batches * 100) if total_batches > 0 else 0,
                "total_planned_quantity": total_planned_quantity,
                "total_actual_quantity": total_actual_quantity,
                "average_yield_percentage": round(avg_yield, 2),
                "on_time_completion_rate": round(on_time_rate, 2),
                "tenant_id": tenant_id
            }
            
        except Exception as e:
            logger.error("Error calculating production metrics", error=str(e))
            raise DatabaseError(f"Failed to calculate production metrics: {str(e)}")
    
    async def get_urgent_batches(self, tenant_id: str, hours_ahead: int = 4) -> List[ProductionBatch]:
        """Get batches that need to start within the specified hours"""
        try:
            cutoff_time = datetime.utcnow() + timedelta(hours=hours_ahead)
            
            batches = await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "status": ProductionStatus.PENDING,
                    "planned_start_time__lte": cutoff_time
                },
                order_by="planned_start_time"
            )
            
            logger.info("Retrieved urgent batches",
                       count=len(batches),
                       hours_ahead=hours_ahead,
                       tenant_id=tenant_id)
            
            return batches
            
        except Exception as e:
            logger.error("Error fetching urgent batches", error=str(e))
            raise DatabaseError(f"Failed to fetch urgent batches: {str(e)}")
    
    async def get_daily_batch_count(
        self,
        tenant_id: str,
        date_start: datetime,
        date_end: datetime,
        prefix: Optional[str] = None
    ) -> int:
        """Get the count of production batches created today for the given tenant"""
        try:
            conditions = {
                "tenant_id": tenant_id,
                "created_at__gte": date_start,
                "created_at__lte": date_end
            }

            if prefix:
                # Filter by batch numbers that start with the given prefix
                filters_list = [
                    and_(
                        ProductionBatch.tenant_id == tenant_id,
                        ProductionBatch.created_at >= date_start,
                        ProductionBatch.created_at <= date_end,
                        ProductionBatch.batch_number.like(f"{prefix}-%")
                    )
                ]
                result = await self.session.execute(
                    select(func.count(ProductionBatch.id)).where(and_(*filters_list))
                )
            else:
                batches = await self.get_multi(filters=conditions)
                result_count = len(batches)
                return result_count

            count = result.scalar() or 0

            logger.debug(
                "Retrieved daily production batch count",
                tenant_id=tenant_id,
                prefix=prefix,
                count=count,
                date_start=date_start,
                date_end=date_end
            )

            return count

        except Exception as e:
            logger.error(
                "Failed to get daily production batch count",
                error=str(e),
                tenant_id=tenant_id,
                prefix=prefix
            )
            raise