# ================================================================
# services/orders/app/repositories/order_repository.py
# ================================================================
"""
Order-related repositories for Orders Service
"""

from datetime import datetime, date, timedelta
from decimal import Decimal
from typing import List, Optional, Dict, Any
from uuid import UUID
from sqlalchemy import select, func, and_, or_, case, extract
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload, joinedload
import structlog

from app.models.customer import Customer
from app.models.order import CustomerOrder, OrderItem, OrderStatusHistory
from app.schemas.order_schemas import OrderCreate, OrderUpdate, OrderItemCreate, OrderItemUpdate
from app.repositories.base_repository import BaseRepository

logger = structlog.get_logger()


class CustomerRepository(BaseRepository[Customer, dict, dict]):
    """Repository for customer operations"""
    
    def __init__(self):
        super().__init__(Customer)
    
    async def get_by_customer_code(
        self, 
        db: AsyncSession, 
        customer_code: str, 
        tenant_id: UUID
    ) -> Optional[Customer]:
        """Get customer by customer code within tenant"""
        try:
            query = select(Customer).where(
                and_(
                    Customer.customer_code == customer_code,
                    Customer.tenant_id == tenant_id
                )
            )
            result = await db.execute(query)
            return result.scalar_one_or_none()
        except Exception as e:
            logger.error("Error getting customer by code", 
                        customer_code=customer_code, 
                        error=str(e))
            raise
    
    async def get_active_customers(
        self,
        db: AsyncSession,
        tenant_id: UUID,
        skip: int = 0,
        limit: int = 100
    ) -> List[Customer]:
        """Get active customers for a tenant"""
        try:
            query = select(Customer).where(
                and_(
                    Customer.tenant_id == tenant_id,
                    Customer.is_active == True
                )
            ).order_by(Customer.name).offset(skip).limit(limit)
            
            result = await db.execute(query)
            return result.scalars().all()
        except Exception as e:
            logger.error("Error getting active customers", error=str(e))
            raise
    
    async def update_customer_metrics(
        self,
        db: AsyncSession,
        customer_id: UUID,
        order_value: Decimal,
        order_date: datetime
    ):
        """Update customer metrics after order creation"""
        try:
            customer = await self.get(db, customer_id)
            if customer:
                customer.total_orders += 1
                customer.total_spent += order_value
                customer.average_order_value = customer.total_spent / customer.total_orders
                customer.last_order_date = order_date
                
                await db.flush()
                logger.info("Customer metrics updated", 
                           customer_id=str(customer_id),
                           new_total_spent=str(customer.total_spent))
        except Exception as e:
            logger.error("Error updating customer metrics", 
                        customer_id=str(customer_id), 
                        error=str(e))
            raise

    async def count_created_since(
        self,
        db: AsyncSession,
        tenant_id: UUID,
        since_date: datetime
    ) -> int:
        """Count customers created since a specific date"""
        try:
            query = select(func.count()).select_from(Customer).where(
                and_(
                    Customer.tenant_id == tenant_id,
                    Customer.created_at >= since_date
                )
            )
            result = await db.execute(query)
            return result.scalar()
        except Exception as e:
            logger.error("Error counting customers created since date",
                        tenant_id=str(tenant_id),
                        since_date=str(since_date),
                        error=str(e))
            raise


