# services/inventory/app/schemas/inventory.py
"""
Pydantic schemas for inventory API requests and responses
"""

from typing import Optional, List, Dict, Any
from datetime import datetime
from decimal import Decimal
from uuid import UUID
from pydantic import BaseModel, Field, validator
from typing import Generic, TypeVar
from enum import Enum

from app.models.inventory import UnitOfMeasure, IngredientCategory, StockMovementType, ProductType, ProductCategory, ProductionStage

T = TypeVar('T')


# ===== BASE SCHEMAS =====

class InventoryBaseSchema(BaseModel):
    """Base schema for inventory models"""
    
    class Config:
        from_attributes = True
        use_enum_values = True
        json_encoders = {
            datetime: lambda v: v.isoformat() if v else None,
            Decimal: lambda v: float(v) if v else None
        }


# ===== INGREDIENT SCHEMAS =====

class IngredientCreate(InventoryBaseSchema):
    """Schema for creating ingredients and finished products"""
    name: str = Field(..., max_length=255, description="Product name")
    product_type: ProductType = Field(ProductType.INGREDIENT, description="Type of product (ingredient or finished_product)")
    sku: Optional[str] = Field(None, max_length=100, description="SKU code")
    barcode: Optional[str] = Field(None, max_length=50, description="Barcode")
    category: Optional[str] = Field(None, description="Product category (ingredient or finished product category)")
    subcategory: Optional[str] = Field(None, max_length=100, description="Subcategory")
    description: Optional[str] = Field(None, description="Ingredient description")
    brand: Optional[str] = Field(None, max_length=100, description="Brand name")
    unit_of_measure: UnitOfMeasure = Field(..., description="Unit of measure")
    package_size: Optional[float] = Field(None, gt=0, description="Package size")

    # Pricing
    # Note: average_cost is calculated automatically from purchases (not set on create)
    # All cost fields are optional - can be added later after onboarding
    standard_cost: Optional[Decimal] = Field(None, ge=0, description="Standard/target cost per unit for budgeting")

    # Stock management - all optional with sensible defaults for onboarding
    # These can be configured later based on actual usage patterns
    low_stock_threshold: Optional[float] = Field(None, ge=0, description="Low stock alert threshold")
    reorder_point: Optional[float] = Field(None, ge=0, description="Reorder point")
    reorder_quantity: Optional[float] = Field(None, gt=0, description="Default reorder quantity")
    max_stock_level: Optional[float] = Field(None, gt=0, description="Maximum stock level")
    
    # Shelf life (default value only - actual per batch)
    shelf_life_days: Optional[int] = Field(None, gt=0, description="Default shelf life in days")
    
    # Properties
    is_perishable: bool = Field(False, description="Is perishable")
    allergen_info: Optional[Dict[str, Any]] = Field(None, description="Allergen information")

    # NEW: Local production support
    produced_locally: bool = Field(False, description="If true, ingredient is produced in-house")
    recipe_id: Optional[str] = Field(None, description="Recipe ID for BOM explosion (if produced locally)")

    @validator('reorder_point')
    def validate_reorder_point(cls, v, values):
        # Only validate if both values are provided and not None
        low_stock = values.get('low_stock_threshold')
        if v is not None and low_stock is not None:
            try:
                if v <= low_stock:
                    raise ValueError('Reorder point must be greater than low stock threshold')
            except TypeError:
                # Skip validation if comparison fails due to type mismatch
                pass
        return v


