# nepal_gateways/core/base.py

from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, Union, Literal, List
import logging

# Initialize a logger for this module
logger = logging.getLogger(__name__)

# --- Type Aliases for Clarity and Consistency ---
Amount = Union[
    int, float
]  # Represents monetary amounts, could be paisa (int) or rupees (float)
OrderID = str  # Unique identifier for an order from the merchant's system
ProductID = (
    str  # Identifier for a product, often the same as OrderID for simple transactions
)
CallbackURL = str  # URL for success/failure callbacks from the gateway
GatewayMode = Literal["sandbox", "live"]  # Defines the operational mode of the gateway
HTTPMethod = Literal[
    "GET", "POST"
]  # HTTP methods commonly used for redirects/API calls


# --- Abstract Response Structure for Payment Initiation ---
class PaymentInitiationResponse(ABC):
    """
    Abstract Base Class for the response after initiating a payment.
    Concrete implementations by specific gateway clients will provide
    the necessary details for redirecting the user or displaying payment instructions.
    """

    @property
    @abstractmethod
    def is_redirect_required(self) -> bool:
        """
        Indicates if this response requires the user to be redirected to the gateway.
        Returns:
            bool: True if redirection is needed, False otherwise (e.g., for QR code display).
        """
        pass

    @property
    @abstractmethod
    def redirect_url(self) -> Optional[str]:
        """
        The URL to which the user should be redirected or a form should be POSTed.
        Returns:
            Optional[str]: The URL, or None if no direct URL redirect is needed.
        """
        pass

    @property
    @abstractmethod
    def redirect_method(self) -> Optional[HTTPMethod]:
        """
        The HTTP method to use for the redirect (e.g., 'GET' or 'POST').
        Returns:
            Optional[HTTPMethod]: The HTTP method, or None if not applicable.
        """
        pass

    @property
    @abstractmethod
    def form_fields(self) -> Optional[Dict[str, Any]]:
        """
        A dictionary of form fields to be submitted if the redirect_method is 'POST'.
        Keys are field names, values are field values.
        Returns:
            Optional[Dict[str, Any]]: The form fields, or None if not a POST redirect or no fields.
        """
        pass

    @property
    @abstractmethod
    def payment_instructions(self) -> Optional[Dict[str, Any]]:
        """
        Any additional instructions or data needed for the payment process if not a direct redirect.
        This could be QR code data, bank details for manual transfer, etc.
        Returns:
            Optional[Dict[str, Any]]: A dictionary of instructions, or None.
        """
        pass

    @property
    @abstractmethod
    def raw_response(self) -> Any:
        """
        The raw response received from the gateway during the initiation phase, if any.
        Useful for debugging or accessing gateway-specific data not covered by other properties.
        Returns:
            Any: The raw response data.
        """
        pass


# --- Abstract Response Structure for Payment Verification ---
class PaymentVerificationResult(ABC):
    """
    Abstract Base Class for the result of a payment verification attempt.
    Concrete implementations will parse the gateway's verification response.
    """

    @property
    @abstractmethod
    def is_successful(self) -> bool:
        """
        Indicates whether the payment was successfully verified.
        Returns:
            bool: True if payment is confirmed successful, False otherwise.
        """
        pass

    @property
    @abstractmethod
    def status_code(self) -> Optional[Union[str, int]]:
        """
        A specific status code from the gateway indicating the payment status
        (e.g., 'SUCCESS', 'PENDING', 'FAILURE', or a numeric code).
        Returns:
            Optional[Union[str, int]]: The gateway's status code.
        """
        pass

    @property
    @abstractmethod
    def status_message(self) -> str:
        """
        A human-readable status message, either from the gateway or generated by the library.
        Returns:
            str: The status message.
        """
        pass

    @property
    @abstractmethod
    def transaction_id(self) -> Optional[str]:
        """
        The unique transaction identifier assigned by the payment gateway.
        Returns:
            Optional[str]: The gateway's transaction ID.
        """
        pass

    @property
    @abstractmethod
    def order_id(self) -> Optional[OrderID]:
        """
        The merchant's order identifier that was part of this transaction.
        Returns:
            Optional[OrderID]: The merchant's order ID.
        """
        pass

    @property
    @abstractmethod
    def verified_amount(self) -> Optional[Amount]:
        """
        The amount that was confirmed by the gateway for this transaction.
        Returns:
            Optional[Amount]: The verified transaction amount.
        """
        pass

    @property
    @abstractmethod
    def raw_response(self) -> Any:
        """
        The raw response received from the gateway during the verification API call.
        Returns:
            Any: The raw verification response data.
        """
        pass

    @property
    @abstractmethod
    def gateway_specific_details(self) -> Dict[str, Any]:
        """
        A dictionary containing any additional gateway-specific details or parsed data
        from the verification response not covered by the standard properties.
        Returns:
            Dict[str, Any]: Extra details from the gateway.
        """
        pass