class OrderRepository(BaseRepository[CustomerOrder, OrderCreate, OrderUpdate]):
    """Repository for customer order operations"""

    def __init__(self):
        super().__init__(CustomerOrder)

    async def get_multi(
        self,
        db: AsyncSession,
        tenant_id: Optional[UUID] = None,
        skip: int = 0,
        limit: int = 100,
        filters: Optional[Dict[str, Any]] = None,
        order_by: Optional[str] = None,
        order_desc: bool = False
    ) -> List[CustomerOrder]:
        """Get multiple orders with eager loading of items and customer"""
        try:
            query = select(self.model).options(
                selectinload(CustomerOrder.items),
                selectinload(CustomerOrder.customer)
            )

            # Apply tenant filter
            if tenant_id:
                query = query.where(self.model.tenant_id == tenant_id)

            # Apply additional filters
            if filters:
                for key, value in filters.items():
                    if hasattr(self.model, key) and value is not None:
                        field = getattr(self.model, key)
                        if isinstance(value, list):
                            query = query.where(field.in_(value))
                        else:
                            query = query.where(field == value)

            # Apply ordering
            if order_by and hasattr(self.model, order_by):
                order_column = getattr(self.model, order_by)
                if order_desc:
                    query = query.order_by(order_column.desc())
                else:
                    query = query.order_by(order_column)
            else:
                # Default ordering by order_date desc
                query = query.order_by(CustomerOrder.order_date.desc())

            # Apply pagination
            query = query.offset(skip).limit(limit)

            result = await db.execute(query)
            return result.scalars().all()
        except Exception as e:
            logger.error("Error getting multiple orders", error=str(e))
            raise
    
    async def get_with_items(
        self, 
        db: AsyncSession, 
        order_id: UUID, 
        tenant_id: UUID
    ) -> Optional[CustomerOrder]:
        """Get order with all its items and customer info"""
        try:
            query = select(CustomerOrder).options(
                selectinload(CustomerOrder.items),
                selectinload(CustomerOrder.customer),
                selectinload(CustomerOrder.status_history)
            ).where(
                and_(
                    CustomerOrder.id == order_id,
                    CustomerOrder.tenant_id == tenant_id
                )
            )
            result = await db.execute(query)
            return result.scalar_one_or_none()
        except Exception as e:
            logger.error("Error getting order with items", 
                        order_id=str(order_id), 
                        error=str(e))
            raise
    
    async def get_by_order_number(
        self, 
        db: AsyncSession, 
        order_number: str, 
        tenant_id: UUID
    ) -> Optional[CustomerOrder]:
        """Get order by order number within tenant"""
        try:
            query = select(CustomerOrder).where(
                and_(
                    CustomerOrder.order_number == order_number,
                    CustomerOrder.tenant_id == tenant_id
                )
            )
            result = await db.execute(query)
            return result.scalar_one_or_none()
        except Exception as e:
            logger.error("Error getting order by number", 
                        order_number=order_number, 
                        error=str(e))
            raise
    
    async def get_orders_by_status(
        self,
        db: AsyncSession,
        tenant_id: UUID,
        status: str,
        skip: int = 0,
        limit: int = 100
    ) -> List[CustomerOrder]:
        """Get orders by status"""
        try:
            query = select(CustomerOrder).options(
                selectinload(CustomerOrder.customer)
            ).where(
                and_(
                    CustomerOrder.tenant_id == tenant_id,
                    CustomerOrder.status == status
                )
            ).order_by(CustomerOrder.order_date.desc()).offset(skip).limit(limit)
            
            result = await db.execute(query)
            return result.scalars().all()
        except Exception as e:
            logger.error("Error getting orders by status", 
                        status=status, 
                        error=str(e))
            raise
    
    async def get_orders_by_date_range(
        self,
        db: AsyncSession,
        tenant_id: UUID,
        start_date: date,
        end_date: date,
        skip: int = 0,
        limit: int = 100
    ) -> List[CustomerOrder]:
        """Get orders within date range"""
        try:
            query = select(CustomerOrder).options(
                selectinload(CustomerOrder.customer),
                selectinload(CustomerOrder.items)
            ).where(
                and_(
                    CustomerOrder.tenant_id == tenant_id,
                    func.date(CustomerOrder.order_date) >= start_date,
                    func.date(CustomerOrder.order_date) <= end_date
                )
            ).order_by(CustomerOrder.order_date.desc()).offset(skip).limit(limit)
            
            result = await db.execute(query)
            return result.scalars().all()
        except Exception as e:
            logger.error("Error getting orders by date range", 
                        start_date=str(start_date), 
                        end_date=str(end_date), 
                        error=str(e))
            raise
    
    async def get_pending_orders_by_delivery_date(
        self,
        db: AsyncSession,
        tenant_id: UUID,
        delivery_date: date
    ) -> List[CustomerOrder]:
        """Get pending orders for a specific delivery date"""
        try:
            query = select(CustomerOrder).options(
                selectinload(CustomerOrder.items),
                selectinload(CustomerOrder.customer)
            ).where(
                and_(
                    CustomerOrder.tenant_id == tenant_id,
                    CustomerOrder.status.in_(["pending", "confirmed", "in_production"]),
                    func.date(CustomerOrder.requested_delivery_date) == delivery_date
                )
            ).order_by(CustomerOrder.priority.desc(), CustomerOrder.order_date)
            
            result = await db.execute(query)
            return result.scalars().all()
        except Exception as e:
            logger.error("Error getting pending orders by delivery date", 
                        delivery_date=str(delivery_date), 
                        error=str(e))
            raise
    
    async def get_dashboard_metrics(
        self,
        db: AsyncSession,
        tenant_id: UUID
    ) -> Dict[str, Any]:
        """Get dashboard metrics for orders"""
        try:
            # Today's metrics
            today = datetime.now().date()
            week_start = today - timedelta(days=today.weekday())
            month_start = today.replace(day=1)
            
            # Order counts by period
            orders_today = await db.execute(
                select(func.count()).select_from(CustomerOrder).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        func.date(CustomerOrder.order_date) == today
                    )
                )
            )
            
            orders_week = await db.execute(
                select(func.count()).select_from(CustomerOrder).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        func.date(CustomerOrder.order_date) >= week_start
                    )
                )
            )
            
            orders_month = await db.execute(
                select(func.count()).select_from(CustomerOrder).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        func.date(CustomerOrder.order_date) >= month_start
                    )
                )
            )
            
            # Revenue by period
            revenue_today = await db.execute(
                select(func.coalesce(func.sum(CustomerOrder.total_amount), 0)).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        func.date(CustomerOrder.order_date) == today,
                        CustomerOrder.status != "cancelled"
                    )
                )
            )
            
            revenue_week = await db.execute(
                select(func.coalesce(func.sum(CustomerOrder.total_amount), 0)).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        func.date(CustomerOrder.order_date) >= week_start,
                        CustomerOrder.status != "cancelled"
                    )
                )
            )
            
            revenue_month = await db.execute(
                select(func.coalesce(func.sum(CustomerOrder.total_amount), 0)).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        func.date(CustomerOrder.order_date) >= month_start,
                        CustomerOrder.status != "cancelled"
                    )
                )
            )
            
            # Status breakdown
            status_counts = await db.execute(
                select(CustomerOrder.status, func.count()).select_from(CustomerOrder).where(
                    CustomerOrder.tenant_id == tenant_id
                ).group_by(CustomerOrder.status)
            )
            
            status_breakdown = {status: count for status, count in status_counts.fetchall()}
            
            # Average order value
            avg_order_value = await db.execute(
                select(func.coalesce(func.avg(CustomerOrder.total_amount), 0)).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        CustomerOrder.status != "cancelled"
                    )
                )
            )
            
            # Calculate repeat customers rate
            # Count customers who have made more than one order
            repeat_customers_query = await db.execute(
                select(func.count()).select_from(
                    select(CustomerOrder.customer_id)
                    .where(CustomerOrder.tenant_id == tenant_id)
                    .group_by(CustomerOrder.customer_id)
                    .having(func.count(CustomerOrder.id) > 1)
                    .subquery()
                )
            )
            
            total_customers_query = await db.execute(
                select(func.count(func.distinct(CustomerOrder.customer_id))).where(
                    CustomerOrder.tenant_id == tenant_id
                )
            )
            
            repeat_customers_count = repeat_customers_query.scalar() or 0
            total_customers_count = total_customers_query.scalar() or 0
            
            repeat_customers_rate = Decimal("0.0")
            if total_customers_count > 0:
                repeat_customers_rate = Decimal(str(repeat_customers_count)) / Decimal(str(total_customers_count))
                repeat_customers_rate = repeat_customers_rate * Decimal("100.0")  # Convert to percentage
            
            # Calculate order fulfillment rate
            total_orders_query = await db.execute(
                select(func.count()).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        CustomerOrder.status != "cancelled"
                    )
                )
            )
            
            fulfilled_orders_query = await db.execute(
                select(func.count()).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        CustomerOrder.status.in_(["delivered", "completed"])
                    )
                )
            )
            
            total_orders_count = total_orders_query.scalar() or 0
            fulfilled_orders_count = fulfilled_orders_query.scalar() or 0
            
            fulfillment_rate = Decimal("0.0")
            if total_orders_count > 0:
                fulfillment_rate = Decimal(str(fulfilled_orders_count)) / Decimal(str(total_orders_count))
                fulfillment_rate = fulfillment_rate * Decimal("100.0")  # Convert to percentage
            
            # Calculate on-time delivery rate
            on_time_delivered_query = await db.execute(
                select(func.count()).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        CustomerOrder.status == "delivered",
                        CustomerOrder.actual_delivery_date <= CustomerOrder.requested_delivery_date
                    )
                )
            )
            
            total_delivered_query = await db.execute(
                select(func.count()).where(
                    and_(
                        CustomerOrder.tenant_id == tenant_id,
                        CustomerOrder.status == "delivered"
                    )
                )
            )
            
            on_time_delivered_count = on_time_delivered_query.scalar() or 0
            total_delivered_count = total_delivered_query.scalar() or 0
            
            on_time_delivery_rate = Decimal("0.0")
            if total_delivered_count > 0:
                on_time_delivery_rate = Decimal(str(on_time_delivered_count)) / Decimal(str(total_delivered_count))
                on_time_delivery_rate = on_time_delivery_rate * Decimal("100.0")  # Convert to percentage
            
            return {
                "total_orders_today": orders_today.scalar(),
                "total_orders_this_week": orders_week.scalar(),
                "total_orders_this_month": orders_month.scalar(),
                "revenue_today": revenue_today.scalar(),
                "revenue_this_week": revenue_week.scalar(),
                "revenue_this_month": revenue_month.scalar(),
                "status_breakdown": status_breakdown,
                "average_order_value": avg_order_value.scalar(),
                "repeat_customers_rate": repeat_customers_rate,
                "fulfillment_rate": fulfillment_rate,
                "on_time_delivery_rate": on_time_delivery_rate,
                "repeat_customers_count": repeat_customers_count,
                "total_customers_count": total_customers_count,
                "total_orders_count": total_orders_count,
                "fulfilled_orders_count": fulfilled_orders_count,
                "on_time_delivered_count": on_time_delivered_count,
                "total_delivered_count": total_delivered_count
            }
        except Exception as e:
            logger.error("Error getting dashboard metrics", error=str(e))
            raise
    
    async def detect_business_model(
        self,
        db: AsyncSession,
        tenant_id: UUID,
        lookback_days: int = 30
    ) -> Optional[str]:
        """Detect business model based on order patterns"""
        try:
            cutoff_date = datetime.now().date() - timedelta(days=lookback_days)
            
            # Analyze order patterns
            query = select(
                func.count().label("total_orders"),
                func.avg(CustomerOrder.total_amount).label("avg_order_value"),
                func.count(func.distinct(CustomerOrder.customer_id)).label("unique_customers"),
                func.sum(
                    case(
                        (CustomerOrder.order_type == "rush", 1),
                        else_=0
                    )
                ).label("rush_orders"),
                func.sum(
                    case(
                        (CustomerOrder.sales_channel == "wholesale", 1),
                        else_=0
                    )
                ).label("wholesale_orders")
            ).where(
                and_(
                    CustomerOrder.tenant_id == tenant_id,
                    func.date(CustomerOrder.order_date) >= cutoff_date
                )
            )
            
            result = await db.execute(query)
            metrics = result.fetchone()
            
            if not metrics or metrics.total_orders == 0:
                return None
            
            # Business model detection logic
            orders_per_customer = metrics.total_orders / metrics.unique_customers
            wholesale_ratio = metrics.wholesale_orders / metrics.total_orders
            rush_ratio = metrics.rush_orders / metrics.total_orders
            
            if wholesale_ratio > 0.6 or orders_per_customer > 20:
                return "central_bakery"
            else:
                return "individual_bakery"
                
        except Exception as e:
            logger.error("Error detecting business model", error=str(e))
            return None