class IngredientUpdate(InventoryBaseSchema):
    """Schema for updating ingredients and finished products"""
    name: Optional[str] = Field(None, max_length=255, description="Product name")
    product_type: Optional[ProductType] = Field(None, description="Type of product (ingredient or finished_product)")
    sku: Optional[str] = Field(None, max_length=100, description="SKU code")
    barcode: Optional[str] = Field(None, max_length=50, description="Barcode")
    category: Optional[str] = Field(None, description="Product category")
    subcategory: Optional[str] = Field(None, max_length=100, description="Subcategory")
    description: Optional[str] = Field(None, description="Ingredient description")
    brand: Optional[str] = Field(None, max_length=100, description="Brand name")
    unit_of_measure: Optional[UnitOfMeasure] = Field(None, description="Unit of measure")
    package_size: Optional[float] = Field(None, gt=0, description="Package size")
    
    # Pricing
    average_cost: Optional[Decimal] = Field(None, ge=0, description="Average cost per unit")
    standard_cost: Optional[Decimal] = Field(None, ge=0, description="Standard cost per unit")
    
    # Stock management
    low_stock_threshold: Optional[float] = Field(None, ge=0, description="Low stock alert threshold")
    reorder_point: Optional[float] = Field(None, ge=0, description="Reorder point")
    reorder_quantity: Optional[float] = Field(None, gt=0, description="Default reorder quantity")
    max_stock_level: Optional[float] = Field(None, gt=0, description="Maximum stock level")
    
    # Shelf life (default value only - actual per batch)
    shelf_life_days: Optional[int] = Field(None, gt=0, description="Default shelf life in days")
    
    # Properties
    is_active: Optional[bool] = Field(None, description="Is active")
    is_perishable: Optional[bool] = Field(None, description="Is perishable")
    allergen_info: Optional[Dict[str, Any]] = Field(None, description="Allergen information")

    # NEW: Local production support
    produced_locally: Optional[bool] = Field(None, description="If true, ingredient is produced in-house")
    recipe_id: Optional[str] = Field(None, description="Recipe ID for BOM explosion (if produced locally)")


class IngredientResponse(InventoryBaseSchema):
    """Schema for ingredient and finished product API responses"""
    id: str
    tenant_id: str
    name: str
    product_type: ProductType
    sku: Optional[str]
    barcode: Optional[str]
    category: Optional[str]  # Will be populated from ingredient_category or product_category
    subcategory: Optional[str]
    description: Optional[str]
    brand: Optional[str]
    unit_of_measure: UnitOfMeasure
    package_size: Optional[float]
    average_cost: Optional[float]
    last_purchase_price: Optional[float]
    standard_cost: Optional[float]
    low_stock_threshold: Optional[float]  # Now optional
    reorder_point: Optional[float]  # Now optional
    reorder_quantity: Optional[float]  # Now optional
    max_stock_level: Optional[float]
    shelf_life_days: Optional[int]  # Default value only
    is_active: bool
    is_perishable: bool
    allergen_info: Optional[Dict[str, Any]]

    # NEW: Local production support
    produced_locally: bool = False
    recipe_id: Optional[str] = None

    created_at: datetime
    updated_at: datetime
    created_by: Optional[str]

    # Computed fields
    current_stock: Optional[float] = None
    is_low_stock: Optional[bool] = None
    needs_reorder: Optional[bool] = None

    @validator('allergen_info', pre=True)
    def validate_allergen_info(cls, v):
        """Convert empty lists or lists to empty dict, handle None"""
        if v is None:
            return None
        if isinstance(v, list):
            # If it's an empty list, return None; if it's a non-empty list, convert to dict format
            return {"allergens": v} if v else None
        if isinstance(v, dict):
            return v
        # For any other type including invalid ones, return None
        return None


# ===== BULK INGREDIENT SCHEMAS =====

class BulkIngredientCreate(InventoryBaseSchema):
    """Schema for bulk creating ingredients"""
    ingredients: List[IngredientCreate] = Field(..., description="List of ingredients to create")


class BulkIngredientResult(InventoryBaseSchema):
    """Schema for individual result in bulk operation"""
    index: int = Field(..., description="Index of the ingredient in the original request")
    success: bool = Field(..., description="Whether the creation succeeded")
    ingredient: Optional[IngredientResponse] = Field(None, description="Created ingredient (if successful)")
    error: Optional[str] = Field(None, description="Error message (if failed)")


class BulkIngredientResponse(InventoryBaseSchema):
    """Schema for bulk ingredient creation response"""
    total_requested: int = Field(..., description="Total number of ingredients requested")
    total_created: int = Field(..., description="Number of ingredients successfully created")
    total_failed: int = Field(..., description="Number of ingredients that failed")
    results: List[BulkIngredientResult] = Field(..., description="Detailed results for each ingredient")
    transaction_id: str = Field(..., description="Transaction ID for audit trail")


