# app/modules/marketplace/exceptions.py
"""
Marketplace module exceptions.

This module provides exception classes for marketplace operations including:
- Letzshop integration (client, authentication, credentials)
- Product import/export
- Sync operations
- Onboarding wizard
"""

from typing import Any

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

__all__ = [
    # Base exception
    "MarketplaceException",
    # Letzshop client exceptions
    "LetzshopClientError",
    "LetzshopAuthenticationError",
    "LetzshopCredentialsNotFoundException",
    "LetzshopConnectionFailedException",
    # Import job exceptions
    "ImportJobNotFoundException",
    "HistoricalImportJobNotFoundException",
    "ImportJobNotOwnedException",
    "ImportJobCannotBeCancelledException",
    "ImportJobCannotBeDeletedException",
    "ImportJobAlreadyProcessingException",
    "ImportValidationError",
    "InvalidImportDataException",
    "ImportRateLimitException",
    # Marketplace exceptions
    "MarketplaceImportException",
    "MarketplaceConnectionException",
    "MarketplaceDataParsingException",
    "InvalidMarketplaceException",
    # Product exceptions
    "StoreNotFoundException",
    "ProductNotFoundException",
    "MarketplaceProductNotFoundException",
    "MarketplaceProductAlreadyExistsException",
    "InvalidMarketplaceProductDataException",
    "MarketplaceProductValidationException",
    "InvalidGTINException",
    "MarketplaceProductCSVImportException",
    # Export/Sync exceptions
    "ExportError",
    "SyncError",
    # Onboarding exceptions
    "OnboardingNotFoundException",
    "OnboardingStepOrderException",
    "OnboardingCsvUrlRequiredException",
    "OnboardingSyncJobNotFoundException",
    "OnboardingSyncNotCompleteException",
]


# =============================================================================
# Base Marketplace Exception
# =============================================================================


class MarketplaceException(BusinessLogicException):
    """Base exception for marketplace module errors."""

    def __init__(
        self,
        message: str,
        error_code: str = "MARKETPLACE_ERROR",
        details: dict | None = None,
    ):
        super().__init__(message=message, error_code=error_code, details=details)


# =============================================================================
# Letzshop Client Exceptions
# =============================================================================


class LetzshopClientError(MarketplaceException):
    """Raised when Letzshop API call fails."""

    def __init__(
        self,
        message: str,
        status_code: int | None = None,
        response: str | None = None,
    ):
        details = {}
        if status_code:
            details["http_status_code"] = status_code
        if response:
            details["response"] = response
        super().__init__(
            message=message,
            error_code="LETZSHOP_CLIENT_ERROR",
            details=details if details else None,
        )
        self.status_code = status_code
        self.response = response


class LetzshopAuthenticationError(LetzshopClientError):  # noqa: MOD025
    """Raised when Letzshop authentication fails."""

    def __init__(self, message: str = "Letzshop authentication failed"):
        super().__init__(message, status_code=401)
        self.error_code = "LETZSHOP_AUTHENTICATION_FAILED"


class LetzshopCredentialsNotFoundException(ResourceNotFoundException):  # noqa: MOD025
    """Raised when Letzshop credentials not found for store."""

    def __init__(self, store_id: int):
        super().__init__(
            resource_type="LetzshopCredentials",
            identifier=str(store_id),
            error_code="LETZSHOP_CREDENTIALS_NOT_FOUND",
        )
        self.store_id = store_id


class LetzshopConnectionFailedException(BusinessLogicException):  # noqa: MOD025
    """Raised when Letzshop API connection test fails."""

    def __init__(self, error_message: str):
        super().__init__(
            message=f"Letzshop connection failed: {error_message}",
            error_code="LETZSHOP_CONNECTION_FAILED",
            details={"error": error_message},
        )


# =============================================================================
# Import Job Exceptions
# =============================================================================


class ImportJobNotFoundException(ResourceNotFoundException):
    """Raised when a marketplace import job is not found."""

    def __init__(self, job_id: int):
        super().__init__(
            resource_type="MarketplaceImportJob",
            identifier=str(job_id),
            message=f"Import job with ID '{job_id}' not found",
            error_code="IMPORT_JOB_NOT_FOUND",
        )


class HistoricalImportJobNotFoundException(ResourceNotFoundException):  # noqa: MOD025
    """Raised when a historical import job is not found."""

    def __init__(self, job_id: int):
        super().__init__(
            resource_type="LetzshopHistoricalImportJob",
            identifier=str(job_id),
            error_code="HISTORICAL_IMPORT_JOB_NOT_FOUND",
        )


class ImportJobNotOwnedException(AuthorizationException):
    """Raised when user tries to access import job they don't own."""

    def __init__(self, job_id: int, user_id: int | None = None):
        details = {"job_id": job_id}
        if user_id:
            details["user_id"] = user_id

        super().__init__(
            message=f"Unauthorized access to import job '{job_id}'",
            error_code="IMPORT_JOB_NOT_OWNED",
            details=details,
        )


