# services/inventory/app/repositories/stock_movement_repository.py
"""
Stock Movement Repository using Repository Pattern
"""

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

from app.models.inventory import StockMovement, Ingredient, StockMovementType
from app.schemas.inventory import StockMovementCreate
from shared.database.repository import BaseRepository

logger = structlog.get_logger()


class StockMovementRepository(BaseRepository[StockMovement, StockMovementCreate, dict]):
    """Repository for stock movement operations"""

    def __init__(self, session: AsyncSession):
        super().__init__(StockMovement, session)

    async def create_movement(
        self, 
        movement_data: StockMovementCreate, 
        tenant_id: UUID,
        created_by: Optional[UUID] = None,
        quantity_before: Optional[float] = None,
        quantity_after: Optional[float] = None
    ) -> StockMovement:
        """Create a new stock movement record"""
        try:
            # Prepare data
            create_data = movement_data.model_dump()
            create_data['tenant_id'] = tenant_id
            create_data['created_by'] = created_by

            # Add quantity_before and quantity_after if provided
            if quantity_before is not None:
                create_data['quantity_before'] = quantity_before
            if quantity_after is not None:
                create_data['quantity_after'] = quantity_after

            # Ensure movement_type is properly converted to enum value
            if 'movement_type' in create_data:
                movement_type = create_data['movement_type']
                if hasattr(movement_type, 'value'):
                    # It's an enum object, use its value
                    create_data['movement_type'] = movement_type.value
                elif isinstance(movement_type, str):
                    # It's already a string, ensure it's uppercase for database
                    create_data['movement_type'] = movement_type.upper()
            
            # Set movement date if not provided
            if not create_data.get('movement_date'):
                create_data['movement_date'] = datetime.now()
            
            # Calculate total cost if unit cost provided
            if create_data.get('unit_cost') and create_data.get('quantity'):
                unit_cost = create_data['unit_cost']
                quantity = Decimal(str(create_data['quantity']))
                create_data['total_cost'] = unit_cost * quantity
            
            # Create record
            record = await self.create(create_data)
            logger.info(
                "Created stock movement", 
                movement_id=record.id,
                ingredient_id=record.ingredient_id,
                movement_type=record.movement_type if record.movement_type else None,
                quantity=record.quantity,
                quantity_before=record.quantity_before,
                quantity_after=record.quantity_after,
                tenant_id=tenant_id
            )
            return record
            
        except Exception as e:
            logger.error("Failed to create stock movement", error=str(e), tenant_id=tenant_id)
            raise

    async def get_movements_by_ingredient(
        self, 
        tenant_id: UUID, 
        ingredient_id: UUID,
        skip: int = 0,
        limit: int = 100,
        days_back: Optional[int] = None
    ) -> List[StockMovement]:
        """Get stock movements for a specific ingredient"""
        try:
            query = select(self.model).where(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.ingredient_id == ingredient_id
                )
            )
            
            # Filter by date range if specified
            if days_back:
                start_date = datetime.now() - timedelta(days=days_back)
                query = query.where(self.model.movement_date >= start_date)
            
            query = query.order_by(desc(self.model.movement_date)).offset(skip).limit(limit)
            
            result = await self.session.execute(query)
            return result.scalars().all()
            
        except Exception as e:
            logger.error("Failed to get movements by ingredient", error=str(e), ingredient_id=ingredient_id)
            raise

    async def get_movements_by_type(
        self, 
        tenant_id: UUID, 
        movement_type: StockMovementType,
        skip: int = 0,
        limit: int = 100,
        days_back: Optional[int] = None
    ) -> List[StockMovement]:
        """Get stock movements by type"""
        try:
            query = select(self.model).where(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.movement_type == movement_type
                )
            )
            
            # Filter by date range if specified
            if days_back:
                start_date = datetime.now() - timedelta(days=days_back)
                query = query.where(self.model.movement_date >= start_date)
            
            query = query.order_by(desc(self.model.movement_date)).offset(skip).limit(limit)
            
            result = await self.session.execute(query)
            return result.scalars().all()
            
        except Exception as e:
            logger.error("Failed to get movements by type", error=str(e), movement_type=movement_type)
            raise

    async def get_recent_movements(
        self, 
        tenant_id: UUID, 
        limit: int = 50
    ) -> List[StockMovement]:
        """Get recent stock movements for dashboard"""
        try:
            result = await self.session.execute(
                select(self.model)
                .where(self.model.tenant_id == tenant_id)
                .order_by(desc(self.model.movement_date))
                .limit(limit)
            )
            return result.scalars().all()
            
        except Exception as e:
            logger.error("Failed to get recent movements", error=str(e), tenant_id=tenant_id)
            raise

    async def get_movements(
        self,
        tenant_id: UUID,
        skip: int = 0,
        limit: int = 100,
        ingredient_id: Optional[UUID] = None,
        movement_type: Optional[str] = None
    ) -> List[StockMovement]:
        """Get stock movements with filtering"""
        logger.info("🔍 Repository getting movements",
                   tenant_id=tenant_id,
                   ingredient_id=ingredient_id,
                   skip=skip,
                   limit=limit)
        try:
            query = select(self.model).where(self.model.tenant_id == tenant_id)

            # Add filters
            if ingredient_id:
                query = query.where(self.model.ingredient_id == ingredient_id)
                logger.info("🎯 Filtering by ingredient_id", ingredient_id=ingredient_id)

            if movement_type:
                # Convert string to enum
                try:
                    movement_type_enum = StockMovementType(movement_type)
                    query = query.where(self.model.movement_type == movement_type_enum)
                    logger.info("🏷️ Filtering by movement_type", movement_type=movement_type)
                except ValueError:
                    logger.warning("⚠️ Invalid movement type", movement_type=movement_type)
                    # Invalid movement type, skip filter
                    pass

            # Order by date (newest first) and apply pagination
            query = query.order_by(desc(self.model.movement_date)).offset(skip).limit(limit)

            result = await self.session.execute(query)
            movements = result.scalars().all()

            logger.info("🔢 Repository found movements", count=len(movements))
            return movements

        except Exception as e:
            logger.error("❌ Repository failed to get movements", error=str(e), tenant_id=tenant_id)
            raise

    async def get_movements_by_reference(
        self, 
        tenant_id: UUID, 
        reference_number: str
    ) -> List[StockMovement]:
        """Get stock movements by reference number (e.g., purchase order)"""
        try:
            result = await self.session.execute(
                select(self.model).where(
                    and_(
                        self.model.tenant_id == tenant_id,
                        self.model.reference_number == reference_number
                    )
                ).order_by(desc(self.model.movement_date))
            )
            return result.scalars().all()
            
        except Exception as e:
            logger.error("Failed to get movements by reference", error=str(e), reference_number=reference_number)
            raise

    async def get_movement_summary_by_period(
        self, 
        tenant_id: UUID,
        days_back: int = 30
    ) -> Dict[str, Any]:
        """Get movement summary for specified period"""
        try:
            start_date = datetime.now() - timedelta(days=days_back)
            
            # Get movement counts by type
            result = await self.session.execute(
                select(
                    self.model.movement_type,
                    func.count(self.model.id).label('count'),
                    func.coalesce(func.sum(self.model.quantity), 0).label('total_quantity'),
                    func.coalesce(func.sum(self.model.total_cost), 0).label('total_cost')
                ).where(
                    and_(
                        self.model.tenant_id == tenant_id,
                        self.model.movement_date >= start_date
                    )
                ).group_by(self.model.movement_type)
            )
            
            summary = {}
            for row in result:
                movement_type = row.movement_type if row.movement_type else "unknown"
                summary[movement_type] = {
                    'count': row.count,
                    'total_quantity': float(row.total_quantity),
                    'total_cost': float(row.total_cost) if row.total_cost else 0.0
                }
            
            # Get total movements count
            total_result = await self.session.execute(
                select(func.count(self.model.id)).where(
                    and_(
                        self.model.tenant_id == tenant_id,
                        self.model.movement_date >= start_date
                    )
                )
            )
            
            summary['total_movements'] = total_result.scalar() or 0
            summary['period_days'] = days_back
            
            return summary
            
        except Exception as e:
            logger.error("Failed to get movement summary", error=str(e), tenant_id=tenant_id)
            raise

    async def get_waste_movements(
        self,
        tenant_id: UUID,
        days_back: Optional[int] = None,
        start_date: Optional[datetime] = None,
        end_date: Optional[datetime] = None,
        skip: int = 0,
        limit: int = 100
    ) -> List[StockMovement]:
        """Get waste-related movements"""
        try:
            query = select(self.model).where(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.movement_type == StockMovementType.WASTE
                )
            )

            # Prefer explicit date range over days_back
            if start_date and end_date:
                query = query.where(
                    and_(
                        self.model.movement_date >= start_date,
                        self.model.movement_date <= end_date
                    )
                )
            elif days_back:
                calculated_start = datetime.now() - timedelta(days=days_back)
                query = query.where(self.model.movement_date >= calculated_start)

            query = query.order_by(desc(self.model.movement_date)).offset(skip).limit(limit)

            result = await self.session.execute(query)
            return result.scalars().all()

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

    async def get_purchase_movements(
        self, 
        tenant_id: UUID,
        days_back: Optional[int] = None,
        skip: int = 0,
        limit: int = 100
    ) -> List[StockMovement]:
        """Get purchase-related movements"""
        try:
            query = select(self.model).where(
                and_(
                    self.model.tenant_id == tenant_id,
                    self.model.movement_type == StockMovementType.PURCHASE
                )
            )
            
            if days_back:
                start_date = datetime.now() - timedelta(days=days_back)
                query = query.where(self.model.movement_date >= start_date)
            
            query = query.order_by(desc(self.model.movement_date)).offset(skip).limit(limit)
            
            result = await self.session.execute(query)
            return result.scalars().all()
            
        except Exception as e:
            logger.error("Failed to get purchase movements", error=str(e), tenant_id=tenant_id)
            raise

    async def calculate_ingredient_usage(
        self, 
        tenant_id: UUID, 
        ingredient_id: UUID,
        days_back: int = 30
    ) -> Dict[str, float]:
        """Calculate ingredient usage statistics"""
        try:
            start_date = datetime.now() - timedelta(days=days_back)
            
            # Get production usage
            production_result = await self.session.execute(
                select(func.coalesce(func.sum(self.model.quantity), 0)).where(
                    and_(
                        self.model.tenant_id == tenant_id,
                        self.model.ingredient_id == ingredient_id,
                        self.model.movement_type == StockMovementType.PRODUCTION_USE,
                        self.model.movement_date >= start_date
                    )
                )
            )
            
            # Get waste quantity
            waste_result = await self.session.execute(
                select(func.coalesce(func.sum(self.model.quantity), 0)).where(
                    and_(
                        self.model.tenant_id == tenant_id,
                        self.model.ingredient_id == ingredient_id,
                        self.model.movement_type == StockMovementType.WASTE,
                        self.model.movement_date >= start_date
                    )
                )
            )
            
            # Get purchases
            purchase_result = await self.session.execute(
                select(func.coalesce(func.sum(self.model.quantity), 0)).where(
                    and_(
                        self.model.tenant_id == tenant_id,
                        self.model.ingredient_id == ingredient_id,
                        self.model.movement_type == StockMovementType.PURCHASE,
                        self.model.movement_date >= start_date
                    )
                )
            )
            
            production_usage = float(production_result.scalar() or 0)
            waste_quantity = float(waste_result.scalar() or 0)
            purchase_quantity = float(purchase_result.scalar() or 0)
            
            # Calculate usage rate per day
            usage_per_day = production_usage / days_back if days_back > 0 else 0
            waste_percentage = (waste_quantity / purchase_quantity * 100) if purchase_quantity > 0 else 0
            
            return {
                'production_usage': production_usage,
                'waste_quantity': waste_quantity,
                'purchase_quantity': purchase_quantity,
                'usage_per_day': usage_per_day,
                'waste_percentage': waste_percentage,
                'period_days': days_back
            }
            
        except Exception as e:
            logger.error("Failed to calculate ingredient usage", error=str(e), ingredient_id=ingredient_id)
            raise

    async def delete_by_ingredient(self, ingredient_id: UUID, tenant_id: UUID) -> int:
        """Delete all stock movements for a specific ingredient"""
        try:
            from sqlalchemy import delete
            from app.models.inventory import StockMovement

            stmt = delete(StockMovement).where(
                and_(
                    StockMovement.ingredient_id == ingredient_id,
                    StockMovement.tenant_id == tenant_id
                )
            )

            result = await self.session.execute(stmt)
            deleted_count = result.rowcount

            logger.info(
                "Deleted stock movements for ingredient",
                ingredient_id=str(ingredient_id),
                tenant_id=str(tenant_id),
                deleted_count=deleted_count
            )

            return deleted_count

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

    async def create_automatic_waste_movement(
        self,
        tenant_id: UUID,
        ingredient_id: UUID,
        stock_id: UUID,
        quantity: float,
        unit_cost: Optional[float],
        batch_number: Optional[str],
        expiration_date: datetime,
        created_by: Optional[UUID] = None
    ) -> StockMovement:
        """Create an automatic waste movement for expired batches"""
        try:
            # Calculate total cost
            total_cost = None
            if unit_cost and quantity:
                total_cost = Decimal(str(unit_cost)) * Decimal(str(quantity))

            # Generate reference number
            reference_number = f"AUTO-EXPIRE-{batch_number or stock_id}"

            # Create movement data (without quantity_before/quantity_after - these will be calculated by the caller)
            movement_data = {
                'tenant_id': tenant_id,
                'ingredient_id': ingredient_id,
                'stock_id': stock_id,
                'movement_type': StockMovementType.WASTE.value,
                'quantity': quantity,
                'unit_cost': Decimal(str(unit_cost)) if unit_cost else None,
                'total_cost': total_cost,
                'reference_number': reference_number,
                'reason_code': 'expired',
                'notes': f"Lote automáticamente marcado como caducado. Vencimiento: {expiration_date.strftime('%Y-%m-%d')}",
                'movement_date': datetime.now(),
                'created_by': created_by
            }

            # Create the movement record
            movement = await self.create(movement_data)

            logger.info("Created automatic waste movement for expired batch",
                       movement_id=str(movement.id),
                       tenant_id=str(tenant_id),
                       ingredient_id=str(ingredient_id),
                       stock_id=str(stock_id),
                       quantity=quantity,
                       batch_number=batch_number,
                       reference_number=reference_number)

            return movement

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

    async def get_inventory_waste_total(
        self,
        tenant_id: UUID,
        start_date: datetime,
        end_date: datetime
    ) -> float:
        """
        Get total inventory waste for sustainability reporting

        Args:
            tenant_id: Tenant UUID
            start_date: Start date for period
            end_date: End date for period

        Returns:
            Total waste quantity
        """
        try:
            from sqlalchemy import text

            query = text("""
                SELECT COALESCE(SUM(sm.quantity), 0) as total_inventory_waste
                FROM stock_movements sm
                JOIN ingredients i ON sm.ingredient_id = i.id
                WHERE i.tenant_id = :tenant_id
                AND sm.movement_type = 'WASTE'
                AND sm.movement_date BETWEEN :start_date AND :end_date
            """)

            result = await self.session.execute(
                query,
                {
                    'tenant_id': tenant_id,
                    'start_date': start_date,
                    'end_date': end_date
                }
            )
            row = result.fetchone()

            return float(row.total_inventory_waste or 0)

        except Exception as e:
            logger.error("Failed to get inventory waste total", error=str(e), tenant_id=str(tenant_id))
            raise