# ===== STOCK SCHEMAS =====

class StockCreate(InventoryBaseSchema):
    """Schema for creating stock entries"""
    ingredient_id: str = Field(..., description="Ingredient ID")
    supplier_id: Optional[str] = Field(None, description="Supplier ID")
    batch_number: Optional[str] = Field(None, max_length=100, description="Batch number")
    lot_number: Optional[str] = Field(None, max_length=100, description="Lot number")
    supplier_batch_ref: Optional[str] = Field(None, max_length=100, description="Supplier batch reference")

    # Production stage tracking
    production_stage: ProductionStage = Field(default=ProductionStage.RAW_INGREDIENT, description="Production stage of the stock")
    transformation_reference: Optional[str] = Field(None, max_length=100, description="Transformation reference ID")

    current_quantity: float = Field(..., ge=0, description="Current quantity")
    received_date: Optional[datetime] = Field(None, description="Date received")
    expiration_date: Optional[datetime] = Field(None, description="Expiration date")
    best_before_date: Optional[datetime] = Field(None, description="Best before date")

    # Stage-specific expiration fields
    original_expiration_date: Optional[datetime] = Field(None, description="Original batch expiration (for par-baked items)")
    transformation_date: Optional[datetime] = Field(None, description="Date when product was transformed")
    final_expiration_date: Optional[datetime] = Field(None, description="Final expiration after transformation")

    unit_cost: Optional[Decimal] = Field(None, ge=0, description="Unit cost")
    storage_location: Optional[str] = Field(None, max_length=100, description="Storage location")
    warehouse_zone: Optional[str] = Field(None, max_length=50, description="Warehouse zone")
    shelf_position: Optional[str] = Field(None, max_length=50, description="Shelf position")

    quality_status: str = Field("good", description="Quality status")

    # Batch-specific storage requirements
    requires_refrigeration: bool = Field(False, description="Requires refrigeration")
    requires_freezing: bool = Field(False, description="Requires freezing")
    storage_temperature_min: Optional[float] = Field(None, description="Min storage temperature (°C)")
    storage_temperature_max: Optional[float] = Field(None, description="Max storage temperature (°C)")
    storage_humidity_max: Optional[float] = Field(None, ge=0, le=100, description="Max humidity (%)")
    shelf_life_days: Optional[int] = Field(None, gt=0, description="Batch-specific shelf life in days")
    storage_instructions: Optional[str] = Field(None, description="Batch-specific storage instructions")

    @validator('ingredient_id')
    def validate_ingredient_id(cls, v):
        """Validate ingredient_id is a valid UUID"""
        if not v:
            raise ValueError("ingredient_id is required")
        if isinstance(v, str):
            try:
                # Validate it's a proper UUID
                UUID(v)
                return v
            except (ValueError, AttributeError) as e:
                raise ValueError(f"ingredient_id must be a valid UUID string, got: {v}")
        return str(v)

    @validator('supplier_id')
    def validate_supplier_id(cls, v):
        """Convert empty string to None for optional UUID field"""
        if v == '' or (isinstance(v, str) and v.strip() == ''):
            return None
        return v

    @validator('storage_temperature_max')
    def validate_temperature_range(cls, v, values):
        # Only validate if both values are provided and not None
        min_temp = values.get('storage_temperature_min')
        if v is not None and min_temp is not None:
            try:
                if v <= min_temp:
                    raise ValueError('Max temperature must be greater than min temperature')
            except TypeError:
                # Skip validation if comparison fails due to type mismatch
                pass
        return v
    
