# app/modules/loyalty/exceptions.py
"""
Loyalty module exceptions.

Custom exceptions for loyalty program operations including
stamp/points management, card operations, and wallet integration.
"""

from typing import Any

from app.exceptions.base import (
    BusinessLogicException,
    ConflictException,
    ResourceNotFoundException,
    ValidationException,
)


class LoyaltyException(BusinessLogicException):
    """Base exception for loyalty module errors."""

    def __init__(
        self,
        message: str,
        error_code: str = "LOYALTY_ERROR",
        details: dict[str, Any] | None = None,
    ):
        super().__init__(message=message, error_code=error_code, details=details)


# =============================================================================
# Program Exceptions
# =============================================================================


class LoyaltyProgramNotFoundException(ResourceNotFoundException):
    """Raised when a loyalty program is not found."""

    def __init__(self, identifier: str):
        super().__init__("LoyaltyProgram", identifier)


class LoyaltyProgramAlreadyExistsException(ConflictException):
    """Raised when store already has a loyalty program."""

    def __init__(self, merchant_id: int):
        super().__init__(
            message=f"Merchant {merchant_id} already has a loyalty program",
            error_code="LOYALTY_PROGRAM_ALREADY_EXISTS",
            details={"merchant_id": merchant_id},
        )


class LoyaltyProgramInactiveException(LoyaltyException):
    """Raised when trying to use an inactive loyalty program."""

    def __init__(self, program_id: int):
        super().__init__(
            message="Loyalty program is not active",
            error_code="LOYALTY_PROGRAM_INACTIVE",
            details={"program_id": program_id},
        )


# =============================================================================
# Card Exceptions
# =============================================================================


class LoyaltyCardNotFoundException(ResourceNotFoundException):
    """Raised when a loyalty card is not found."""

    def __init__(self, identifier: str):
        super().__init__("LoyaltyCard", identifier)


class LoyaltyCardAlreadyExistsException(ConflictException):
    """Raised when customer already has a card for this program."""

    def __init__(self, customer_id: int, program_id: int):
        super().__init__(
            message="Customer already enrolled in this loyalty program",
            error_code="LOYALTY_CARD_ALREADY_EXISTS",
            details={"customer_id": customer_id, "program_id": program_id},
        )


class LoyaltyCardInactiveException(LoyaltyException):
    """Raised when trying to use an inactive loyalty card."""

    def __init__(self, card_id: int):
        super().__init__(
            message="Loyalty card is not active",
            error_code="LOYALTY_CARD_INACTIVE",
            details={"card_id": card_id},
        )


# =============================================================================
# Anti-Fraud Exceptions
# =============================================================================


class StaffPinNotFoundException(ResourceNotFoundException):
    """Raised when a staff PIN is not found."""

    def __init__(self, identifier: str):
        super().__init__("StaffPin", identifier)


class StaffPinRequiredException(LoyaltyException):
    """Raised when staff PIN is required but not provided."""

    def __init__(self):
        super().__init__(
            message="Staff PIN is required for this operation",
            error_code="STAFF_PIN_REQUIRED",
        )


class InvalidStaffPinException(LoyaltyException):
    """Raised when staff PIN is invalid."""

    def __init__(self, remaining_attempts: int | None = None):
        details = {}
        if remaining_attempts is not None:
            details["remaining_attempts"] = remaining_attempts
        super().__init__(
            message="Invalid staff PIN",
            error_code="INVALID_STAFF_PIN",
            details=details if details else None,
        )


class StaffPinLockedException(LoyaltyException):
    """Raised when staff PIN is locked due to too many failed attempts."""

    def __init__(self, locked_until: str):
        super().__init__(
            message="Staff PIN is locked due to too many failed attempts",
            error_code="STAFF_PIN_LOCKED",
            details={"locked_until": locked_until},
        )


