# app/modules/orders/services/order_item_exception_service.py
"""
Service for managing order item exceptions (unmatched products).

This service handles:
- Creating exceptions when products are not found during order import
- Resolving exceptions by assigning products
- Auto-matching when new products are imported
- Querying and statistics for exceptions
"""

import logging
from datetime import UTC, datetime

from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session, joinedload

from app.modules.catalog.exceptions import ProductNotFoundException
from app.modules.orders.exceptions import (
    ExceptionAlreadyResolvedException,
    InvalidProductForExceptionException,
    OrderItemExceptionNotFoundException,
)
from app.modules.orders.models.order import Order, OrderItem
from app.modules.orders.models.order_item_exception import OrderItemException

logger = logging.getLogger(__name__)


class OrderItemExceptionService:
    """Service for order item exception CRUD and resolution workflow."""

    # =========================================================================
    # Exception Creation
    # =========================================================================

    def create_exception(
        self,
        db: Session,
        order_item: OrderItem,
        store_id: int,
        original_gtin: str | None,
        original_product_name: str | None,
        original_sku: str | None,
        exception_type: str = "product_not_found",
    ) -> OrderItemException:
        """Create an exception record for an unmatched order item."""
        exception = OrderItemException(
            order_item_id=order_item.id,
            store_id=store_id,
            original_gtin=original_gtin,
            original_product_name=original_product_name,
            original_sku=original_sku,
            exception_type=exception_type,
            status="pending",
        )
        db.add(exception)
        db.flush()

        logger.info(
            f"Created order item exception {exception.id} for order item "
            f"{order_item.id}, GTIN: {original_gtin}"
        )

        return exception

    # =========================================================================
    # Exception Retrieval
    # =========================================================================

    def get_exception_by_id(
        self,
        db: Session,
        exception_id: int,
        store_id: int | None = None,
    ) -> OrderItemException:
        """Get an exception by ID, optionally filtered by store."""
        query = db.query(OrderItemException).filter(
            OrderItemException.id == exception_id
        )

        if store_id is not None:
            query = query.filter(OrderItemException.store_id == store_id)

        exception = query.first()

        if not exception:
            raise OrderItemExceptionNotFoundException(exception_id)

        return exception

    def get_pending_exceptions(
        self,
        db: Session,
        store_id: int | None = None,
        status: str | None = None,
        search: str | None = None,
        skip: int = 0,
        limit: int = 50,
    ) -> tuple[list[OrderItemException], int]:
        """Get exceptions with pagination and filtering."""
        query = (
            db.query(OrderItemException)
            .join(OrderItem)
            .join(Order)
            .options(
                joinedload(OrderItemException.order_item).joinedload(OrderItem.order)
            )
        )

        if store_id is not None:
            query = query.filter(OrderItemException.store_id == store_id)

        if status:
            query = query.filter(OrderItemException.status == status)

        if search:
            search_pattern = f"%{search}%"
            query = query.filter(
                or_(
                    OrderItemException.original_gtin.ilike(search_pattern),
                    OrderItemException.original_product_name.ilike(search_pattern),
                    OrderItemException.original_sku.ilike(search_pattern),
                    Order.order_number.ilike(search_pattern),
                )
            )

        total = query.count()

        exceptions = (
            query.order_by(OrderItemException.created_at.desc())
            .offset(skip)
            .limit(limit)
            .all()
        )

        return exceptions, total

    def get_exceptions_for_order(
        self,
        db: Session,
        order_id: int,
    ) -> list[OrderItemException]:
        """Get all exceptions for items in an order."""
        return (
            db.query(OrderItemException)
            .join(OrderItem)
            .filter(OrderItem.order_id == order_id)
            .all()
        )

    # =========================================================================
    # Exception Statistics
    # =========================================================================

    def get_exception_stats(
        self,
        db: Session,
        store_id: int | None = None,
    ) -> dict[str, int]:
        """Get exception counts by status."""
        query = db.query(
            OrderItemException.status,
            func.count(OrderItemException.id).label("count"),
        )

        if store_id is not None:
            query = query.filter(OrderItemException.store_id == store_id)

        results = query.group_by(OrderItemException.status).all()

        stats = {
            "pending": 0,
            "resolved": 0,
            "ignored": 0,
            "total": 0,
        }

        for status, count in results:
            if status in stats:
                stats[status] = count
            stats["total"] += count

        orders_query = (
            db.query(func.count(func.distinct(OrderItem.order_id)))
            .join(OrderItemException)
            .filter(OrderItemException.status == "pending")
        )

        if store_id is not None:
            orders_query = orders_query.filter(
                OrderItemException.store_id == store_id
            )

        stats["orders_with_exceptions"] = orders_query.scalar() or 0

        return stats

    # =========================================================================
    # Exception Resolution
    # =========================================================================

    def resolve_exception(
        self,
        db: Session,
        exception_id: int,
        product_id: int,
        resolved_by: int,
        notes: str | None = None,
        store_id: int | None = None,
    ) -> OrderItemException:
        """Resolve an exception by assigning a product."""
        from app.modules.catalog.services.product_service import product_service

        exception = self.get_exception_by_id(db, exception_id, store_id)

        if exception.status == "resolved":
            raise ExceptionAlreadyResolvedException(exception_id)

        product = product_service.get_product_by_id(db, product_id)
        if not product:
            raise ProductNotFoundException(product_id)

        if product.store_id != exception.store_id:
            raise InvalidProductForExceptionException(
                product_id, "Product belongs to a different store"
            )

        if not product.is_active:
            raise InvalidProductForExceptionException(
                product_id, "Product is not active"
            )

        exception.status = "resolved"
        exception.resolved_product_id = product_id
        exception.resolved_at = datetime.now(UTC)
        exception.resolved_by = resolved_by
        exception.resolution_notes = notes

        order_item = exception.order_item
        order_item.product_id = product_id
        order_item.needs_product_match = False

        if product.marketplace_product:
            order_item.product_name = product.marketplace_product.get_title("en")
        order_item.product_sku = product.store_sku or order_item.product_sku

        db.flush()

        logger.info(
            f"Resolved exception {exception_id} with product {product_id} "
            f"by user {resolved_by}"
        )

        return exception

    def ignore_exception(
        self,
        db: Session,
        exception_id: int,
        resolved_by: int,
        notes: str,
        store_id: int | None = None,
    ) -> OrderItemException:
        """Mark an exception as ignored."""
        exception = self.get_exception_by_id(db, exception_id, store_id)

        if exception.status == "resolved":
            raise ExceptionAlreadyResolvedException(exception_id)

        exception.status = "ignored"
        exception.resolved_at = datetime.now(UTC)
        exception.resolved_by = resolved_by
        exception.resolution_notes = notes

        db.flush()

        logger.info(
            f"Ignored exception {exception_id} by user {resolved_by}: {notes}"
        )

        return exception

    # =========================================================================
    # Auto-Matching
    # =========================================================================

    def auto_match_by_gtin(
        self,
        db: Session,
        store_id: int,
        gtin: str,
        product_id: int,
    ) -> list[OrderItemException]:
        """Auto-resolve pending exceptions matching a GTIN."""
        if not gtin:
            return []

        pending = (
            db.query(OrderItemException)
            .filter(
                and_(
                    OrderItemException.store_id == store_id,
                    OrderItemException.original_gtin == gtin,
                    OrderItemException.status == "pending",
                )
            )
            .all()
        )

        if not pending:
            return []

        from app.modules.catalog.services.product_service import product_service

        product = product_service.get_product_by_id(db, product_id)
        if not product:
            logger.warning(f"Product {product_id} not found for auto-match")
            return []

        resolved = []
        now = datetime.now(UTC)

        for exception in pending:
            exception.status = "resolved"
            exception.resolved_product_id = product_id
            exception.resolved_at = now
            exception.resolution_notes = "Auto-matched during product import"

            order_item = exception.order_item
            order_item.product_id = product_id
            order_item.needs_product_match = False
            if product.marketplace_product:
                order_item.product_name = product.marketplace_product.get_title("en")

            resolved.append(exception)

        if resolved:
            db.flush()
            logger.info(
                f"Auto-matched {len(resolved)} exceptions for GTIN {gtin} "
                f"with product {product_id}"
            )

        return resolved

    def auto_match_batch(
        self,
        db: Session,
        store_id: int,
        gtin_to_product: dict[str, int],
    ) -> int:
        """Batch auto-match multiple GTINs after bulk import."""
        if not gtin_to_product:
            return 0

        total_resolved = 0

        for gtin, product_id in gtin_to_product.items():
            resolved = self.auto_match_by_gtin(db, store_id, gtin, product_id)
            total_resolved += len(resolved)

        return total_resolved

    # =========================================================================
    # Confirmation Checks
    # =========================================================================

    def order_has_unresolved_exceptions(
        self,
        db: Session,
        order_id: int,
    ) -> bool:
        """Check if order has any unresolved exceptions."""
        count = (
            db.query(func.count(OrderItemException.id))
            .join(OrderItem)
            .filter(
                and_(
                    OrderItem.order_id == order_id,
                    OrderItemException.status.in_(["pending", "ignored"]),
                )
            )
            .scalar()
        )

        return count > 0

    def get_unresolved_exception_count(
        self,
        db: Session,
        order_id: int,
    ) -> int:
        """Get count of unresolved exceptions for an order."""
        return (
            db.query(func.count(OrderItemException.id))
            .join(OrderItem)
            .filter(
                and_(
                    OrderItem.order_id == order_id,
                    OrderItemException.status.in_(["pending", "ignored"]),
                )
            )
            .scalar()
        ) or 0

    # =========================================================================
    # Bulk Operations
    # =========================================================================

    def bulk_resolve_by_gtin(
        self,
        db: Session,
        store_id: int,
        gtin: str,
        product_id: int,
        resolved_by: int,
        notes: str | None = None,
    ) -> int:
        """Bulk resolve all pending exceptions for a GTIN."""
        from app.modules.catalog.services.product_service import product_service

        product = product_service.get_product_by_id(db, product_id)
        if not product:
            raise ProductNotFoundException(product_id)

        if product.store_id != store_id:
            raise InvalidProductForExceptionException(
                product_id, "Product belongs to a different store"
            )

        pending = (
            db.query(OrderItemException)
            .filter(
                and_(
                    OrderItemException.store_id == store_id,
                    OrderItemException.original_gtin == gtin,
                    OrderItemException.status == "pending",
                )
            )
            .all()
        )

        now = datetime.now(UTC)
        resolution_notes = notes or f"Bulk resolved for GTIN {gtin}"

        for exception in pending:
            exception.status = "resolved"
            exception.resolved_product_id = product_id
            exception.resolved_at = now
            exception.resolved_by = resolved_by
            exception.resolution_notes = resolution_notes

            order_item = exception.order_item
            order_item.product_id = product_id
            order_item.needs_product_match = False
            if product.marketplace_product:
                order_item.product_name = product.marketplace_product.get_title("en")

        db.flush()

        logger.info(
            f"Bulk resolved {len(pending)} exceptions for GTIN {gtin} "
            f"with product {product_id} by user {resolved_by}"
        )

        return len(pending)


# Global service instance
order_item_exception_service = OrderItemExceptionService()
