# app/modules/orders/services/order_inventory_service.py
"""
Order-Inventory Integration Service.

This service orchestrates inventory operations for orders:
- Reserve inventory when orders are confirmed
- Fulfill (deduct) inventory when orders are shipped
- Release reservations when orders are cancelled

All operations are logged to the inventory_transactions table for audit trail.
"""

import logging

from sqlalchemy.orm import Session

from app.modules.inventory.exceptions import (
    InsufficientInventoryException,
    InventoryNotFoundException,
)
from app.modules.inventory.schemas.inventory import InventoryReserve
from app.modules.inventory.services.inventory_service import inventory_service
from app.modules.orders.exceptions import (
    OrderNotFoundException,
    OrderValidationException,
)
from app.modules.orders.models.order import Order, OrderItem

logger = logging.getLogger(__name__)

# Default location for inventory operations
DEFAULT_LOCATION = "DEFAULT"


class OrderInventoryService:
    """
    Orchestrate order and inventory operations together.
    """

    def get_order_with_items(
        self, db: Session, store_id: int, order_id: int
    ) -> Order:
        """Get order with items or raise OrderNotFoundException."""
        order = (
            db.query(Order)
            .filter(Order.id == order_id, Order.store_id == store_id)
            .first()
        )
        if not order:
            raise OrderNotFoundException(f"Order {order_id} not found")
        return order

    def _find_inventory_location(
        self, db: Session, product_id: int, store_id: int
    ) -> str | None:
        """
        Find the location with available inventory for a product.
        """
        from app.modules.inventory.models.inventory import Inventory

        inventory = (
            db.query(Inventory)
            .filter(
                Inventory.product_id == product_id,
                Inventory.store_id == store_id,
                Inventory.quantity > Inventory.reserved_quantity,
            )
            .first()
        )
        return inventory.bin_location if inventory else None

    def _is_placeholder_product(self, order_item: OrderItem) -> bool:
        """Check if the order item uses a placeholder product."""
        if not order_item.product:
            return True
        return order_item.product.gtin == "0000000000000"

    def _log_transaction(
        self,
        db: Session,
        store_id: int,
        product_id: int,
        inventory,
        transaction_type,
        quantity_change: int,
        order: Order,
        reason: str | None = None,
    ):
        """Create an inventory transaction record for audit trail."""
        from app.modules.inventory.models.inventory_transaction import (
            InventoryTransaction,
        )

        transaction = InventoryTransaction.create_transaction(
            store_id=store_id,
            product_id=product_id,
            inventory_id=inventory.id if inventory else None,
            transaction_type=transaction_type,
            quantity_change=quantity_change,
            quantity_after=inventory.quantity if inventory else 0,
            reserved_after=inventory.reserved_quantity if inventory else 0,
            location=inventory.bin_location if inventory else None,
            warehouse=inventory.warehouse if inventory else None,
            order_id=order.id,
            order_number=order.order_number,
            reason=reason,
            created_by="system",
        )
        db.add(transaction)
        return transaction

    def reserve_for_order(
        self,
        db: Session,
        store_id: int,
        order_id: int,
        skip_missing: bool = True,
    ) -> dict:
        """Reserve inventory for all items in an order."""
        from app.modules.inventory.models.inventory_transaction import TransactionType
        order = self.get_order_with_items(db, store_id, order_id)

        reserved_count = 0
        skipped_items = []

        for item in order.items:
            if self._is_placeholder_product(item):
                skipped_items.append({
                    "item_id": item.id,
                    "reason": "placeholder_product",
                })
                continue

            location = self._find_inventory_location(db, item.product_id, store_id)

            if not location:
                if skip_missing:
                    skipped_items.append({
                        "item_id": item.id,
                        "product_id": item.product_id,
                        "reason": "no_inventory",
                    })
                    continue
                raise InventoryNotFoundException(
                    f"No inventory found for product {item.product_id}"
                )

            try:
                reserve_data = InventoryReserve(
                    product_id=item.product_id,
                    location=location,
                    quantity=item.quantity,
                )
                updated_inventory = inventory_service.reserve_inventory(
                    db, store_id, reserve_data
                )
                reserved_count += 1

                self._log_transaction(
                    db=db,
                    store_id=store_id,
                    product_id=item.product_id,
                    inventory=updated_inventory,
                    transaction_type=TransactionType.RESERVE,
                    quantity_change=0,
                    order=order,
                    reason=f"Reserved for order {order.order_number}",
                )

                logger.info(
                    f"Reserved {item.quantity} units of product {item.product_id} "
                    f"for order {order.order_number}"
                )
            except InsufficientInventoryException:
                if skip_missing:
                    skipped_items.append({
                        "item_id": item.id,
                        "product_id": item.product_id,
                        "reason": "insufficient_inventory",
                    })
                else:
                    raise

        logger.info(
            f"Order {order.order_number}: reserved {reserved_count} items, "
            f"skipped {len(skipped_items)}"
        )

        return {
            "order_id": order_id,
            "order_number": order.order_number,
            "reserved_count": reserved_count,
            "skipped_items": skipped_items,
        }

    def fulfill_order(
        self,
        db: Session,
        store_id: int,
        order_id: int,
        skip_missing: bool = True,
    ) -> dict:
        """Fulfill (deduct) inventory when an order is shipped."""
        from app.modules.inventory.models.inventory import Inventory
        from app.modules.inventory.models.inventory_transaction import TransactionType
        order = self.get_order_with_items(db, store_id, order_id)

        fulfilled_count = 0
        skipped_items = []

        for item in order.items:
            if item.is_fully_shipped:
                continue

            if self._is_placeholder_product(item):
                skipped_items.append({
                    "item_id": item.id,
                    "reason": "placeholder_product",
                })
                continue

            quantity_to_fulfill = item.remaining_quantity

            location = self._find_inventory_location(db, item.product_id, store_id)

            if not location:
                inventory = (
                    db.query(Inventory)
                    .filter(
                        Inventory.product_id == item.product_id,
                        Inventory.store_id == store_id,
                    )
                    .first()
                )
                if inventory:
                    location = inventory.bin_location

            if not location:
                if skip_missing:
                    skipped_items.append({
                        "item_id": item.id,
                        "product_id": item.product_id,
                        "reason": "no_inventory",
                    })
                    continue
                raise InventoryNotFoundException(
                    f"No inventory found for product {item.product_id}"
                )

            try:
                reserve_data = InventoryReserve(
                    product_id=item.product_id,
                    location=location,
                    quantity=quantity_to_fulfill,
                )
                updated_inventory = inventory_service.fulfill_reservation(
                    db, store_id, reserve_data
                )
                fulfilled_count += 1

                item.shipped_quantity = item.quantity
                item.inventory_fulfilled = True

                self._log_transaction(
                    db=db,
                    store_id=store_id,
                    product_id=item.product_id,
                    inventory=updated_inventory,
                    transaction_type=TransactionType.FULFILL,
                    quantity_change=-quantity_to_fulfill,
                    order=order,
                    reason=f"Fulfilled for order {order.order_number}",
                )

                logger.info(
                    f"Fulfilled {quantity_to_fulfill} units of product {item.product_id} "
                    f"for order {order.order_number}"
                )
            except (InsufficientInventoryException, InventoryNotFoundException) as e:
                if skip_missing:
                    skipped_items.append({
                        "item_id": item.id,
                        "product_id": item.product_id,
                        "reason": str(e),
                    })
                else:
                    raise

        logger.info(
            f"Order {order.order_number}: fulfilled {fulfilled_count} items, "
            f"skipped {len(skipped_items)}"
        )

        return {
            "order_id": order_id,
            "order_number": order.order_number,
            "fulfilled_count": fulfilled_count,
            "skipped_items": skipped_items,
        }

    def fulfill_item(
        self,
        db: Session,
        store_id: int,
        order_id: int,
        item_id: int,
        quantity: int | None = None,
        skip_missing: bool = True,
    ) -> dict:
        """Fulfill (deduct) inventory for a specific order item."""
        from app.modules.inventory.models.inventory import Inventory
        from app.modules.inventory.models.inventory_transaction import TransactionType
        order = self.get_order_with_items(db, store_id, order_id)

        item = None
        for order_item in order.items:
            if order_item.id == item_id:
                item = order_item
                break

        if not item:
            raise OrderValidationException(f"Item {item_id} not found in order {order_id}")

        if item.is_fully_shipped:
            return {
                "order_id": order_id,
                "item_id": item_id,
                "fulfilled_quantity": 0,
                "message": "Item already fully shipped",
            }

        quantity_to_fulfill = quantity or item.remaining_quantity

        if quantity_to_fulfill > item.remaining_quantity:
            raise OrderValidationException(
                f"Cannot ship {quantity_to_fulfill} units - only {item.remaining_quantity} remaining"
            )

        if quantity_to_fulfill <= 0:
            return {
                "order_id": order_id,
                "item_id": item_id,
                "fulfilled_quantity": 0,
                "message": "Nothing to fulfill",
            }

        if self._is_placeholder_product(item):
            return {
                "order_id": order_id,
                "item_id": item_id,
                "fulfilled_quantity": 0,
                "message": "Placeholder product - skipped",
            }

        location = self._find_inventory_location(db, item.product_id, store_id)

        if not location:
            inventory = (
                db.query(Inventory)
                .filter(
                    Inventory.product_id == item.product_id,
                    Inventory.store_id == store_id,
                )
                .first()
            )
            if inventory:
                location = inventory.bin_location

        if not location:
            if skip_missing:
                return {
                    "order_id": order_id,
                    "item_id": item_id,
                    "fulfilled_quantity": 0,
                    "message": "No inventory found",
                }
            raise InventoryNotFoundException(
                f"No inventory found for product {item.product_id}"
            )

        try:
            reserve_data = InventoryReserve(
                product_id=item.product_id,
                location=location,
                quantity=quantity_to_fulfill,
            )
            updated_inventory = inventory_service.fulfill_reservation(
                db, store_id, reserve_data
            )

            item.shipped_quantity += quantity_to_fulfill

            if item.is_fully_shipped:
                item.inventory_fulfilled = True

            self._log_transaction(
                db=db,
                store_id=store_id,
                product_id=item.product_id,
                inventory=updated_inventory,
                transaction_type=TransactionType.FULFILL,
                quantity_change=-quantity_to_fulfill,
                order=order,
                reason=f"Partial shipment for order {order.order_number}",
            )

            logger.info(
                f"Fulfilled {quantity_to_fulfill} of {item.quantity} units "
                f"for item {item_id} in order {order.order_number}"
            )

            return {
                "order_id": order_id,
                "item_id": item_id,
                "fulfilled_quantity": quantity_to_fulfill,
                "shipped_quantity": item.shipped_quantity,
                "remaining_quantity": item.remaining_quantity,
                "is_fully_shipped": item.is_fully_shipped,
            }

        except (InsufficientInventoryException, InventoryNotFoundException) as e:
            if skip_missing:
                return {
                    "order_id": order_id,
                    "item_id": item_id,
                    "fulfilled_quantity": 0,
                    "message": str(e),
                }
            raise

    def release_order_reservation(
        self,
        db: Session,
        store_id: int,
        order_id: int,
        skip_missing: bool = True,
    ) -> dict:
        """Release reserved inventory when an order is cancelled."""
        from app.modules.inventory.models.inventory import Inventory
        from app.modules.inventory.models.inventory_transaction import TransactionType

        order = self.get_order_with_items(db, store_id, order_id)

        released_count = 0
        skipped_items = []

        for item in order.items:
            if self._is_placeholder_product(item):
                skipped_items.append({
                    "item_id": item.id,
                    "reason": "placeholder_product",
                })
                continue

            inventory = (
                db.query(Inventory)
                .filter(
                    Inventory.product_id == item.product_id,
                    Inventory.store_id == store_id,
                )
                .first()
            )

            if not inventory:
                if skip_missing:
                    skipped_items.append({
                        "item_id": item.id,
                        "product_id": item.product_id,
                        "reason": "no_inventory",
                    })
                    continue
                raise InventoryNotFoundException(
                    f"No inventory found for product {item.product_id}"
                )

            try:
                reserve_data = InventoryReserve(
                    product_id=item.product_id,
                    location=inventory.bin_location,
                    quantity=item.quantity,
                )
                updated_inventory = inventory_service.release_reservation(
                    db, store_id, reserve_data
                )
                released_count += 1

                self._log_transaction(
                    db=db,
                    store_id=store_id,
                    product_id=item.product_id,
                    inventory=updated_inventory,
                    transaction_type=TransactionType.RELEASE,
                    quantity_change=0,
                    order=order,
                    reason=f"Released for cancelled order {order.order_number}",
                )

                logger.info(
                    f"Released {item.quantity} units of product {item.product_id} "
                    f"for cancelled order {order.order_number}"
                )
            except Exception as e:  # noqa: EXC003
                if skip_missing:
                    skipped_items.append({
                        "item_id": item.id,
                        "product_id": item.product_id,
                        "reason": str(e),
                    })
                else:
                    raise

        logger.info(
            f"Order {order.order_number}: released {released_count} items, "
            f"skipped {len(skipped_items)}"
        )

        return {
            "order_id": order_id,
            "order_number": order.order_number,
            "released_count": released_count,
            "skipped_items": skipped_items,
        }

    def handle_status_change(
        self,
        db: Session,
        store_id: int,
        order_id: int,
        old_status: str | None,
        new_status: str,
    ) -> dict | None:
        """Handle inventory operations based on order status changes."""
        if old_status == new_status:
            return None

        result = None

        if new_status == "processing":
            result = self.reserve_for_order(db, store_id, order_id, skip_missing=True)
            logger.info(f"Order {order_id} confirmed: inventory reserved")

        elif new_status == "shipped":
            result = self.fulfill_order(db, store_id, order_id, skip_missing=True)
            logger.info(f"Order {order_id} shipped: inventory fulfilled")

        elif new_status == "partially_shipped":
            logger.info(
                f"Order {order_id} partially shipped: use fulfill_item for item-level fulfillment"
            )
            result = {"order_id": order_id, "status": "partially_shipped"}

        elif new_status == "cancelled":
            if old_status and old_status not in ("cancelled", "refunded"):
                result = self.release_order_reservation(
                    db, store_id, order_id, skip_missing=True
                )
                logger.info(f"Order {order_id} cancelled: reservations released")

        return result

    def get_shipment_status(
        self,
        db: Session,
        store_id: int,
        order_id: int,
    ) -> dict:
        """Get detailed shipment status for an order."""
        order = self.get_order_with_items(db, store_id, order_id)

        items = []
        for item in order.items:
            items.append({
                "item_id": item.id,
                "product_id": item.product_id,
                "product_name": item.product_name,
                "quantity": item.quantity,
                "shipped_quantity": item.shipped_quantity,
                "remaining_quantity": item.remaining_quantity,
                "is_fully_shipped": item.is_fully_shipped,
                "is_partially_shipped": item.is_partially_shipped,
            })

        return {
            "order_id": order_id,
            "order_number": order.order_number,
            "order_status": order.status,
            "is_fully_shipped": order.is_fully_shipped,
            "is_partially_shipped": order.is_partially_shipped,
            "shipped_item_count": order.shipped_item_count,
            "total_item_count": len(order.items),
            "total_shipped_units": order.total_shipped_units,
            "total_ordered_units": order.total_ordered_units,
            "items": items,
        }


# Create service instance
order_inventory_service = OrderInventoryService()