class StampCooldownException(LoyaltyException):
    """Raised when trying to stamp before cooldown period ends."""

    def __init__(self, cooldown_ends: str, cooldown_minutes: int):
        super().__init__(
            message=f"Please wait {cooldown_minutes} minutes between stamps",
            error_code="STAMP_COOLDOWN",
            details={"cooldown_ends": cooldown_ends, "cooldown_minutes": cooldown_minutes},
        )


class DailyStampLimitException(LoyaltyException):
    """Raised when daily stamp limit is exceeded."""

    def __init__(self, max_daily_stamps: int, stamps_today: int):
        super().__init__(
            message=f"Daily stamp limit of {max_daily_stamps} reached",
            error_code="DAILY_STAMP_LIMIT",
            details={"max_daily_stamps": max_daily_stamps, "stamps_today": stamps_today},
        )


# =============================================================================
# Redemption Exceptions
# =============================================================================


class InsufficientStampsException(LoyaltyException):
    """Raised when card doesn't have enough stamps to redeem."""

    def __init__(self, current_stamps: int, required_stamps: int):
        super().__init__(
            message=f"Insufficient stamps: {current_stamps}/{required_stamps}",
            error_code="INSUFFICIENT_STAMPS",
            details={"current_stamps": current_stamps, "required_stamps": required_stamps},
        )


class InsufficientPointsException(LoyaltyException):
    """Raised when card doesn't have enough points to redeem."""

    def __init__(self, current_points: int, required_points: int):
        super().__init__(
            message=f"Insufficient points: {current_points}/{required_points}",
            error_code="INSUFFICIENT_POINTS",
            details={"current_points": current_points, "required_points": required_points},
        )


class InvalidRewardException(LoyaltyException):
    """Raised when trying to redeem an invalid or unavailable reward."""

    def __init__(self, reward_id: str):
        super().__init__(
            message="Invalid or unavailable reward",
            error_code="INVALID_REWARD",
            details={"reward_id": reward_id},
        )


# =============================================================================
# Wallet Exceptions
# =============================================================================


class WalletIntegrationException(LoyaltyException):
    """Raised when wallet integration fails."""

    def __init__(self, provider: str, message: str):
        super().__init__(
            message=f"Wallet integration error: {message}",
            error_code="WALLET_INTEGRATION_ERROR",
            details={"provider": provider},
        )


class GoogleWalletNotConfiguredException(LoyaltyException):
    """Raised when Google Wallet is not configured."""

    def __init__(self):
        super().__init__(
            message="Google Wallet is not configured for this program",
            error_code="GOOGLE_WALLET_NOT_CONFIGURED",
        )


class AppleWalletNotConfiguredException(LoyaltyException):
    """Raised when Apple Wallet is not configured."""

    def __init__(self):
        super().__init__(
            message="Apple Wallet is not configured for this program",
            error_code="APPLE_WALLET_NOT_CONFIGURED",
        )


# =============================================================================
# Authentication Exceptions
# =============================================================================


class InvalidAppleAuthTokenException(LoyaltyException):
    """Raised when Apple Wallet auth token is invalid."""

    def __init__(self):
        super().__init__(
            message="Invalid Apple Wallet authentication token",
            error_code="INVALID_APPLE_AUTH_TOKEN",
        )
        self.status_code = 401


class ApplePassGenerationException(LoyaltyException):
    """Raised when Apple Wallet pass generation fails."""

    def __init__(self, card_id: int):
        super().__init__(
            message="Failed to generate Apple Wallet pass",
            error_code="APPLE_PASS_GENERATION_FAILED",
            details={"card_id": card_id},
        )
        self.status_code = 500


class DeviceRegistrationException(LoyaltyException):
    """Raised when Apple Wallet device registration/unregistration fails."""

    def __init__(self, device_id: str, operation: str = "register"):
        super().__init__(
            message=f"Failed to {operation} device",
            error_code="DEVICE_REGISTRATION_FAILED",
            details={"device_id": device_id, "operation": operation},
        )
        self.status_code = 500


# =============================================================================
# Enrollment Exceptions
# =============================================================================


