"""
Repository for persisting inventory reservations and events to database.

Provides async database operations for:
- Creating/updating/querying InventoryReservation records
- Appending InventoryEvent audit logs
- Reconciliation queries
"""

from typing import List, Optional, Callable
from datetime import datetime, timezone
from uuid import UUID
import logging

from sqlalchemy import select, update, and_
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.ext.asyncio import AsyncSession

from ..models.warehouse.inventory_reservation import (
    InventoryReservation,
    ReservationStatus,
    ReservationSource,
    ReleaseReason,
)
from ..models.warehouse.inventory_event import (
    InventoryEvent,
    InventoryEventType,
    create_reserve_event,
    create_release_event,
    create_erp_update_event,
)

logger = logging.getLogger(__name__)


class InventoryRepository:
    """
    Async repository for inventory persistence operations.
    
    Usage:
        async with get_async_session() as session:
            repo = InventoryRepository(session)
            await repo.create_reservation(...)
    """
    
    def __init__(self, session: AsyncSession):
        self.session = session
    
    async def create_reservation(
        self,
        sku: str,
        warehouse_id: str,
        quantity: int,
        source: str,
        order_id: str,
        order_display_id: str = None,
        available_before: int = None,
        available_after: int = None,
        expires_at: datetime = None,
    ) -> InventoryReservation:
        """Create a new active reservation record."""
        reservation = InventoryReservation(
            sku=sku,
            warehouse_id=warehouse_id,
            quantity=quantity,
            source=ReservationSource(source),
            order_id=order_id,
            order_display_id=order_display_id,
            status=ReservationStatus.ACTIVE,
            available_before=available_before,
            available_after=available_after,
            expires_at=expires_at,
        )
        self.session.add(reservation)
        await self.session.flush()
        return reservation
    
    async def release_reservation(
        self,
        sku: str,
        warehouse_id: str,
        order_id: str,
        source: str,
        reason: str,
    ) -> Optional[InventoryReservation]:
        """Mark a reservation as released."""
        stmt = (
            update(InventoryReservation)
            .where(
                and_(
                    InventoryReservation.sku == sku,
                    InventoryReservation.warehouse_id == warehouse_id,
                    InventoryReservation.order_id == order_id,
                    InventoryReservation.source == ReservationSource(source),
                    InventoryReservation.status == ReservationStatus.ACTIVE,
                )
            )
            .values(
                status=ReservationStatus.RELEASED,
                release_reason=ReleaseReason(reason),
                released_at=datetime.now(timezone.utc),
            )
            .returning(InventoryReservation)
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()
    
    async def get_active_reservations(
        self,
        sku: str = None,
        warehouse_id: str = None,
        order_id: str = None,
    ) -> List[InventoryReservation]:
        """Query active reservations with optional filters."""
        stmt = select(InventoryReservation).where(
            InventoryReservation.status == ReservationStatus.ACTIVE
        )
        
        if sku:
            stmt = stmt.where(InventoryReservation.sku == sku)
        if warehouse_id:
            stmt = stmt.where(InventoryReservation.warehouse_id == warehouse_id)
        if order_id:
            stmt = stmt.where(InventoryReservation.order_id == order_id)
        
        result = await self.session.execute(stmt)
        return list(result.scalars().all())
    
    async def get_reservation_totals(
        self,
        sku: str,
        warehouse_id: str,
    ) -> dict:
        """Get total reserved quantities per source for a SKU/warehouse."""
        reservations = await self.get_active_reservations(sku=sku, warehouse_id=warehouse_id)
        
        totals = {"medusa": 0, "shopify": 0, "total": 0}
        for res in reservations:
            source_key = res.source.value
            totals[source_key] = totals.get(source_key, 0) + res.quantity
            totals["total"] += res.quantity
        
        return totals
    
    async def log_reserve_event(
        self,
        sku: str,
        warehouse_id: str,
        quantity: int,
        source: str,
        order_id: str,
        order_display_id: str = None,
        reservation_id: UUID = None,
        available_after: int = None,
        reserved_medusa_after: int = None,
        reserved_shopify_after: int = None,
        correlation_id: str = None,
    ) -> InventoryEvent:
        """Log a RESERVED event."""
        event = create_reserve_event(
            sku=sku,
            warehouse_id=warehouse_id,
            quantity=quantity,
            source=source,
            order_id=order_id,
            order_display_id=order_display_id,
            reservation_id=reservation_id,
            available_after=available_after,
            reserved_medusa_after=reserved_medusa_after,
            reserved_shopify_after=reserved_shopify_after,
            correlation_id=correlation_id,
        )
        self.session.add(event)
        await self.session.flush()
        return event
    
    async def log_release_event(
        self,
        sku: str,
        warehouse_id: str,
        quantity: int,
        source: str,
        reason: str,
        order_id: str = None,
        order_display_id: str = None,
        reservation_id: UUID = None,
        available_after: int = None,
        reserved_medusa_after: int = None,
        reserved_shopify_after: int = None,
        correlation_id: str = None,
    ) -> InventoryEvent:
        """Log a RELEASED event."""
        event = create_release_event(
            sku=sku,
            warehouse_id=warehouse_id,
            quantity=quantity,
            source=source,
            reason=reason,
            order_id=order_id,
            order_display_id=order_display_id,
            reservation_id=reservation_id,
            available_after=available_after,
            reserved_medusa_after=reserved_medusa_after,
            reserved_shopify_after=reserved_shopify_after,
            correlation_id=correlation_id,
        )
        self.session.add(event)
        await self.session.flush()
        return event
    
    async def log_erp_update_event(
        self,
        sku: str,
        warehouse_id: str,
        erp_quantity: int,
        available_after: int,
        reserved_medusa_after: int = None,
        reserved_shopify_after: int = None,
        event_id: str = None,
        correlation_id: str = None,
    ) -> InventoryEvent:
        """Log an ERP_UPDATE event."""
        event = create_erp_update_event(
            sku=sku,
            warehouse_id=warehouse_id,
            erp_quantity=erp_quantity,
            available_after=available_after,
            reserved_medusa_after=reserved_medusa_after,
            reserved_shopify_after=reserved_shopify_after,
            event_id=event_id,
            correlation_id=correlation_id,
        )
        self.session.add(event)
        await self.session.flush()
        return event
    
    async def get_events_for_sku(
        self,
        sku: str,
        warehouse_id: str = None,
        since: datetime = None,
        until: datetime = None,
        limit: int = 100,
    ) -> List[InventoryEvent]:
        """Query events for a SKU with optional filters."""
        stmt = select(InventoryEvent).where(InventoryEvent.sku == sku)
        
        if warehouse_id:
            stmt = stmt.where(InventoryEvent.warehouse_id == warehouse_id)
        if since:
            stmt = stmt.where(InventoryEvent.created_at >= since)
        if until:
            stmt = stmt.where(InventoryEvent.created_at <= until)
        
        stmt = stmt.order_by(InventoryEvent.created_at.desc()).limit(limit)
        
        result = await self.session.execute(stmt)
        return list(result.scalars().all())
    
    async def get_events_for_order(self, order_id: str) -> List[InventoryEvent]:
        """Get all events for a specific order."""
        stmt = (
            select(InventoryEvent)
            .where(InventoryEvent.order_id == order_id)
            .order_by(InventoryEvent.created_at.asc())
        )
        result = await self.session.execute(stmt)
        return list(result.scalars().all())