class ImportJobCannotBeCancelledException(BusinessLogicException):  # noqa: MOD025
    """Raised when trying to cancel job that cannot be cancelled."""

    def __init__(self, job_id: int, current_status: str):
        super().__init__(
            message=f"Import job '{job_id}' cannot be cancelled (current status: {current_status})",
            error_code="IMPORT_JOB_CANNOT_BE_CANCELLED",
            details={
                "job_id": job_id,
                "current_status": current_status,
            },
        )


class ImportJobCannotBeDeletedException(BusinessLogicException):  # noqa: MOD025
    """Raised when trying to delete job that cannot be deleted."""

    def __init__(self, job_id: int, current_status: str):
        super().__init__(
            message=f"Import job '{job_id}' cannot be deleted (current status: {current_status})",
            error_code="IMPORT_JOB_CANNOT_BE_DELETED",
            details={
                "job_id": job_id,
                "current_status": current_status,
            },
        )


class ImportJobAlreadyProcessingException(BusinessLogicException):  # noqa: MOD025
    """Raised when trying to start import while another is already processing."""

    def __init__(self, store_code: str, existing_job_id: int):
        super().__init__(
            message=f"Import already in progress for store '{store_code}'",
            error_code="IMPORT_JOB_ALREADY_PROCESSING",
            details={
                "store_code": store_code,
                "existing_job_id": existing_job_id,
            },
        )


class ImportValidationError(MarketplaceException):
    """Raised when import data validation fails."""

    def __init__(self, message: str, errors: list[dict] | None = None):
        super().__init__(
            message=message,
            error_code="IMPORT_VALIDATION_ERROR",
            details={"errors": errors} if errors else None,
        )
        self.errors = errors or []


class InvalidImportDataException(ValidationException):  # noqa: MOD025
    """Raised when import data is invalid."""

    def __init__(
        self,
        message: str = "Invalid import data",
        field: str | None = None,
        row_number: int | None = None,
        details: dict[str, Any] | None = None,
    ):
        if not details:
            details = {}

        if row_number:
            details["row_number"] = row_number

        super().__init__(
            message=message,
            field=field,
            details=details,
        )
        self.error_code = "INVALID_IMPORT_DATA"


class ImportRateLimitException(BusinessLogicException):  # noqa: MOD025
    """Raised when import rate limit is exceeded."""

    def __init__(
        self,
        max_imports: int,
        time_window: str,
        retry_after: int | None = None,
    ):
        details = {
            "max_imports": max_imports,
            "time_window": time_window,
        }

        if retry_after:
            details["retry_after"] = retry_after

        super().__init__(
            message=f"Import rate limit exceeded: {max_imports} imports per {time_window}",
            error_code="IMPORT_RATE_LIMIT_EXCEEDED",
            details=details,
        )


# =============================================================================
# Marketplace Exceptions
# =============================================================================


class MarketplaceImportException(BusinessLogicException):  # noqa: MOD025
    """Base exception for marketplace import operations."""

    def __init__(
        self,
        message: str,
        error_code: str = "MARKETPLACE_IMPORT_ERROR",
        marketplace: str | None = None,
        details: dict[str, Any] | None = None,
    ):
        if not details:
            details = {}

        if marketplace:
            details["marketplace"] = marketplace

        super().__init__(
            message=message,
            error_code=error_code,
            details=details,
        )


class MarketplaceConnectionException(ExternalServiceException):  # noqa: MOD025
    """Raised when marketplace connection fails."""

    def __init__(
        self, marketplace: str, message: str = "Failed to connect to marketplace"
    ):
        super().__init__(
            service_name=marketplace,
            message=f"{message}: {marketplace}",
            error_code="MARKETPLACE_CONNECTION_FAILED",
        )


class MarketplaceDataParsingException(ValidationException):  # noqa: MOD025
    """Raised when marketplace data cannot be parsed."""

    def __init__(
        self,
        marketplace: str,
        message: str = "Failed to parse marketplace data",
        details: dict[str, Any] | None = None,
    ):
        if not details:
            details = {}
        details["marketplace"] = marketplace

        super().__init__(
            message=f"{message} from {marketplace}",
            details=details,
        )
        self.error_code = "MARKETPLACE_DATA_PARSING_FAILED"


class InvalidMarketplaceException(ValidationException):  # noqa: MOD025
    """Raised when marketplace is not supported."""

    def __init__(self, marketplace: str, supported_marketplaces: list | None = None):
        details = {"marketplace": marketplace}
        if supported_marketplaces:
            details["supported_marketplaces"] = supported_marketplaces

        super().__init__(
            message=f"Unsupported marketplace: {marketplace}",
            field="marketplace",
            details=details,
        )
        self.error_code = "INVALID_MARKETPLACE"


# =============================================================================
# Product Exceptions
# =============================================================================


class StoreNotFoundException(ResourceNotFoundException):
    """Raised when a store is not found."""

    def __init__(self, store_id: int):
        super().__init__(
            resource_type="Store",
            identifier=str(store_id),
            error_code="STORE_NOT_FOUND",
        )