class SelfEnrollmentDisabledException(LoyaltyException):
    """Raised when self-enrollment is not allowed."""

    def __init__(self):
        super().__init__(
            message="Self-enrollment is not available",
            error_code="SELF_ENROLLMENT_DISABLED",
        )
        self.status_code = 403


class CustomerNotFoundByEmailException(LoyaltyException):
    """Raised when customer is not found by email during enrollment."""

    def __init__(self, email: str):
        super().__init__(
            message="Customer not found with provided email",
            error_code="CUSTOMER_NOT_FOUND_BY_EMAIL",
            details={"email": email},
        )


class CustomerIdentifierRequiredException(LoyaltyException):
    """Raised when neither customer_id nor email is provided."""

    def __init__(self):
        super().__init__(
            message="Either customer_id or email is required",
            error_code="CUSTOMER_IDENTIFIER_REQUIRED",
        )


# =============================================================================
# Order Exceptions
# =============================================================================


class OrderReferenceRequiredException(LoyaltyException):
    """Raised when order reference is required but not provided."""

    def __init__(self):
        super().__init__(
            message="Order reference required",
            error_code="ORDER_REFERENCE_REQUIRED",
        )


# =============================================================================
# Terminal Device Exceptions
# =============================================================================


class TerminalDeviceNotFoundException(ResourceNotFoundException):
    """Raised when a terminal device is not found."""

    def __init__(self, identifier: str):
        super().__init__("TerminalDevice", identifier)


class TerminalDeviceRevokedException(LoyaltyException):
    """Raised when a request presents a revoked terminal device token."""

    def __init__(self, device_id: int):
        super().__init__(
            message="Terminal device has been revoked",
            error_code="TERMINAL_DEVICE_REVOKED",
            details={"device_id": device_id},
        )
        self.status_code = 401


class TerminalDeviceExpiredException(LoyaltyException):
    """Raised when a request presents an expired terminal device token."""

    def __init__(self, device_id: int):
        super().__init__(
            message="Terminal device token has expired — re-pair the tablet",
            error_code="TERMINAL_DEVICE_EXPIRED",
            details={"device_id": device_id},
        )
        self.status_code = 401


# =============================================================================
# Validation Exceptions
# =============================================================================


class LoyaltyValidationException(ValidationException):  # noqa: MOD025
    """Raised when loyalty data validation fails."""

    def __init__(
        self,
        message: str = "Loyalty validation failed",
        field: str | None = None,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(message=message, field=field, details=details)
        self.error_code = "LOYALTY_VALIDATION_FAILED"


__all__ = [
    # Base
    "LoyaltyException",
    # Program
    "LoyaltyProgramNotFoundException",
    "LoyaltyProgramAlreadyExistsException",
    "LoyaltyProgramInactiveException",
    # Card
    "LoyaltyCardNotFoundException",
    "LoyaltyCardAlreadyExistsException",
    "LoyaltyCardInactiveException",
    # Anti-Fraud
    "StaffPinNotFoundException",
    "StaffPinRequiredException",
    "InvalidStaffPinException",
    "StaffPinLockedException",
    "StampCooldownException",
    "DailyStampLimitException",
    # Redemption
    "InsufficientStampsException",
    "InsufficientPointsException",
    "InvalidRewardException",
    # Wallet
    "WalletIntegrationException",
    "GoogleWalletNotConfiguredException",
    "AppleWalletNotConfiguredException",
    # Authentication
    "InvalidAppleAuthTokenException",
    "ApplePassGenerationException",
    "DeviceRegistrationException",
    # Enrollment
    "SelfEnrollmentDisabledException",
    "CustomerNotFoundByEmailException",
    "CustomerIdentifierRequiredException",
    # Order
    "OrderReferenceRequiredException",
    # Terminal Device
    "TerminalDeviceNotFoundException",
    "TerminalDeviceRevokedException",
    "TerminalDeviceExpiredException",
    # Validation
    "LoyaltyValidationException",
]
