# app/services/inventory_service.py
import logging
from datetime import UTC, datetime

from sqlalchemy.orm import Session

from app.exceptions import (
    InsufficientInventoryException,
    InvalidQuantityException,
    InventoryNotFoundException,
    InventoryValidationException,
    ProductNotFoundException,
    ValidationException,
)
from models.database.inventory import Inventory
from models.database.product import Product
from models.schema.inventory import (
    InventoryAdjust,
    InventoryCreate,
    InventoryLocationResponse,
    InventoryReserve,
    InventoryUpdate,
    ProductInventorySummary,
)

logger = logging.getLogger(__name__)


class InventoryService:
    """Service for inventory operations with vendor isolation."""

    def set_inventory(
        self, db: Session, vendor_id: int, inventory_data: InventoryCreate
    ) -> Inventory:
        """
        Set exact inventory quantity for a product at a location (replaces existing).

        Args:
            db: Database session
            vendor_id: Vendor ID (from middleware)
            inventory_data: Inventory data

        Returns:
            Inventory object
        """
        try:
            # Validate product belongs to vendor
            product = self._get_vendor_product(db, vendor_id, inventory_data.product_id)

            # Validate location
            location = self._validate_location(inventory_data.location)

            # Validate quantity
            self._validate_quantity(inventory_data.quantity, allow_zero=True)

            # Check if inventory entry exists
            existing = self._get_inventory_entry(
                db, inventory_data.product_id, location
            )

            if existing:
                old_qty = existing.quantity
                existing.quantity = inventory_data.quantity
                existing.updated_at = datetime.now(UTC)
                db.flush()
                db.refresh(existing)

                logger.info(
                    f"Set inventory for product {inventory_data.product_id} at {location}: "
                    f"{old_qty} → {inventory_data.quantity}"
                )
                return existing
            # Create new inventory entry
            new_inventory = Inventory(
                product_id=inventory_data.product_id,
                vendor_id=vendor_id,
                location=location,
                quantity=inventory_data.quantity,
                gtin=product.marketplace_product.gtin,  # Optional reference
            )
            db.add(new_inventory)
            db.flush()
            db.refresh(new_inventory)

            logger.info(
                f"Created inventory for product {inventory_data.product_id} at {location}: "
                f"{inventory_data.quantity}"
            )
            return new_inventory

        except (
            ProductNotFoundException,
            InvalidQuantityException,
            InventoryValidationException,
        ):
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"Error setting inventory: {str(e)}")
            raise ValidationException("Failed to set inventory")

    def adjust_inventory(
        self, db: Session, vendor_id: int, inventory_data: InventoryAdjust
    ) -> Inventory:
        """
        Adjust inventory by adding or removing quantity.
        Positive quantity = add, negative = remove.

        Args:
            db: Database session
            vendor_id: Vendor ID
            inventory_data: Adjustment data

        Returns:
            Updated Inventory object
        """
        try:
            # Validate product belongs to vendor
            product = self._get_vendor_product(db, vendor_id, inventory_data.product_id)

            # Validate location
            location = self._validate_location(inventory_data.location)

            # Check if inventory exists
            existing = self._get_inventory_entry(
                db, inventory_data.product_id, location
            )

            if not existing:
                # Create new if adding, error if removing
                if inventory_data.quantity < 0:
                    raise InventoryNotFoundException(
                        f"No inventory found for product {inventory_data.product_id} at {location}"
                    )

                # Create with positive quantity
                new_inventory = Inventory(
                    product_id=inventory_data.product_id,
                    vendor_id=vendor_id,
                    location=location,
                    quantity=inventory_data.quantity,
                    gtin=product.marketplace_product.gtin,
                )
                db.add(new_inventory)
                db.flush()
                db.refresh(new_inventory)

                logger.info(
                    f"Created inventory for product {inventory_data.product_id} at {location}: "
                    f"+{inventory_data.quantity}"
                )
                return new_inventory

            # Adjust existing inventory
            old_qty = existing.quantity
            new_qty = old_qty + inventory_data.quantity

            # Validate resulting quantity
            if new_qty < 0:
                raise InsufficientInventoryException(
                    f"Insufficient inventory. Available: {old_qty}, "
                    f"Requested removal: {abs(inventory_data.quantity)}"
                )

            existing.quantity = new_qty
            existing.updated_at = datetime.now(UTC)
            db.flush()
            db.refresh(existing)

            logger.info(
                f"Adjusted inventory for product {inventory_data.product_id} at {location}: "
                f"{old_qty} {'+' if inventory_data.quantity >= 0 else ''}{inventory_data.quantity} = {new_qty}"
            )
            return existing

        except (
            ProductNotFoundException,
            InventoryNotFoundException,
            InsufficientInventoryException,
            InventoryValidationException,
        ):
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"Error adjusting inventory: {str(e)}")
            raise ValidationException("Failed to adjust inventory")

    def reserve_inventory(
        self, db: Session, vendor_id: int, reserve_data: InventoryReserve
    ) -> Inventory:
        """
        Reserve inventory for an order (increases reserved_quantity).

        Args:
            db: Database session
            vendor_id: Vendor ID
            reserve_data: Reservation data

        Returns:
            Updated Inventory object
        """
        try:
            # Validate product
            product = self._get_vendor_product(db, vendor_id, reserve_data.product_id)

            # Validate location and quantity
            location = self._validate_location(reserve_data.location)
            self._validate_quantity(reserve_data.quantity, allow_zero=False)

            # Get inventory entry
            inventory = self._get_inventory_entry(db, reserve_data.product_id, location)
            if not inventory:
                raise InventoryNotFoundException(
                    f"No inventory found for product {reserve_data.product_id} at {location}"
                )

            # Check available quantity
            available = inventory.quantity - inventory.reserved_quantity
            if available < reserve_data.quantity:
                raise InsufficientInventoryException(
                    f"Insufficient available inventory. Available: {available}, "
                    f"Requested: {reserve_data.quantity}"
                )

            # Reserve inventory
            inventory.reserved_quantity += reserve_data.quantity
            inventory.updated_at = datetime.now(UTC)
            db.flush()
            db.refresh(inventory)

            logger.info(
                f"Reserved {reserve_data.quantity} units for product {reserve_data.product_id} "
                f"at {location}"
            )
            return inventory

        except (
            ProductNotFoundException,
            InventoryNotFoundException,
            InsufficientInventoryException,
            InvalidQuantityException,
        ):
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"Error reserving inventory: {str(e)}")
            raise ValidationException("Failed to reserve inventory")

    def release_reservation(
        self, db: Session, vendor_id: int, reserve_data: InventoryReserve
    ) -> Inventory:
        """
        Release reserved inventory (decreases reserved_quantity).

        Args:
            db: Database session
            vendor_id: Vendor ID
            reserve_data: Reservation data

        Returns:
            Updated Inventory object
        """
        try:
            # Validate product
            product = self._get_vendor_product(db, vendor_id, reserve_data.product_id)

            location = self._validate_location(reserve_data.location)
            self._validate_quantity(reserve_data.quantity, allow_zero=False)

            inventory = self._get_inventory_entry(db, reserve_data.product_id, location)
            if not inventory:
                raise InventoryNotFoundException(
                    f"No inventory found for product {reserve_data.product_id} at {location}"
                )

            # Validate reserved quantity
            if inventory.reserved_quantity < reserve_data.quantity:
                logger.warning(
                    f"Attempting to release more than reserved. Reserved: {inventory.reserved_quantity}, "
                    f"Requested: {reserve_data.quantity}"
                )
                inventory.reserved_quantity = 0
            else:
                inventory.reserved_quantity -= reserve_data.quantity

            inventory.updated_at = datetime.now(UTC)
            db.flush()
            db.refresh(inventory)

            logger.info(
                f"Released {reserve_data.quantity} units for product {reserve_data.product_id} "
                f"at {location}"
            )
            return inventory

        except (
            ProductNotFoundException,
            InventoryNotFoundException,
            InvalidQuantityException,
        ):
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"Error releasing reservation: {str(e)}")
            raise ValidationException("Failed to release reservation")

    def fulfill_reservation(
        self, db: Session, vendor_id: int, reserve_data: InventoryReserve
    ) -> Inventory:
        """
        Fulfill a reservation (decreases both quantity and reserved_quantity).
        Use when order is shipped/completed.

        Args:
            db: Database session
            vendor_id: Vendor ID
            reserve_data: Reservation data

        Returns:
            Updated Inventory object
        """
        try:
            product = self._get_vendor_product(db, vendor_id, reserve_data.product_id)
            location = self._validate_location(reserve_data.location)
            self._validate_quantity(reserve_data.quantity, allow_zero=False)

            inventory = self._get_inventory_entry(db, reserve_data.product_id, location)
            if not inventory:
                raise InventoryNotFoundException(
                    f"No inventory found for product {reserve_data.product_id} at {location}"
                )

            # Validate quantities
            if inventory.quantity < reserve_data.quantity:
                raise InsufficientInventoryException(
                    f"Insufficient inventory. Available: {inventory.quantity}, "
                    f"Requested: {reserve_data.quantity}"
                )

            if inventory.reserved_quantity < reserve_data.quantity:
                logger.warning(
                    f"Fulfilling more than reserved. Reserved: {inventory.reserved_quantity}, "
                    f"Fulfilling: {reserve_data.quantity}"
                )

            # Fulfill (remove from both quantity and reserved)
            inventory.quantity -= reserve_data.quantity
            inventory.reserved_quantity = max(
                0, inventory.reserved_quantity - reserve_data.quantity
            )
            inventory.updated_at = datetime.now(UTC)
            db.flush()
            db.refresh(inventory)

            logger.info(
                f"Fulfilled {reserve_data.quantity} units for product {reserve_data.product_id} "
                f"at {location}"
            )
            return inventory

        except (
            ProductNotFoundException,
            InventoryNotFoundException,
            InsufficientInventoryException,
            InvalidQuantityException,
        ):
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"Error fulfilling reservation: {str(e)}")
            raise ValidationException("Failed to fulfill reservation")

    def get_product_inventory(
        self, db: Session, vendor_id: int, product_id: int
    ) -> ProductInventorySummary:
        """
        Get inventory summary for a product across all locations.

        Args:
            db: Database session
            vendor_id: Vendor ID
            product_id: Product ID

        Returns:
            ProductInventorySummary
        """
        try:
            product = self._get_vendor_product(db, vendor_id, product_id)

            inventory_entries = (
                db.query(Inventory).filter(Inventory.product_id == product_id).all()
            )

            if not inventory_entries:
                return ProductInventorySummary(
                    product_id=product_id,
                    vendor_id=vendor_id,
                    product_sku=product.product_id,
                    product_title=product.marketplace_product.title,
                    total_quantity=0,
                    total_reserved=0,
                    total_available=0,
                    locations=[],
                )

            total_qty = sum(inv.quantity for inv in inventory_entries)
            total_reserved = sum(inv.reserved_quantity for inv in inventory_entries)
            total_available = sum(inv.available_quantity for inv in inventory_entries)

            locations = [
                InventoryLocationResponse(
                    location=inv.location,
                    quantity=inv.quantity,
                    reserved_quantity=inv.reserved_quantity,
                    available_quantity=inv.available_quantity,
                )
                for inv in inventory_entries
            ]

            return ProductInventorySummary(
                product_id=product_id,
                vendor_id=vendor_id,
                product_sku=product.product_id,
                product_title=product.marketplace_product.title,
                total_quantity=total_qty,
                total_reserved=total_reserved,
                total_available=total_available,
                locations=locations,
            )

        except ProductNotFoundException:
            raise
        except Exception as e:
            logger.error(f"Error getting product inventory: {str(e)}")
            raise ValidationException("Failed to retrieve product inventory")

    def get_vendor_inventory(
        self,
        db: Session,
        vendor_id: int,
        skip: int = 0,
        limit: int = 100,
        location: str | None = None,
        low_stock_threshold: int | None = None,
    ) -> list[Inventory]:
        """
        Get all inventory for a vendor with filtering.

        Args:
            db: Database session
            vendor_id: Vendor ID
            skip: Pagination offset
            limit: Pagination limit
            location: Filter by location
            low_stock_threshold: Filter items below threshold

        Returns:
            List of Inventory objects
        """
        try:
            query = db.query(Inventory).filter(Inventory.vendor_id == vendor_id)

            if location:
                query = query.filter(Inventory.location.ilike(f"%{location}%"))

            if low_stock_threshold is not None:
                query = query.filter(Inventory.quantity <= low_stock_threshold)

            return query.offset(skip).limit(limit).all()

        except Exception as e:
            logger.error(f"Error getting vendor inventory: {str(e)}")
            raise ValidationException("Failed to retrieve vendor inventory")

    def update_inventory(
        self,
        db: Session,
        vendor_id: int,
        inventory_id: int,
        inventory_update: InventoryUpdate,
    ) -> Inventory:
        """Update inventory entry."""
        try:
            inventory = self._get_inventory_by_id(db, inventory_id)

            # Verify ownership
            if inventory.vendor_id != vendor_id:
                raise InventoryNotFoundException(f"Inventory {inventory_id} not found")

            # Update fields
            if inventory_update.quantity is not None:
                self._validate_quantity(inventory_update.quantity, allow_zero=True)
                inventory.quantity = inventory_update.quantity

            if inventory_update.reserved_quantity is not None:
                self._validate_quantity(
                    inventory_update.reserved_quantity, allow_zero=True
                )
                inventory.reserved_quantity = inventory_update.reserved_quantity

            if inventory_update.location:
                inventory.location = self._validate_location(inventory_update.location)

            inventory.updated_at = datetime.now(UTC)
            db.flush()
            db.refresh(inventory)

            logger.info(f"Updated inventory {inventory_id}")
            return inventory

        except (
            InventoryNotFoundException,
            InvalidQuantityException,
            InventoryValidationException,
        ):
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"Error updating inventory: {str(e)}")
            raise ValidationException("Failed to update inventory")

    def delete_inventory(self, db: Session, vendor_id: int, inventory_id: int) -> bool:
        """Delete inventory entry."""
        try:
            inventory = self._get_inventory_by_id(db, inventory_id)

            # Verify ownership
            if inventory.vendor_id != vendor_id:
                raise InventoryNotFoundException(f"Inventory {inventory_id} not found")

            db.delete(inventory)
            db.flush()

            logger.info(f"Deleted inventory {inventory_id}")
            return True

        except InventoryNotFoundException:
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"Error deleting inventory: {str(e)}")
            raise ValidationException("Failed to delete inventory")

    # Private helper methods
    def _get_vendor_product(
        self, db: Session, vendor_id: int, product_id: int
    ) -> Product:
        """Get product and verify it belongs to vendor."""
        product = (
            db.query(Product)
            .filter(Product.id == product_id, Product.vendor_id == vendor_id)
            .first()
        )

        if not product:
            raise ProductNotFoundException(
                f"Product {product_id} not found in your catalog"
            )

        return product

    def _get_inventory_entry(
        self, db: Session, product_id: int, location: str
    ) -> Inventory | None:
        """Get inventory entry by product and location."""
        return (
            db.query(Inventory)
            .filter(Inventory.product_id == product_id, Inventory.location == location)
            .first()
        )

    def _get_inventory_by_id(self, db: Session, inventory_id: int) -> Inventory:
        """Get inventory by ID or raise exception."""
        inventory = db.query(Inventory).filter(Inventory.id == inventory_id).first()
        if not inventory:
            raise InventoryNotFoundException(f"Inventory {inventory_id} not found")
        return inventory

    def _validate_location(self, location: str) -> str:
        """Validate and normalize location."""
        if not location or not location.strip():
            raise InventoryValidationException("Location is required")
        return location.strip().upper()

    def _validate_quantity(self, quantity: int, allow_zero: bool = True) -> None:
        """Validate quantity value."""
        if quantity is None:
            raise InvalidQuantityException("Quantity is required")

        if not isinstance(quantity, int):
            raise InvalidQuantityException("Quantity must be an integer")

        if quantity < 0:
            raise InvalidQuantityException("Quantity cannot be negative")

        if not allow_zero and quantity == 0:
            raise InvalidQuantityException("Quantity must be positive")


# Create service instance
inventory_service = InventoryService()