class StockUpdate(InventoryBaseSchema):
    """Schema for updating stock entries"""
    supplier_id: Optional[str] = Field(None, description="Supplier ID")
    batch_number: Optional[str] = Field(None, max_length=100, description="Batch number")
    lot_number: Optional[str] = Field(None, max_length=100, description="Lot number")
    supplier_batch_ref: Optional[str] = Field(None, max_length=100, description="Supplier batch reference")

    # Production stage tracking
    production_stage: Optional[ProductionStage] = Field(None, description="Production stage of the stock")
    transformation_reference: Optional[str] = Field(None, max_length=100, description="Transformation reference ID")

    current_quantity: Optional[float] = Field(None, ge=0, description="Current quantity")
    reserved_quantity: Optional[float] = Field(None, ge=0, description="Reserved quantity")
    received_date: Optional[datetime] = Field(None, description="Date received")
    expiration_date: Optional[datetime] = Field(None, description="Expiration date")
    best_before_date: Optional[datetime] = Field(None, description="Best before date")

    # Stage-specific expiration fields
    original_expiration_date: Optional[datetime] = Field(None, description="Original batch expiration (for par-baked items)")
    transformation_date: Optional[datetime] = Field(None, description="Date when product was transformed")
    final_expiration_date: Optional[datetime] = Field(None, description="Final expiration after transformation")

    unit_cost: Optional[Decimal] = Field(None, ge=0, description="Unit cost")
    storage_location: Optional[str] = Field(None, max_length=100, description="Storage location")
    warehouse_zone: Optional[str] = Field(None, max_length=50, description="Warehouse zone")
    shelf_position: Optional[str] = Field(None, max_length=50, description="Shelf position")

    is_available: Optional[bool] = Field(None, description="Is available")
    quality_status: Optional[str] = Field(None, description="Quality status")

    # Batch-specific storage requirements
    requires_refrigeration: Optional[bool] = Field(None, description="Requires refrigeration")
    requires_freezing: Optional[bool] = Field(None, description="Requires freezing")
    storage_temperature_min: Optional[float] = Field(None, description="Min storage temperature (°C)")
    storage_temperature_max: Optional[float] = Field(None, description="Max storage temperature (°C)")
    storage_humidity_max: Optional[float] = Field(None, ge=0, le=100, description="Max humidity (%)")
    shelf_life_days: Optional[int] = Field(None, gt=0, description="Batch-specific shelf life in days")
    storage_instructions: Optional[str] = Field(None, description="Batch-specific storage instructions")

    @validator('supplier_id')
    def validate_supplier_id(cls, v):
        """Convert empty string to None for optional UUID field"""
        if v == '' or (isinstance(v, str) and v.strip() == ''):
            return None
        return v


class StockResponse(InventoryBaseSchema):
    """Schema for stock API responses"""
    id: str
    tenant_id: str
    ingredient_id: str
    supplier_id: Optional[str]
    batch_number: Optional[str]
    lot_number: Optional[str]
    supplier_batch_ref: Optional[str]

    # Production stage tracking
    production_stage: ProductionStage
    transformation_reference: Optional[str]

    current_quantity: float
    reserved_quantity: float
    available_quantity: float
    received_date: Optional[datetime]
    expiration_date: Optional[datetime]
    best_before_date: Optional[datetime]

    # Stage-specific expiration fields
    original_expiration_date: Optional[datetime]
    transformation_date: Optional[datetime]
    final_expiration_date: Optional[datetime]

    unit_cost: Optional[float]
    total_cost: Optional[float]
    storage_location: Optional[str]
    warehouse_zone: Optional[str]
    shelf_position: Optional[str]
    is_available: bool
    is_expired: bool
    quality_status: str

    # Batch-specific storage requirements
    requires_refrigeration: bool
    requires_freezing: bool
    storage_temperature_min: Optional[float]
    storage_temperature_max: Optional[float]
    storage_humidity_max: Optional[float]
    shelf_life_days: Optional[int]
    storage_instructions: Optional[str]
    created_at: datetime
    updated_at: datetime

    # Related data
    ingredient: Optional[IngredientResponse] = None


# ===== BULK STOCK SCHEMAS =====

class BulkStockCreate(InventoryBaseSchema):
    """Schema for bulk creating stock entries"""
    stocks: List[StockCreate] = Field(..., description="List of stock entries to create")


class BulkStockResult(InventoryBaseSchema):
    """Schema for individual result in bulk stock operation"""
    index: int = Field(..., description="Index of the stock in the original request")
    success: bool = Field(..., description="Whether the creation succeeded")
    stock: Optional[StockResponse] = Field(None, description="Created stock (if successful)")
    error: Optional[str] = Field(None, description="Error message (if failed)")