# --- Abstract Base Class for all Payment Gateway Clients ---
class BasePaymentGateway(ABC):
    """
    Abstract Base Class for all payment gateway clients.
    It defines the common interface that specific gateway implementations
    (e.g., EsewaClient, KhaltiClient) must adhere to.
    """

    def __init__(self, config: Dict[str, Any]):
        """
        Initializes the gateway client with common configuration.

        Args:
            config (Dict[str, Any]): A dictionary containing gateway-specific configuration.
                                     Must include a 'mode' key ('sandbox' or 'live').
                                     Other keys like API credentials, URLs will be gateway-specific.
        Raises:
            ConfigurationError: If the 'mode' is invalid or missing essential base config.
        """
        # Import locally to avoid potential circular imports if exceptions.py imports from base.py
        from .exceptions import ConfigurationError

        self.config: Dict[str, Any] = config
        self.mode: GatewayMode = str(
            self.config.get("mode", "sandbox")
        ).lower()  # Default to sandbox

        if self.mode not in ["sandbox", "live"]:
            raise ConfigurationError(
                f"Invalid 'mode' configuration: '{self.mode}'. Must be 'sandbox' or 'live'."
            )

        # Log basic initialization details. Avoid logging the full config if it contains secrets.
        # Subclasses should log their specific loaded credentials carefully.
        # logger.info(
        #     f"Initialized {self.__class__.__name__} in '{self.mode}' mode. "
        #     f"Loaded config keys: {list(self.config.keys())}" # Log keys to show what was loaded
        # )

    @abstractmethod
    def initiate_payment(
        self,
        amount: Amount,
        order_id: OrderID,
        description: Optional[str] = None,
        success_url: Optional[CallbackURL] = None,
        failure_url: Optional[CallbackURL] = None,
        customer_info: Optional[
            Dict[str, Any]
        ] = None,  # e.g., {'name': 'John Doe', 'email': 'j.doe@me.com'}
        product_details: Optional[
            Union[Dict[str, Any], List[Dict[str, Any]]]
        ] = None,  # For itemized lists
        **kwargs: Any,  # For any other gateway-specific parameters during initiation
    ) -> PaymentInitiationResponse:
        """
        Initiates a payment transaction with the gateway.

        This method should prepare the necessary data and make an API call or
        generate data for a form post to the payment gateway.

        Args:
            amount (Amount): The total amount to be paid.
            order_id (OrderID): The merchant's unique identifier for this order/transaction.
            description (Optional[str]): A brief description of the transaction or product.
            success_url (Optional[CallbackURL]): URL to redirect to on successful payment.
                                                 Overrides default from config if provided.
            failure_url (Optional[CallbackURL]): URL to redirect to on failed/cancelled payment.
                                                Overrides default from config if provided.
            customer_info (Optional[Dict[str, Any]]): Optional customer details.
            product_details (Optional[Union[Dict[str, Any], List[Dict[str, Any]]]]): Optional itemized product details.
            **kwargs: Additional gateway-specific parameters.

        Returns:
            PaymentInitiationResponse: An object containing information needed for the next step
                                       (e.g., redirect URL, form fields).

        Raises:
            InitiationError: If payment initiation fails for any reason.
            ConfigurationError: If essential configuration for initiation is missing.
            NetworkError: If there's a problem communicating with the gateway.
        """
        pass

    @abstractmethod
    def verify_payment(
        self,
        transaction_data_from_callback: Dict[str, Any],
        order_id_from_merchant_system: Optional[OrderID] = None,
        amount_from_merchant_system: Optional[Amount] = None,
        **kwargs: Any,  # For any other gateway-specific parameters during verification
    ) -> PaymentVerificationResult:
        """
        Verifies a payment transaction after the user returns from the gateway or a webhook is received.
        This typically involves a server-to-server API call to the gateway to confirm the payment status.

        Args:
            transaction_data_from_callback (Dict[str, Any]): Data received from the gateway
                in the callback request (e.g., request.GET, request.POST, or parsed JSON body).
            order_id_from_merchant_system (Optional[OrderID]): The order ID stored in the merchant's
                system, used for cross-verification. Highly recommended.
            amount_from_merchant_system (Optional[Amount]): The amount stored in the merchant's
                system for this order, used for cross-verification. Highly recommended.
            **kwargs: Additional gateway-specific parameters needed for verification.

        Returns:
            PaymentVerificationResult: An object containing the verification status and details.

        Raises:
            VerificationError: If payment verification fails, data mismatches, or signature is invalid.
            ConfigurationError: If essential configuration for verification is missing.
            NetworkError: If there's a problem communicating with the gateway.
        """
        pass

    # --- Helper method for accessing configuration ---
    def _get_config_value(
        self,
        key: str,
        default: Optional[
            Any
        ] = None,  # If None, this becomes a marker for "no default"
        required: bool = False,
        allowed_values: Optional[List[Any]] = None,
    ) -> Any:
        """
        Helper method to retrieve a configuration value for the current gateway.

        Args:
            key (str): The configuration key to retrieve.
            default (Optional[Any]): The default value to return if the key is not found.
                                     If None and required is False, None is returned.
            required (bool): If True and the key is not found (and no default is effectively set by passing a non-None default),
                             a ConfigurationError is raised.
            allowed_values (Optional[List[Any]]): If provided, the retrieved value must be one of these.

        Returns:
            Any: The configuration value.

        Raises:
            ConfigurationError: If a required key is missing or the value is not in allowed_values.
        """
        from .exceptions import ConfigurationError  # Local import for safety

        value_present = key in self.config
        value = self.config.get(key, default)  # Get value or default

        if (
            required and not value_present and default is None
        ):  # Check if key was missing and no default provided
            raise ConfigurationError(
                f"Missing required configuration key for {self.__class__.__name__}: '{key}'"
            )

        # If required is true, but a default value was provided, the above doesn't trigger.
        # We might still want to ensure a non-None value if required and default was implicitly None.
        if required and value is None:
            raise ConfigurationError(
                f"Configuration key '{key}' for {self.__class__.__name__} is required but resolved to None (or was missing with no default)."
            )

        if allowed_values and value is not None and value not in allowed_values:
            raise ConfigurationError(
                f"Invalid value for configuration key '{key}' in {self.__class__.__name__}: "
                f"'{value}'. Allowed values are: {allowed_values}"
            )
        return value

    # --- Future common methods (can be added later if applicable to multiple gateways) ---
    # @abstractmethod
    # def refund_payment(self, transaction_id: str, refund_amount: Amount, **kwargs) -> PaymentRefundResult:
    #     pass

    # @abstractmethod
    # def check_transaction_status(self, order_id: OrderID, **kwargs) -> PaymentStatusResult:
    #     pass