class ProductNotFoundException(ResourceNotFoundException):
    """Raised when a marketplace product is not found."""

    def __init__(self, product_id: str | int):
        super().__init__(
            resource_type="MarketplaceProduct",
            identifier=str(product_id),
            error_code="MARKETPLACE_PRODUCT_NOT_FOUND",
        )


class MarketplaceProductNotFoundException(ResourceNotFoundException):
    """Raised when a product is not found."""

    def __init__(self, marketplace_product_id: str):
        super().__init__(
            resource_type="MarketplaceProduct",
            identifier=marketplace_product_id,
            message=f"MarketplaceProduct with ID '{marketplace_product_id}' not found",
            error_code="PRODUCT_NOT_FOUND",
        )


class MarketplaceProductAlreadyExistsException(ConflictException):
    """Raised when trying to create a product that already exists."""

    def __init__(self, marketplace_product_id: str):
        super().__init__(
            message=f"MarketplaceProduct with ID '{marketplace_product_id}' already exists",
            error_code="PRODUCT_ALREADY_EXISTS",
            details={"marketplace_product_id": marketplace_product_id},
        )


class InvalidMarketplaceProductDataException(ValidationException):
    """Raised when product data is invalid."""

    def __init__(
        self,
        message: str = "Invalid product data",
        field: str | None = None,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message,
            field=field,
            details=details,
        )
        self.error_code = "INVALID_PRODUCT_DATA"


class MarketplaceProductValidationException(ValidationException):
    """Raised when product validation fails."""

    def __init__(
        self,
        message: str,
        field: str | None = None,
        validation_errors: dict[str, str] | None = None,
    ):
        details = {}
        if validation_errors:
            details["validation_errors"] = validation_errors

        super().__init__(
            message=message,
            field=field,
            details=details,
        )
        self.error_code = "PRODUCT_VALIDATION_FAILED"


class InvalidGTINException(ValidationException):  # noqa: MOD025
    """Raised when GTIN format is invalid."""

    def __init__(self, gtin: str, message: str = "Invalid GTIN format"):
        super().__init__(
            message=f"{message}: {gtin}",
            field="gtin",
            details={"gtin": gtin},
        )
        self.error_code = "INVALID_GTIN"


class MarketplaceProductCSVImportException(BusinessLogicException):  # noqa: MOD025
    """Raised when product CSV import fails."""

    def __init__(
        self,
        message: str = "MarketplaceProduct CSV import failed",
        row_number: int | None = None,
        errors: dict[str, Any] | None = None,
    ):
        details = {}
        if row_number:
            details["row_number"] = row_number
        if errors:
            details["errors"] = errors

        super().__init__(
            message=message,
            error_code="PRODUCT_CSV_IMPORT_FAILED",
            details=details,
        )


# =============================================================================
# Export/Sync Exceptions
# =============================================================================


class ExportError(MarketplaceException):  # noqa: MOD025
    """Raised when product export fails."""

    def __init__(self, message: str, language: str | None = None):
        details = {}
        if language:
            details["language"] = language
        super().__init__(
            message=message,
            error_code="EXPORT_ERROR",
            details=details if details else None,
        )
        self.language = language


class SyncError(MarketplaceException):
    """Raised when store directory sync fails."""

    def __init__(self, message: str, store_code: str | None = None):
        details = {}
        if store_code:
            details["store_code"] = store_code
        super().__init__(
            message=message,
            error_code="SYNC_ERROR",
            details=details if details else None,
        )
        self.store_code = store_code


# =============================================================================
# Onboarding Exceptions
# =============================================================================


class OnboardingNotFoundException(ResourceNotFoundException):
    """Raised when onboarding record is not found for a store."""

    def __init__(self, store_id: int):
        super().__init__(
            resource_type="StoreOnboarding",
            identifier=str(store_id),
            error_code="ONBOARDING_NOT_FOUND",
        )


class OnboardingStepOrderException(ValidationException):
    """Raised when trying to access a step out of order."""

    def __init__(self, current_step: str, required_step: str):
        super().__init__(
            message=f"Please complete the {required_step} step first",
            field="step",
            details={
                "current_step": current_step,
                "required_step": required_step,
            },
        )
        self.error_code = "ONBOARDING_STEP_ORDER_ERROR"


class OnboardingCsvUrlRequiredException(ValidationException):
    """Raised when no CSV URL is provided in product import step."""

    def __init__(self):
        super().__init__(
            message="At least one CSV URL must be provided",
            field="csv_url",
        )
        self.error_code = "ONBOARDING_CSV_URL_REQUIRED"


class OnboardingSyncJobNotFoundException(ResourceNotFoundException):
    """Raised when sync job is not found."""

    def __init__(self, job_id: int):
        super().__init__(
            resource_type="LetzshopHistoricalImportJob",
            identifier=str(job_id),
            error_code="ONBOARDING_SYNC_JOB_NOT_FOUND",
        )


class OnboardingSyncNotCompleteException(BusinessLogicException):
    """Raised when trying to complete onboarding before sync is done."""

    def __init__(self, job_status: str):
        super().__init__(
            message=f"Import job is still {job_status}, please wait",
            error_code="SYNC_NOT_COMPLETE",
            details={"job_status": job_status},
        )