class BulkStockResponse(InventoryBaseSchema):
    """Schema for bulk stock creation response"""
    total_requested: int = Field(..., description="Total number of stock entries requested")
    total_created: int = Field(..., description="Number of stock entries successfully created")
    total_failed: int = Field(..., description="Number of stock entries that failed")
    results: List[BulkStockResult] = Field(..., description="Detailed results for each stock entry")
    transaction_id: str = Field(..., description="Transaction ID for audit trail")


# ===== STOCK MOVEMENT SCHEMAS =====

class StockMovementCreate(InventoryBaseSchema):
    """Schema for creating stock movements"""
    ingredient_id: str = Field(..., description="Ingredient ID")
    stock_id: Optional[str] = Field(None, description="Stock ID")
    movement_type: StockMovementType = Field(..., description="Movement type")
    quantity: float = Field(..., description="Quantity moved")

    unit_cost: Optional[Decimal] = Field(None, ge=0, description="Unit cost")
    reference_number: Optional[str] = Field(None, max_length=100, description="Reference number")
    supplier_id: Optional[str] = Field(None, description="Supplier ID")

    notes: Optional[str] = Field(None, description="Movement notes")
    reason_code: Optional[str] = Field(None, max_length=50, description="Reason code")
    movement_date: Optional[datetime] = Field(None, description="Movement date")

    @validator('ingredient_id')
    def validate_ingredient_id(cls, v):
        """Validate ingredient_id is a valid UUID"""
        if not v:
            raise ValueError("ingredient_id is required")
        if isinstance(v, str):
            try:
                # Validate it's a proper UUID
                UUID(v)
                return v
            except (ValueError, AttributeError) as e:
                raise ValueError(f"ingredient_id must be a valid UUID string, got: {v}")
        return str(v)


class StockMovementResponse(InventoryBaseSchema):
    """Schema for stock movement API responses"""
    id: str
    tenant_id: str
    ingredient_id: str
    stock_id: Optional[str]
    movement_type: StockMovementType
    quantity: float
    unit_cost: Optional[float]
    total_cost: Optional[float]
    quantity_before: Optional[float]
    quantity_after: Optional[float]
    reference_number: Optional[str]
    supplier_id: Optional[str]
    notes: Optional[str]
    reason_code: Optional[str]
    movement_date: datetime
    created_at: datetime
    created_by: Optional[str]
    
    # Related data
    ingredient: Optional[IngredientResponse] = None


# ===== PRODUCT TRANSFORMATION SCHEMAS =====

class ProductTransformationCreate(InventoryBaseSchema):
    """Schema for creating product transformations"""
    source_ingredient_id: str = Field(..., description="Source ingredient ID")
    target_ingredient_id: str = Field(..., description="Target ingredient ID")
    source_stage: ProductionStage = Field(..., description="Source production stage")
    target_stage: ProductionStage = Field(..., description="Target production stage")

    source_quantity: float = Field(..., gt=0, description="Input quantity")
    target_quantity: float = Field(..., gt=0, description="Output quantity")
    conversion_ratio: Optional[float] = Field(None, gt=0, description="Conversion ratio (auto-calculated if not provided)")

    # Expiration handling
    expiration_calculation_method: str = Field("days_from_transformation", description="How to calculate expiration")
    expiration_days_offset: Optional[int] = Field(1, description="Days from transformation date for expiration")

    # Process details
    process_notes: Optional[str] = Field(None, description="Process notes")
    target_batch_number: Optional[str] = Field(None, max_length=100, description="Target batch number")

    # Source stock selection (optional - if not provided, uses FIFO)
    source_stock_ids: Optional[List[str]] = Field(None, description="Specific source stock IDs to transform")

    @validator('source_ingredient_id', 'target_ingredient_id')
    def validate_ingredient_ids(cls, v):
        """Validate ingredient IDs are valid UUIDs"""
        if not v:
            raise ValueError("ingredient_id is required")
        if isinstance(v, str):
            try:
                # Validate it's a proper UUID
                UUID(v)
                return v
            except (ValueError, AttributeError) as e:
                raise ValueError(f"ingredient_id must be a valid UUID string, got: {v}")
        return str(v)


