"""
Data models for inventory operations.

These dataclasses provide type-safe results from inventory operations,
making it easy to check success/failure and access relevant data.
"""

from dataclasses import dataclass, field
from typing import Optional, Literal, Union
import json


@dataclass
class ReserveResult:
    """
    Result of an inventory reservation attempt.
    
    Attributes:
        success: Whether the reservation was successful
        available: Current available quantity after operation
        reserved: Current reserved quantity for this source after operation
        source: The channel that made the reservation ("medusa" or "shopify")
        quantity_reserved: Amount that was actually reserved (0 if failed)
        reason: Failure reason if success=False
        message: Human-readable message (optional)
    """
    success: bool
    available: int = 0
    reserved: int = 0
    source: str = ""
    quantity_reserved: int = 0
    reason: Optional[str] = None
    message: Optional[str] = None
    requested: int = 0  # Only set on failure to show what was requested
    
    @classmethod
    def from_json(cls, json_str: Union[bytes, str]) -> "ReserveResult":
        """Parse result from Redis LUA script JSON response."""
        if isinstance(json_str, bytes):
            json_str = json_str.decode("utf-8")
        data = json.loads(json_str)
        return cls(
            success=data.get("success", False),
            available=data.get("available", 0),
            reserved=data.get("reserved", 0),
            source=data.get("source", ""),
            quantity_reserved=data.get("quantity_reserved", 0),
            reason=data.get("reason"),
            message=data.get("message"),
            requested=data.get("requested", 0),
        )
    
    def __bool__(self) -> bool:
        """Allow using result in boolean context."""
        return self.success


@dataclass
class ReleaseResult:
    """
    Result of an inventory release operation.
    
    Attributes:
        success: Whether the release was successful
        released: Amount that was actually released
        available: Current available quantity after operation
        reserved: Current reserved quantity for this source after operation
        erp_quantity: Current ERP quantity after operation (for fulfillment)
        source: The channel that released ("medusa" or "shopify")
        reason: Release reason ("fulfilled", "cancelled", "expired") or failure reason
        message: Human-readable message (optional)
    """
    success: bool
    released: int = 0
    available: int = 0
    reserved: int = 0
    source: str = ""
    erp_quantity: Optional[int] = None
    reason: Optional[str] = None
    message: Optional[str] = None
    
    @classmethod
    def from_json(cls, json_str: Union[bytes, str]) -> "ReleaseResult":
        """Parse result from Redis LUA script JSON response."""
        if isinstance(json_str, bytes):
            json_str = json_str.decode("utf-8")
        data = json.loads(json_str)
        return cls(
            success=data.get("success", False),
            released=data.get("released", 0),
            available=data.get("available", 0),
            reserved=data.get("reserved", 0),
            source=data.get("source", ""),
            erp_quantity=data.get("erp_quantity"),
            reason=data.get("reason"),
            message=data.get("message"),
        )
    
    def __bool__(self) -> bool:
        """Allow using result in boolean context."""
        return self.success


@dataclass
class SetInventoryResult:
    """
    Result of setting ERP inventory quantity.
    
    Attributes:
        success: Whether the operation was successful
        erp_quantity: The raw ERP quantity that was set
        available: Calculated available quantity (erp - total_reserved)
        reserved_medusa: Current Medusa reservations
        reserved_shopify: Current Shopify reservations
        total_reserved: Sum of all reservations
        reason: Failure reason if success=False
        message: Human-readable message (optional)
    """
    success: bool
    erp_quantity: int = 0
    available: int = 0
    reserved_medusa: int = 0
    reserved_shopify: int = 0
    total_reserved: int = 0
    reason: Optional[str] = None
    message: Optional[str] = None
    
    @classmethod
    def from_json(cls, json_str: Union[bytes, str]) -> "SetInventoryResult":
        """Parse result from Redis LUA script JSON response."""
        if isinstance(json_str, bytes):
            json_str = json_str.decode("utf-8")
        data = json.loads(json_str)
        return cls(
            success=data.get("success", False),
            erp_quantity=data.get("erp_quantity", 0),
            available=data.get("available", 0),
            reserved_medusa=data.get("reserved_medusa", 0),
            reserved_shopify=data.get("reserved_shopify", 0),
            total_reserved=data.get("total_reserved", 0),
            reason=data.get("reason"),
            message=data.get("message"),
        )
    
    def __bool__(self) -> bool:
        """Allow using result in boolean context."""
        return self.success


@dataclass
class InventoryState:
    """
    Full inventory state for a SKU/warehouse combination.
    
    Attributes:
        sku: Product SKU
        warehouse_id: Warehouse identifier
        available: Currently available for sale
        erp_quantity: Raw quantity from ERP
        reserved_medusa: Reserved by Medusa orders
        reserved_shopify: Reserved by Shopify orders
        total_reserved: Sum of all reservations
    """
    sku: str
    warehouse_id: str
    available: int = 0
    erp_quantity: int = 0
    reserved_medusa: int = 0
    reserved_shopify: int = 0
    total_reserved: int = 0
    
    @classmethod
    def from_json(cls, sku: str, warehouse_id: str, json_str: Union[bytes, str]) -> "InventoryState":
        """Parse state from Redis LUA script JSON response."""
        if isinstance(json_str, bytes):
            json_str = json_str.decode("utf-8")
        data = json.loads(json_str)
        return cls(
            sku=sku,
            warehouse_id=warehouse_id,
            available=data.get("available", 0),
            erp_quantity=data.get("erp_quantity", 0),
            reserved_medusa=data.get("reserved_medusa", 0),
            reserved_shopify=data.get("reserved_shopify", 0),
            total_reserved=data.get("total_reserved", 0),
        )
    
    @property
    def is_in_stock(self) -> bool:
        """Check if item is available for purchase."""
        return self.available > 0


@dataclass
class InsufficientInventoryError(Exception):
    """
    Exception raised when there's not enough inventory to fulfill a reservation.
    
    Attributes:
        sku: Product SKU
        warehouse_id: Warehouse identifier
        requested: Quantity that was requested
        available: Quantity that was available
    """
    sku: str
    warehouse_id: str
    requested: int
    available: int
    message: str = field(init=False)
    
    def __post_init__(self):
        self.message = (
            f"Insufficient inventory for SKU {self.sku} in warehouse {self.warehouse_id}: "
            f"requested {self.requested}, available {self.available}"
        )
        super().__init__(self.message)
    
    def __str__(self) -> str:
        return self.message
