# services/inventory/app/services/inventory_service.py
"""
Inventory Service - Business Logic Layer
"""

from typing import List, Optional, Dict, Any, Tuple
from uuid import UUID
from datetime import datetime, timedelta
import structlog
import uuid

from app.models.inventory import Ingredient, Stock, StockMovement, StockAlert, StockMovementType
from app.repositories.ingredient_repository import IngredientRepository
from app.repositories.stock_repository import StockRepository
from app.repositories.stock_movement_repository import StockMovementRepository
from app.schemas.inventory import (
    IngredientCreate, IngredientUpdate, IngredientResponse,
    StockCreate, StockUpdate, StockResponse,
    StockMovementCreate, StockMovementResponse,
    InventorySummary, StockLevelSummary
)
from app.core.database import get_db_transaction
from shared.database.exceptions import DatabaseError
from shared.utils.batch_generator import BatchNumberGenerator, create_fallback_batch_number
from app.utils.cache import delete_cached, make_cache_key

logger = structlog.get_logger()


class InventoryService:
    """Service layer for inventory operations"""

    def __init__(self):
        pass

    # ===== INGREDIENT MANAGEMENT =====

    async def create_ingredient(
        self, 
        ingredient_data: IngredientCreate, 
        tenant_id: UUID,
        user_id: Optional[UUID] = None
    ) -> IngredientResponse:
        """Create a new ingredient with business validation"""
        try:
            # Business validation
            await self._validate_ingredient_data(ingredient_data, tenant_id)
            
            async with get_db_transaction() as db:
                repository = IngredientRepository(db)

                # Auto-generate SKU if not provided
                if not ingredient_data.sku:
                    ingredient_data.sku = await self._generate_sku(db, tenant_id, ingredient_data.name)
                    logger.info("Auto-generated SKU", sku=ingredient_data.sku, name=ingredient_data.name)

                # Check for duplicates
                if ingredient_data.sku:
                    existing = await repository.get_by_sku(tenant_id, ingredient_data.sku)
                    if existing:
                        raise ValueError(f"Ingredient with SKU '{ingredient_data.sku}' already exists")

                if ingredient_data.barcode:
                    existing = await repository.get_by_barcode(tenant_id, ingredient_data.barcode)
                    if existing:
                        raise ValueError(f"Ingredient with barcode '{ingredient_data.barcode}' already exists")
                
                # Create ingredient
                ingredient = await repository.create_ingredient(ingredient_data, tenant_id)
                
                # Convert to response schema
                ingredient_dict = ingredient.to_dict()
                
                # Map category field based on product type
                if ingredient.product_type and ingredient.product_type.value == 'finished_product':
                    ingredient_dict['category'] = ingredient.product_category.value if ingredient.product_category else None
                else:
                    ingredient_dict['category'] = ingredient.ingredient_category.value if ingredient.ingredient_category else None
                
                response = IngredientResponse(**ingredient_dict)
                
                # Add computed fields
                response.current_stock = 0.0
                response.is_low_stock = True
                response.needs_reorder = True
                
                logger.info("Ingredient created successfully", ingredient_id=ingredient.id, name=ingredient.name)
                return response
                
        except Exception as e:
            logger.error("Failed to create ingredient", error=str(e), tenant_id=tenant_id)
            raise

    async def get_ingredient(self, ingredient_id: UUID, tenant_id: UUID) -> Optional[IngredientResponse]:
        """Get ingredient by ID with stock information"""
        try:
            async with get_db_transaction() as db:
                ingredient_repo = IngredientRepository(db)
                stock_repo = StockRepository(db)
                
                ingredient = await ingredient_repo.get_by_id(ingredient_id)
                if not ingredient or ingredient.tenant_id != tenant_id:
                    return None
                
                # Get current stock levels
                stock_totals = await stock_repo.get_total_stock_by_ingredient(tenant_id, ingredient_id)
                
                # Convert to response schema
                ingredient_dict = ingredient.to_dict()
                
                # Map category field based on product type
                if ingredient.product_type and ingredient.product_type.value == 'finished_product':
                    ingredient_dict['category'] = ingredient.product_category.value if ingredient.product_category else None
                else:
                    ingredient_dict['category'] = ingredient.ingredient_category.value if ingredient.ingredient_category else None
                
                response = IngredientResponse(**ingredient_dict)
                response.current_stock = stock_totals['total_available']
                response.is_low_stock = (
                    stock_totals['total_available'] <= ingredient.low_stock_threshold
                    if ingredient.low_stock_threshold is not None else False
                )
                response.needs_reorder = (
                    stock_totals['total_available'] <= ingredient.reorder_point
                    if ingredient.reorder_point is not None else False
                )
                
                return response
                
        except Exception as e:
            logger.error("Failed to get ingredient", error=str(e), ingredient_id=ingredient_id)
            raise

    async def update_ingredient(
        self, 
        ingredient_id: UUID, 
        ingredient_data: IngredientUpdate, 
        tenant_id: UUID
    ) -> Optional[IngredientResponse]:
        """Update ingredient with business validation"""
        try:
            async with get_db_transaction() as db:
                ingredient_repo = IngredientRepository(db)
                stock_repo = StockRepository(db)
                
                # Check if ingredient exists and belongs to tenant
                existing = await ingredient_repo.get_by_id(ingredient_id)
                if not existing or existing.tenant_id != tenant_id:
                    return None
                
                # Validate unique constraints
                if ingredient_data.sku and ingredient_data.sku != existing.sku:
                    sku_check = await ingredient_repo.get_by_sku(tenant_id, ingredient_data.sku)
                    if sku_check and sku_check.id != ingredient_id:
                        raise ValueError(f"Ingredient with SKU '{ingredient_data.sku}' already exists")
                
                if ingredient_data.barcode and ingredient_data.barcode != existing.barcode:
                    barcode_check = await ingredient_repo.get_by_barcode(tenant_id, ingredient_data.barcode)
                    if barcode_check and barcode_check.id != ingredient_id:
                        raise ValueError(f"Ingredient with barcode '{ingredient_data.barcode}' already exists")
                
                # Update ingredient
                updated_ingredient = await ingredient_repo.update(ingredient_id, ingredient_data)
                if not updated_ingredient:
                    return None
                
                # Get current stock levels
                stock_totals = await stock_repo.get_total_stock_by_ingredient(tenant_id, ingredient_id)
                
                # Convert to response schema
                ingredient_dict = updated_ingredient.to_dict()
                
                # Map category field based on product type
                if updated_ingredient.product_type and updated_ingredient.product_type.value == 'finished_product':
                    ingredient_dict['category'] = updated_ingredient.product_category.value if updated_ingredient.product_category else None
                else:
                    ingredient_dict['category'] = updated_ingredient.ingredient_category.value if updated_ingredient.ingredient_category else None
                
                response = IngredientResponse(**ingredient_dict)
                response.current_stock = stock_totals['total_available']
                response.is_low_stock = (
                    stock_totals['total_available'] <= updated_ingredient.low_stock_threshold
                    if updated_ingredient.low_stock_threshold is not None else False
                )
                response.needs_reorder = (
                    stock_totals['total_available'] <= updated_ingredient.reorder_point
                    if updated_ingredient.reorder_point is not None else False
                )
                
                return response
                
        except Exception as e:
            logger.error("Failed to update ingredient", error=str(e), ingredient_id=ingredient_id)
            raise

    async def get_ingredients(
        self, 
        tenant_id: UUID,
        skip: int = 0,
        limit: int = 100,
        filters: Optional[Dict[str, Any]] = None
    ) -> List[IngredientResponse]:
        """Get ingredients with filtering and stock information"""
        try:
            async with get_db_transaction() as db:
                ingredient_repo = IngredientRepository(db)
                stock_repo = StockRepository(db)
                
                # Get ingredients
                ingredients = await ingredient_repo.get_ingredients_by_tenant(
                    tenant_id, skip, limit, filters
                )
                
                responses = []
                for ingredient in ingredients:
                    # Get current stock levels
                    stock_totals = await stock_repo.get_total_stock_by_ingredient(tenant_id, ingredient.id)
                    
                    # Convert to response schema
                    ingredient_dict = ingredient.to_dict()
                    
                    # Map category field based on product type
                    if ingredient.product_type and ingredient.product_type.value == 'finished_product':
                        ingredient_dict['category'] = ingredient.product_category.value if ingredient.product_category else None
                    else:
                        ingredient_dict['category'] = ingredient.ingredient_category.value if ingredient.ingredient_category else None
                    
                    response = IngredientResponse(**ingredient_dict)
                    response.current_stock = stock_totals['total_available']
                    response.is_low_stock = (
                        stock_totals['total_available'] <= ingredient.low_stock_threshold
                        if ingredient.low_stock_threshold is not None else False
                    )
                    response.needs_reorder = (
                        stock_totals['total_available'] <= ingredient.reorder_point
                        if ingredient.reorder_point is not None else False
                    )
                    
                    responses.append(response)
                
                return responses
                
        except Exception as e:
            logger.error("Failed to get ingredients", error=str(e), tenant_id=tenant_id)
            raise

    async def count_ingredients_by_tenant(self, tenant_id: UUID) -> int:
        """Count total number of active ingredients for a tenant"""
        try:
            async with get_db_transaction() as db:
                # Use SQLAlchemy count query for efficiency
                from sqlalchemy import select, func, and_

                query = select(func.count(Ingredient.id)).where(
                    and_(
                        Ingredient.tenant_id == tenant_id,
                        Ingredient.is_active == True
                    )
                )

                result = await db.execute(query)
                count = result.scalar() or 0

                logger.info("Counted ingredients", tenant_id=tenant_id, count=count)
                return count

        except Exception as e:
            logger.error("Failed to count ingredients", error=str(e), tenant_id=tenant_id)
            raise

    # ===== STOCK MANAGEMENT =====

    async def add_stock(
        self, 
        stock_data: StockCreate, 
        tenant_id: UUID,
        user_id: Optional[UUID] = None
    ) -> StockResponse:
        """Add new stock with automatic movement tracking"""
        try:
            async with get_db_transaction() as db:
                ingredient_repo = IngredientRepository(db)
                stock_repo = StockRepository(db)
                movement_repo = StockMovementRepository(db)
                
                # Validate ingredient exists
                ingredient = await ingredient_repo.get_by_id(UUID(stock_data.ingredient_id))
                if not ingredient or ingredient.tenant_id != tenant_id:
                    raise ValueError("Ingredient not found")

                # Generate batch number if not provided
                if not stock_data.batch_number:
                    try:
                        batch_generator = BatchNumberGenerator(stock_repo)
                        stock_data.batch_number = await batch_generator.generate_batch_number(
                            tenant_id=str(tenant_id),
                            prefix="INV"
                        )
                        logger.info("Generated batch number", batch_number=stock_data.batch_number)
                    except Exception as e:
                        # Fallback to a simple batch number if generation fails
                        stock_data.batch_number = create_fallback_batch_number("INV")
                        logger.warning("Used fallback batch number", batch_number=stock_data.batch_number, error=str(e))

                # Create stock entry
                stock = await stock_repo.create_stock_entry(stock_data, tenant_id)
                
                # Get current stock level before this movement
                current_stock = await stock_repo.get_total_stock_by_ingredient(tenant_id, UUID(stock_data.ingredient_id))
                quantity_before = current_stock['total_available']
                quantity_after = quantity_before + stock_data.current_quantity

                # Create stock movement record
                movement_data = StockMovementCreate(
                    ingredient_id=stock_data.ingredient_id,
                    stock_id=str(stock.id),
                    movement_type=StockMovementType.PURCHASE,
                    quantity=stock_data.current_quantity,
                    unit_cost=stock_data.unit_cost,
                    notes=f"Initial stock entry - Batch: {stock_data.batch_number or 'N/A'}"
                )
                await movement_repo.create_movement(movement_data, tenant_id, user_id, quantity_before, quantity_after)

                # Update ingredient's last purchase price and weighted average cost
                if stock_data.unit_cost:
                    await ingredient_repo.update_last_purchase_price(
                        UUID(stock_data.ingredient_id),
                        float(stock_data.unit_cost)
                    )

                    # Calculate and update weighted average cost
                    await ingredient_repo.update_weighted_average_cost(
                        ingredient_id=UUID(stock_data.ingredient_id),
                        current_stock_quantity=quantity_before,
                        new_purchase_quantity=stock_data.current_quantity,
                        new_unit_cost=float(stock_data.unit_cost)
                    )
                
                # Convert to response schema
                response = StockResponse(**stock.to_dict())
                response.ingredient = IngredientResponse(**ingredient.to_dict())
                
                logger.info("Stock added successfully", stock_id=stock.id, quantity=stock.current_quantity)
                return response

        except Exception as e:
            logger.error("Failed to add stock", error=str(e), tenant_id=tenant_id)
            raise

    async def bulk_add_stock(
        self,
        bulk_data: 'BulkStockCreate',
        tenant_id: UUID,
        user_id: Optional[UUID] = None
    ) -> 'BulkStockResponse':
        """Bulk add stock entries for efficient batch operations"""
        import uuid as uuid_lib
        from app.schemas.inventory import BulkStockCreate, BulkStockResult, BulkStockResponse

        transaction_id = str(uuid_lib.uuid4())
        results = []
        total_created = 0
        total_failed = 0

        for index, stock_data in enumerate(bulk_data.stocks):
            try:
                stock_response = await self.add_stock(stock_data, tenant_id, user_id)
                results.append(BulkStockResult(
                    index=index,
                    success=True,
                    stock=stock_response,
                    error=None
                ))
                total_created += 1
            except Exception as e:
                logger.warning(
                    "Failed to create stock in bulk operation",
                    index=index,
                    ingredient_id=stock_data.ingredient_id,
                    error=str(e)
                )
                results.append(BulkStockResult(
                    index=index,
                    success=False,
                    stock=None,
                    error=str(e)
                ))
                total_failed += 1

        logger.info(
            "Bulk stock operation completed",
            transaction_id=transaction_id,
            total_requested=len(bulk_data.stocks),
            total_created=total_created,
            total_failed=total_failed
        )

        return BulkStockResponse(
            total_requested=len(bulk_data.stocks),
            total_created=total_created,
            total_failed=total_failed,
            results=results,
            transaction_id=transaction_id
        )

    async def consume_stock(
        self, 
        ingredient_id: UUID, 
        quantity: float,
        tenant_id: UUID,
        user_id: Optional[UUID] = None,
        reference_number: Optional[str] = None,
        notes: Optional[str] = None,
        fifo: bool = True
    ) -> List[Dict[str, Any]]:
        """Consume stock using FIFO/LIFO with movement tracking"""
        try:
            async with get_db_transaction() as db:
                ingredient_repo = IngredientRepository(db)
                stock_repo = StockRepository(db)
                movement_repo = StockMovementRepository(db)
                
                # Validate ingredient
                ingredient = await ingredient_repo.get_by_id(ingredient_id)
                if not ingredient or ingredient.tenant_id != tenant_id:
                    raise ValueError("Ingredient not found")
                
                # Reserve stock first
                reservations = await stock_repo.reserve_stock(tenant_id, ingredient_id, quantity, fifo)

                if not reservations:
                    raise ValueError("Insufficient stock available")

                # Get current stock level before this consumption
                current_stock = await stock_repo.get_total_stock_by_ingredient(tenant_id, ingredient_id)
                running_stock_level = current_stock['total_available']

                consumed_items = []
                for reservation in reservations:
                    stock_id = UUID(reservation['stock_id'])
                    reserved_qty = reservation['reserved_quantity']

                    # Calculate before/after for this specific batch
                    batch_quantity_before = running_stock_level
                    batch_quantity_after = running_stock_level - reserved_qty
                    running_stock_level = batch_quantity_after  # Update for next iteration

                    # Consume from reserved stock
                    consumed_stock = await stock_repo.consume_stock(stock_id, reserved_qty, from_reserved=True)

                    # Create movement record with progressive tracking
                    movement_data = StockMovementCreate(
                        ingredient_id=str(ingredient_id),
                        stock_id=str(stock_id),
                        movement_type=StockMovementType.PRODUCTION_USE,
                        quantity=reserved_qty,
                        reference_number=reference_number,
                        notes=notes or f"Stock consumption - Batch: {reservation.get('batch_number', 'N/A')}"
                    )
                    await movement_repo.create_movement(movement_data, tenant_id, user_id, batch_quantity_before, batch_quantity_after)
                    
                    consumed_items.append({
                        'stock_id': str(stock_id),
                        'quantity_consumed': reserved_qty,
                        'batch_number': reservation.get('batch_number'),
                        'expiration_date': reservation.get('expiration_date')
                    })
                
                logger.info(
                    "Stock consumed successfully", 
                    ingredient_id=ingredient_id, 
                    total_quantity=quantity,
                    items_consumed=len(consumed_items)
                )
                return consumed_items
                
        except Exception as e:
            logger.error("Failed to consume stock", error=str(e), ingredient_id=ingredient_id)
            raise

    async def get_stock_by_ingredient(
        self, 
        ingredient_id: UUID, 
        tenant_id: UUID,
        include_unavailable: bool = False
    ) -> List[StockResponse]:
        """Get all stock entries for an ingredient"""
        try:
            async with get_db_transaction() as db:
                ingredient_repo = IngredientRepository(db)
                stock_repo = StockRepository(db)
                
                # Validate ingredient
                ingredient = await ingredient_repo.get_by_id(ingredient_id)
                if not ingredient or ingredient.tenant_id != tenant_id:
                    return []
                
                # Get stock entries
                stock_entries = await stock_repo.get_stock_by_ingredient(
                    tenant_id, ingredient_id, include_unavailable
                )
                
                responses = []
                for stock in stock_entries:
                    response = StockResponse(**stock.to_dict())
                    response.ingredient = IngredientResponse(**ingredient.to_dict())
                    responses.append(response)
                
                return responses
                
        except Exception as e:
            logger.error("Failed to get stock by ingredient", error=str(e), ingredient_id=ingredient_id)
            raise

    async def get_stock_movements(
        self,
        tenant_id: UUID,
        skip: int = 0,
        limit: int = 100,
        ingredient_id: Optional[UUID] = None,
        movement_type: Optional[str] = None
    ) -> List[StockMovementResponse]:
        """Get stock movements with filtering"""
        logger.info("📈 Getting stock movements",
                   tenant_id=str(tenant_id),
                   ingredient_id=str(ingredient_id) if ingredient_id else None,
                   skip=skip,
                   limit=limit,
                   movement_type=movement_type)
        try:
            async with get_db_transaction() as db:
                movement_repo = StockMovementRepository(db)
                ingredient_repo = IngredientRepository(db)

                # Validate ingredient exists if filtering by ingredient
                if ingredient_id:
                    ingredient = await ingredient_repo.get_by_id(ingredient_id)
                    if not ingredient:
                        logger.warning("Ingredient not found for movements query",
                                     ingredient_id=str(ingredient_id),
                                     tenant_id=str(tenant_id))
                        raise ValueError(f"Ingredient {ingredient_id} not found")

                    if ingredient.tenant_id != tenant_id:
                        logger.error("Ingredient does not belong to tenant",
                                   ingredient_id=str(ingredient_id),
                                   ingredient_tenant=str(ingredient.tenant_id),
                                   requested_tenant=str(tenant_id))
                        raise ValueError(f"Ingredient {ingredient_id} does not belong to tenant {tenant_id}")

                    logger.info("Ingredient validated for movements query",
                              ingredient_name=ingredient.name,
                              ingredient_id=str(ingredient_id))

                # Get filtered movements
                movements = await movement_repo.get_movements(
                    tenant_id=tenant_id,
                    skip=skip,
                    limit=limit,
                    ingredient_id=ingredient_id,
                    movement_type=movement_type
                )

                logger.info("📊 Found movements", count=len(movements))

                responses = []
                for movement in movements:
                    response = StockMovementResponse(**movement.to_dict())

                    # Include ingredient information if needed
                    if movement.ingredient_id:
                        ingredient = await ingredient_repo.get_by_id(movement.ingredient_id)
                        if ingredient:
                            response.ingredient = IngredientResponse(**ingredient.to_dict())

                    responses.append(response)

                logger.info("✅ Returning movements", response_count=len(responses))
                return responses

        except ValueError:
            # Re-raise validation errors as-is
            raise
        except Exception as e:
            logger.error("❌ Failed to get stock movements",
                        error=str(e),
                        error_type=type(e).__name__,
                        tenant_id=str(tenant_id))
            raise

    # ===== ALERTS AND NOTIFICATIONS =====

    async def check_low_stock_alerts(self, tenant_id: UUID) -> List[Dict[str, Any]]:
        """Check for ingredients with low stock levels"""
        try:
            async with get_db_transaction() as db:
                ingredient_repo = IngredientRepository(db)
                
                low_stock_items = await ingredient_repo.get_low_stock_ingredients(tenant_id)
                
                alerts = []
                for item in low_stock_items:
                    ingredient = item['ingredient']
                    alerts.append({
                        'ingredient_id': str(ingredient.id),
                        'ingredient_name': ingredient.name,
                        'current_stock': item['current_stock'],
                        'threshold': item['threshold'],
                        'needs_reorder': item['needs_reorder'],
                        'alert_type': 'reorder_needed' if item['needs_reorder'] else 'low_stock',
                        'severity': 'high' if item['needs_reorder'] else 'medium'
                    })
                
                return alerts
                
        except Exception as e:
            logger.error("Failed to check low stock alerts", error=str(e), tenant_id=tenant_id)
            raise

    async def check_expiration_alerts(self, tenant_id: UUID, days_ahead: int = 7) -> List[Dict[str, Any]]:
        """Check for stock items expiring soon"""
        try:
            async with get_db_transaction() as db:
                stock_repo = StockRepository(db)
                
                expiring_items = await stock_repo.get_expiring_stock(tenant_id, days_ahead)
                
                alerts = []
                for stock, ingredient in expiring_items:
                    days_to_expiry = (stock.expiration_date - datetime.now()).days if stock.expiration_date else None
                    
                    alerts.append({
                        'stock_id': str(stock.id),
                        'ingredient_id': str(ingredient.id),
                        'ingredient_name': ingredient.name,
                        'batch_number': stock.batch_number,
                        'quantity': stock.available_quantity,
                        'expiration_date': stock.expiration_date,
                        'days_to_expiry': days_to_expiry,
                        'alert_type': 'expiring_soon',
                        'severity': 'critical' if days_to_expiry and days_to_expiry <= 1 else 'high'
                    })
                
                return alerts
                
        except Exception as e:
            logger.error("Failed to check expiration alerts", error=str(e), tenant_id=tenant_id)
            raise

    # ===== DASHBOARD AND ANALYTICS =====

    async def get_inventory_summary(self, tenant_id: UUID) -> InventorySummary:
        """Get inventory summary for dashboard"""
        try:
            async with get_db_transaction() as db:
                ingredient_repo = IngredientRepository(db)
                stock_repo = StockRepository(db)
                movement_repo = StockMovementRepository(db)
                
                # Get basic counts
                total_ingredients_result = await ingredient_repo.count({'tenant_id': tenant_id, 'is_active': True})
                
                # Get stock summary
                stock_summary = await stock_repo.get_stock_summary_by_tenant(tenant_id)
                
                # Get low stock and expiring items
                low_stock_items = await ingredient_repo.get_low_stock_ingredients(tenant_id)
                expiring_items = await stock_repo.get_expiring_stock(tenant_id, 7)
                expired_items = await stock_repo.get_expired_stock(tenant_id)
                
                # Get recent activity
                recent_activity = await movement_repo.get_movement_summary_by_period(tenant_id, 7)
                
                # Build category breakdown
                stock_by_category = {}
                ingredients = await ingredient_repo.get_ingredients_by_tenant(tenant_id, 0, 1000)
                
                for ingredient in ingredients:
                    # Determine category based on product type, similar to to_dict() method
                    category = 'other'
                    if ingredient.product_type and ingredient.product_type.value == 'finished_product':
                        # For finished products, use product_category
                        if ingredient.product_category:
                            category = ingredient.product_category.value
                        elif ingredient.ingredient_category and ingredient.ingredient_category.value != 'other':
                            category = ingredient.ingredient_category.value
                    else:
                        # For ingredients, use ingredient_category
                        if ingredient.ingredient_category and ingredient.ingredient_category.value != 'other':
                            category = ingredient.ingredient_category.value
                        elif ingredient.product_category:
                            category = ingredient.product_category.value
                    
                    if category not in stock_by_category:
                        stock_by_category[category] = {
                            'count': 0,
                            'total_value': 0.0,
                            'low_stock_items': 0
                        }
                    
                    stock_by_category[category]['count'] += 1
                    # Additional calculations would go here
                
                return InventorySummary(
                    total_ingredients=total_ingredients_result,
                    total_stock_value=stock_summary['total_stock_value'],
                    low_stock_alerts=len(low_stock_items),
                    expiring_soon_items=len(expiring_items),
                    expired_items=len(expired_items),
                    out_of_stock_items=stock_summary.get('out_of_stock_count', 0),
                    stock_by_category=stock_by_category,
                    recent_movements=recent_activity.get('total_movements', 0),
                    recent_purchases=recent_activity.get('purchase', {}).get('count', 0),
                    recent_waste=recent_activity.get('waste', {}).get('count', 0)
                )
                
        except Exception as e:
            logger.error("Failed to get inventory summary", error=str(e), tenant_id=tenant_id)
            raise

    async def get_stock(
        self,
        tenant_id: UUID,
        skip: int = 0,
        limit: int = 100,
        ingredient_id: Optional[UUID] = None,
        available_only: bool = True
    ) -> List[StockResponse]:
        """Get stock entries with filtering"""
        try:
            async with get_db_transaction() as db:
                stock_repo = StockRepository(db)
                ingredient_repo = IngredientRepository(db)

                # Get stock entries
                stock_entries = await stock_repo.get_stock_entries(
                    tenant_id, skip, limit, ingredient_id, available_only
                )

                responses = []
                for stock in stock_entries:
                    # Get ingredient information
                    ingredient = await ingredient_repo.get_by_id(stock.ingredient_id)

                    response = StockResponse(**stock.to_dict())
                    if ingredient:
                        response.ingredient = IngredientResponse(**ingredient.to_dict())
                    responses.append(response)

                return responses

        except Exception as e:
            logger.error("Failed to get stock entries", error=str(e), tenant_id=tenant_id)
            raise

    async def create_stock_movement(
        self,
        movement_data: StockMovementCreate,
        tenant_id: UUID,
        user_id: Optional[UUID] = None
    ) -> StockMovementResponse:
        """Create a stock movement record with proper quantity tracking"""
        try:
            async with get_db_transaction() as db:
                movement_repo = StockMovementRepository(db)
                ingredient_repo = IngredientRepository(db)
                stock_repo = StockRepository(db)

                # Validate ingredient exists
                ingredient = await ingredient_repo.get_by_id(UUID(movement_data.ingredient_id))
                if not ingredient or ingredient.tenant_id != tenant_id:
                    raise ValueError("Ingredient not found")

                # Get current stock level before this movement
                current_stock = await stock_repo.get_total_stock_by_ingredient(tenant_id, UUID(movement_data.ingredient_id))
                quantity_before = current_stock['total_available']
                
                # Calculate quantity_after based on movement type
                movement_quantity = movement_data.quantity or 0
                if movement_data.movement_type in [StockMovementType.PURCHASE, StockMovementType.TRANSFORMATION, StockMovementType.INITIAL_STOCK]:
                    # These are additions to stock
                    quantity_after = quantity_before + movement_quantity
                else:
                    # These are subtractions from stock (PRODUCTION_USE, WASTE, ADJUSTMENT)
                    quantity_after = quantity_before - movement_quantity

                # Create stock movement record
                movement = await movement_repo.create_movement(
                    movement_data, 
                    tenant_id, 
                    user_id, 
                    quantity_before, 
                    quantity_after
                )

                # Convert to response schema
                response = StockMovementResponse(**movement.to_dict())
                response.ingredient = IngredientResponse(**ingredient.to_dict())

                logger.info(
                    "Stock movement created successfully", 
                    movement_id=movement.id, 
                    ingredient_id=movement.ingredient_id,
                    quantity=movement.quantity,
                    quantity_before=quantity_before,
                    quantity_after=quantity_after
                )
                return response

        except Exception as e:
            logger.error("Failed to create stock movement", error=str(e), tenant_id=tenant_id)
            raise

    async def get_stock_entry(
        self,
        stock_id: UUID,
        tenant_id: UUID
    ) -> Optional[StockResponse]:
        """Get a single stock entry by ID"""
        try:
            async with get_db_transaction() as db:
                stock_repo = StockRepository(db)
                ingredient_repo = IngredientRepository(db)

                # Get stock entry
                stock = await stock_repo.get_by_id(stock_id)

                # Check if stock exists and belongs to tenant
                if not stock or stock.tenant_id != tenant_id:
                    return None

                # Get ingredient information
                ingredient = await ingredient_repo.get_by_id(stock.ingredient_id)

                response = StockResponse(**stock.to_dict())
                if ingredient:
                    ingredient_dict = ingredient.to_dict()
                    # Map category field based on product type
                    if ingredient.product_type and ingredient.product_type.value == 'finished_product':
                        ingredient_dict['category'] = ingredient.product_category.value if ingredient.product_category else None
                    else:
                        ingredient_dict['category'] = ingredient.ingredient_category.value if ingredient.ingredient_category else None
                    response.ingredient = IngredientResponse(**ingredient_dict)

                return response

        except Exception as e:
            logger.error("Failed to get stock entry", error=str(e), stock_id=stock_id, tenant_id=tenant_id)
            raise

    async def update_stock(
        self,
        stock_id: UUID,
        stock_data: StockUpdate,
        tenant_id: UUID
    ) -> Optional[StockResponse]:
        """Update a stock entry"""
        try:
            async with get_db_transaction() as db:
                stock_repo = StockRepository(db)
                ingredient_repo = IngredientRepository(db)

                # Check if stock exists and belongs to tenant
                existing_stock = await stock_repo.get_by_id(stock_id)
                if not existing_stock or existing_stock.tenant_id != tenant_id:
                    return None

                # Prepare update data
                update_data = stock_data.model_dump(exclude_unset=True)

                # Recalculate available_quantity if current_quantity or reserved_quantity changed
                if 'current_quantity' in update_data or 'reserved_quantity' in update_data:
                    current_qty = update_data.get('current_quantity', existing_stock.current_quantity)
                    reserved_qty = update_data.get('reserved_quantity', existing_stock.reserved_quantity)
                    update_data['available_quantity'] = max(0, current_qty - reserved_qty)

                # Recalculate total cost if unit_cost or current_quantity changed
                if 'unit_cost' in update_data or 'current_quantity' in update_data:
                    unit_cost = update_data.get('unit_cost', existing_stock.unit_cost)
                    current_qty = update_data.get('current_quantity', existing_stock.current_quantity)
                    if unit_cost is not None and current_qty is not None:
                        from decimal import Decimal
                        update_data['total_cost'] = Decimal(str(unit_cost)) * Decimal(str(current_qty))

                # Update the stock entry
                updated_stock = await stock_repo.update(stock_id, update_data)
                if not updated_stock:
                    return None

                # Get ingredient information
                ingredient = await ingredient_repo.get_by_id(updated_stock.ingredient_id)

                response = StockResponse(**updated_stock.to_dict())
                if ingredient:
                    ingredient_dict = ingredient.to_dict()
                    # Map category field based on product type
                    if ingredient.product_type and ingredient.product_type.value == 'finished_product':
                        ingredient_dict['category'] = ingredient.product_category.value if ingredient.product_category else None
                    else:
                        ingredient_dict['category'] = ingredient.ingredient_category.value if ingredient.ingredient_category else None
                    response.ingredient = IngredientResponse(**ingredient_dict)

                # PHASE 2: Invalidate inventory dashboard cache
                cache_key = make_cache_key("inventory_dashboard", str(tenant_id))
                await delete_cached(cache_key)
                logger.debug("Invalidated inventory dashboard cache", cache_key=cache_key, tenant_id=str(tenant_id))

                logger.info("Stock entry updated successfully", stock_id=stock_id, tenant_id=tenant_id)
                return response

        except Exception as e:
            logger.error("Failed to update stock entry", error=str(e), stock_id=stock_id, tenant_id=tenant_id)
            raise

    async def delete_stock(
        self,
        stock_id: UUID,
        tenant_id: UUID
    ) -> bool:
        """Delete a stock entry"""
        try:
            async with get_db_transaction() as db:
                stock_repo = StockRepository(db)

                # Check if stock exists and belongs to tenant
                existing_stock = await stock_repo.get_by_id(stock_id)
                if not existing_stock or existing_stock.tenant_id != tenant_id:
                    return False

                # Delete the stock entry
                success = await stock_repo.delete_by_id(stock_id)

                if success:
                    logger.info("Stock entry deleted successfully", stock_id=stock_id, tenant_id=tenant_id)

                return success

        except Exception as e:
            logger.error("Failed to delete stock entry", error=str(e), stock_id=stock_id, tenant_id=tenant_id)
            raise

    # ===== DELETION METHODS =====

    async def hard_delete_ingredient(
        self,
        ingredient_id: UUID,
        tenant_id: UUID,
        user_id: Optional[UUID] = None
    ) -> Dict[str, Any]:
        """
        Completely delete an ingredient and all associated data.
        This includes:
        - All stock entries
        - All stock movements
        - All stock alerts
        - The ingredient record itself

        Returns a summary of what was deleted.
        """
        try:
            deletion_summary = {
                "ingredient_id": str(ingredient_id),
                "deleted_stock_entries": 0,
                "deleted_stock_movements": 0,
                "deleted_stock_alerts": 0,
                "ingredient_name": None,
                "success": False
            }

            async with get_db_transaction() as db:
                ingredient_repo = IngredientRepository(db)
                stock_repo = StockRepository(db)
                movement_repo = StockMovementRepository(db)

                # 1. Verify ingredient exists and belongs to tenant
                ingredient = await ingredient_repo.get_by_id(ingredient_id)
                if not ingredient or ingredient.tenant_id != tenant_id:
                    raise ValueError(f"Ingredient {ingredient_id} not found or access denied")

                deletion_summary["ingredient_name"] = ingredient.name

                logger.info(
                    "Starting hard deletion of ingredient",
                    ingredient_id=str(ingredient_id),
                    ingredient_name=ingredient.name,
                    tenant_id=str(tenant_id)
                )

                # 2. Delete all stock movements first (due to foreign key constraints)
                try:
                    deleted_movements = await movement_repo.delete_by_ingredient(ingredient_id, tenant_id)
                    deletion_summary["deleted_stock_movements"] = deleted_movements
                    logger.info(f"Deleted {deleted_movements} stock movements")
                except Exception as e:
                    logger.warning(f"Error deleting stock movements: {str(e)}")
                    # Continue with deletion even if this fails

                # 3. Delete all stock entries
                try:
                    deleted_stock = await stock_repo.delete_by_ingredient(ingredient_id, tenant_id)
                    deletion_summary["deleted_stock_entries"] = deleted_stock
                    logger.info(f"Deleted {deleted_stock} stock entries")
                except Exception as e:
                    logger.warning(f"Error deleting stock entries: {str(e)}")
                    # Continue with deletion even if this fails

                # 4. Delete stock alerts if they exist
                try:
                    # Note: StockAlert deletion would go here if that table exists
                    # For now, we'll assume this is handled by cascading deletes or doesn't exist
                    deletion_summary["deleted_stock_alerts"] = 0
                except Exception as e:
                    logger.warning(f"Error deleting stock alerts: {str(e)}")

                # 5. Finally, delete the ingredient itself
                deleted_ingredient = await ingredient_repo.delete_by_id(ingredient_id, tenant_id)
                if not deleted_ingredient:
                    raise ValueError("Failed to delete ingredient record")

                deletion_summary["success"] = True

                logger.info(
                    "Successfully completed hard deletion of ingredient",
                    ingredient_id=str(ingredient_id),
                    ingredient_name=ingredient.name,
                    summary=deletion_summary
                )

                return deletion_summary

        except ValueError:
            # Re-raise validation errors
            raise
        except Exception as e:
            logger.error(
                "Failed to hard delete ingredient",
                ingredient_id=str(ingredient_id),
                tenant_id=str(tenant_id),
                error=str(e)
            )
            raise

    async def soft_delete_ingredient(
        self,
        ingredient_id: UUID,
        tenant_id: UUID,
        user_id: Optional[UUID] = None
    ) -> IngredientResponse:
        """
        Soft delete an ingredient (mark as inactive).
        This preserves all associated data for reporting and audit purposes.
        """
        try:
            async with get_db_transaction() as db:
                ingredient_repo = IngredientRepository(db)

                # Verify ingredient exists and belongs to tenant
                ingredient = await ingredient_repo.get_by_id(ingredient_id)
                if not ingredient or ingredient.tenant_id != tenant_id:
                    raise ValueError(f"Ingredient {ingredient_id} not found or access denied")

                # Mark as inactive
                update_data = IngredientUpdate(is_active=False)
                updated_ingredient = await ingredient_repo.update_ingredient(ingredient_id, update_data)

                logger.info(
                    "Soft deleted ingredient",
                    ingredient_id=str(ingredient_id),
                    ingredient_name=ingredient.name,
                    tenant_id=str(tenant_id)
                )

                return IngredientResponse(**updated_ingredient.to_dict())

        except ValueError:
            raise
        except Exception as e:
            logger.error(
                "Failed to soft delete ingredient",
                ingredient_id=str(ingredient_id),
                tenant_id=str(tenant_id),
                error=str(e)
            )
            raise

    # ===== BATCH OPERATIONS FOR SALES IMPORT =====

    async def search_ingredients_by_name(
        self,
        product_name: str,
        tenant_id: UUID,
        db
    ) -> Optional[Ingredient]:
        """Search for an ingredient by name (case-insensitive exact match)"""
        try:
            repository = IngredientRepository(db)
            ingredients = await repository.search_ingredients(
                tenant_id=tenant_id,
                search_term=product_name,
                skip=0,
                limit=10
            )

            product_name_lower = product_name.lower().strip()
            for ingredient in ingredients:
                if ingredient.name.lower().strip() == product_name_lower:
                    return ingredient

            return None

        except Exception as e:
            logger.warning("Error searching ingredients by name",
                         product_name=product_name, error=str(e), tenant_id=tenant_id)
            return None

    async def create_ingredient_fast(
        self,
        ingredient_data: Dict[str, Any],
        tenant_id: UUID,
        db
    ) -> Ingredient:
        """Create ingredient without full validation for batch operations"""
        try:
            repository = IngredientRepository(db)

            ingredient_create = IngredientCreate(
                name=ingredient_data.get('name'),
                product_type=ingredient_data.get('type', 'FINISHED_PRODUCT'),
                unit_of_measure=ingredient_data.get('unit', 'UNITS'),
                low_stock_threshold=ingredient_data.get('current_stock', 0),
                reorder_point=max(ingredient_data.get('reorder_point', 1),
                                 ingredient_data.get('current_stock', 0) + 1),
                average_cost=ingredient_data.get('cost_per_unit', 0.0),
                ingredient_category=ingredient_data.get('category') if ingredient_data.get('type') == 'INGREDIENT' else None,
                product_category=ingredient_data.get('category') if ingredient_data.get('type') == 'FINISHED_PRODUCT' else None
            )

            ingredient = await repository.create_ingredient(ingredient_create, tenant_id)
            logger.debug("Created ingredient fast", ingredient_id=ingredient.id, name=ingredient.name)
            return ingredient

        except Exception as e:
            logger.error("Failed to create ingredient fast",
                       error=str(e), ingredient_data=ingredient_data, tenant_id=tenant_id)
            raise

    # ===== PRIVATE HELPER METHODS =====

    async def _generate_sku(self, db, tenant_id: UUID, product_name: str) -> str:
        """
        Generate unique SKU for inventory item
        Format: SKU-{PREFIX}-{SEQUENCE}
        Example: SKU-FLO-0001 for Flour, SKU-BRE-0023 for Bread

        Following the same pattern as order number generation in orders service
        """
        try:
            from sqlalchemy import select, func

            # Extract prefix from product name (first 3 chars, uppercase)
            prefix = product_name[:3].upper() if product_name and len(product_name) >= 3 else "ITM"

            # Count existing items with this SKU prefix for this tenant
            # This ensures sequential numbering per prefix per tenant
            stmt = select(func.count(Ingredient.id)).where(
                Ingredient.tenant_id == tenant_id,
                Ingredient.sku.like(f"SKU-{prefix}-%")
            )
            result = await db.execute(stmt)
            count = result.scalar() or 0

            # Generate sequential number
            sequence = count + 1
            sku = f"SKU-{prefix}-{sequence:04d}"

            logger.info("Generated SKU", sku=sku, prefix=prefix, sequence=sequence, tenant_id=tenant_id)
            return sku

        except Exception as e:
            logger.error("Error generating SKU, using fallback", error=str(e), product_name=product_name)
            # Fallback to UUID-based SKU to ensure uniqueness
            fallback_sku = f"SKU-{uuid.uuid4().hex[:8].upper()}"
            logger.warning("Using fallback SKU", sku=fallback_sku)
            return fallback_sku

    async def _validate_ingredient_data(self, ingredient_data: IngredientCreate, tenant_id: UUID):
        """Validate ingredient data for business rules"""
        # Only validate reorder_point if both values are provided
        # During onboarding, these fields may be None, which is valid
        if (ingredient_data.reorder_point is not None and
            ingredient_data.low_stock_threshold is not None):
            if ingredient_data.reorder_point <= ingredient_data.low_stock_threshold:
                raise ValueError("Reorder point must be greater than low stock threshold")

        # Storage requirements validation moved to stock level (not ingredient level)
        # This is now handled in stock creation/update validation

        # Add more validations as needed
        pass