class ProductTransformationResponse(InventoryBaseSchema):
    """Schema for product transformation responses"""
    id: str
    tenant_id: str
    transformation_reference: str
    source_ingredient_id: str
    target_ingredient_id: str
    source_stage: ProductionStage
    target_stage: ProductionStage
    source_quantity: float
    target_quantity: float
    conversion_ratio: float
    expiration_calculation_method: str
    expiration_days_offset: Optional[int]
    transformation_date: datetime
    process_notes: Optional[str]
    performed_by: Optional[str]
    source_batch_numbers: Optional[str]
    target_batch_number: Optional[str]
    is_completed: bool
    is_reversed: bool
    created_at: datetime
    created_by: Optional[str]

    # Related data
    source_ingredient: Optional[IngredientResponse] = None
    target_ingredient: Optional[IngredientResponse] = None


# ===== ALERT SCHEMAS =====

class StockAlertResponse(InventoryBaseSchema):
    """Schema for stock alert API responses"""
    id: str
    tenant_id: str
    ingredient_id: str
    stock_id: Optional[str]
    alert_type: str
    severity: str
    title: str
    message: str
    current_quantity: Optional[float]
    threshold_value: Optional[float]
    expiration_date: Optional[datetime]
    is_active: bool
    is_acknowledged: bool
    acknowledged_by: Optional[str]
    acknowledged_at: Optional[datetime]
    is_resolved: bool
    resolved_by: Optional[str]
    resolved_at: Optional[datetime]
    resolution_notes: Optional[str]
    created_at: datetime
    updated_at: datetime
    
    # Related data
    ingredient: Optional[IngredientResponse] = None


# ===== DASHBOARD AND SUMMARY SCHEMAS =====

class InventorySummary(InventoryBaseSchema):
    """Inventory dashboard summary"""
    total_ingredients: int
    total_stock_value: float
    low_stock_alerts: int
    expiring_soon_items: int
    expired_items: int
    out_of_stock_items: int
    
    # By category
    stock_by_category: Dict[str, Dict[str, Any]]
    
    # Recent activity
    recent_movements: int
    recent_purchases: int
    recent_waste: int


class StockLevelSummary(InventoryBaseSchema):
    """Stock level summary for an ingredient"""
    ingredient_id: str
    ingredient_name: str
    unit_of_measure: str
    total_quantity: float
    available_quantity: float
    reserved_quantity: float
    
    # Status indicators
    is_low_stock: bool
    needs_reorder: bool
    has_expired_stock: bool
    
    # Batch information
    total_batches: int
    oldest_batch_date: Optional[datetime]
    newest_batch_date: Optional[datetime]
    next_expiration_date: Optional[datetime]
    
    # Cost information
    average_unit_cost: Optional[float]
    total_stock_value: Optional[float]


# ===== REQUEST/RESPONSE WRAPPER SCHEMAS =====

class PaginatedResponse(BaseModel, Generic[T]):
    """Generic paginated response"""
    items: List[T]
    total: int
    page: int
    size: int
    pages: int
    
    class Config:
        from_attributes = True


class InventoryFilter(BaseModel):
    """Inventory filtering parameters"""
    category: Optional[IngredientCategory] = None
    is_active: Optional[bool] = None
    is_low_stock: Optional[bool] = None
    needs_reorder: Optional[bool] = None
    search: Optional[str] = None
    
    
class StockFilter(BaseModel):
    """Stock filtering parameters"""
    ingredient_id: Optional[str] = None
    production_stage: Optional[ProductionStage] = None
    transformation_reference: Optional[str] = None
    is_available: Optional[bool] = None
    is_expired: Optional[bool] = None
    expiring_within_days: Optional[int] = None
    storage_location: Optional[str] = None
    quality_status: Optional[str] = None


# Type aliases for paginated responses
IngredientListResponse = PaginatedResponse[IngredientResponse]
StockListResponse = PaginatedResponse[StockResponse]
StockMovementListResponse = PaginatedResponse[StockMovementResponse]
StockAlertListResponse = PaginatedResponse[StockAlertResponse]