class OrderItemRepository(BaseRepository[OrderItem, OrderItemCreate, OrderItemUpdate]):
    """Repository for order item operations"""
    
    def __init__(self):
        super().__init__(OrderItem)
    
    async def get_items_by_order(
        self, 
        db: AsyncSession, 
        order_id: UUID
    ) -> List[OrderItem]:
        """Get all items for an order"""
        try:
            query = select(OrderItem).where(OrderItem.order_id == order_id)
            result = await db.execute(query)
            return result.scalars().all()
        except Exception as e:
            logger.error("Error getting order items", 
                        order_id=str(order_id), 
                        error=str(e))
            raise


class OrderStatusHistoryRepository(BaseRepository[OrderStatusHistory, dict, dict]):
    """Repository for order status history operations"""
    
    def __init__(self):
        super().__init__(OrderStatusHistory)
    
    async def create_status_change(
        self,
        db: AsyncSession,
        order_id: UUID,
        from_status: Optional[str],
        to_status: str,
        change_reason: Optional[str] = None,
        changed_by: Optional[UUID] = None,
        event_data: Optional[Dict[str, Any]] = None
    ) -> OrderStatusHistory:
        """Create a status change record"""
        try:
            status_history = OrderStatusHistory(
                order_id=order_id,
                from_status=from_status,
                to_status=to_status,
                change_reason=change_reason,
                changed_by=changed_by,
                event_data=event_data
            )
            
            db.add(status_history)
            await db.flush()
            await db.refresh(status_history)
            
            logger.info("Status change recorded", 
                       order_id=str(order_id),
                       from_status=from_status,
                       to_status=to_status)
            
            return status_history
        except Exception as e:
            logger.error("Error creating status change", 
                        order_id=str(order_id), 
                        error=str(e))
            raise
