import importlib
from enum import Enum

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import pgettext_lazy

from .interface import GatewayConfig


class PaymentError(Exception):
    def __init__(self, message):
        super(PaymentError, self).__init__(message)
        self.message = message


class GatewayError(IOError):
    pass


class CustomPaymentChoices:
    MANUAL = "manual"

    CHOICES = [(MANUAL, pgettext_lazy("Custom payment choice type", "Manual"))]


class OperationType(Enum):
    PROCESS_PAYMENT = "process_payment"
    AUTH = "authorize"
    CAPTURE = "capture"
    VOID = "void"
    REFUND = "refund"


class TransactionError(Enum):
    """Represents a transaction error."""

    INCORRECT_NUMBER = "incorrect_number"
    INVALID_NUMBER = "invalid_number"
    INCORRECT_CVV = "incorrect_cvv"
    INVALID_CVV = "invalid_cvv"
    INCORRECT_ZIP = "incorrect_zip"
    INCORRECT_ADDRESS = "incorrect_address"
    INVALID_EXPIRY_DATE = "invalid_expiry_date"
    EXPIRED = "expired"
    PROCESSING_ERROR = "processing_error"
    DECLINED = "declined"


class TransactionKind:
    """Represents the type of a transaction.

    The following transactions types are possible:
    - REGISTER - Some gateways require an initial register transaction, before authorizing.
    - AUTH - an amount reserved against the customer's funding source. Money
    does not change hands until the authorization is captured.
    - VOID - a cancellation of a pending authorization or capture.
    - CAPTURE - a transfer of the money that was reserved during the
    authorization stage.
    - REFUND - full or partial return of captured funds to the customer.
    """

    REGISTER = "register"
    AUTH = "auth"
    CAPTURE = "capture"
    VOID = "void"
    REFUND = "refund"
    # FIXME we could use another status like WAITING_FOR_AUTH for transactions
    # Which were authorized, but needs to be confirmed manually by staff
    # eg. Braintree with "submit_for_settlement" enabled
    CHOICES = [
        (REGISTER, pgettext_lazy("transaction kind", "Registration")),
        (AUTH, pgettext_lazy("transaction kind", "Authorization")),
        (REFUND, pgettext_lazy("transaction kind", "Refund")),
        (CAPTURE, pgettext_lazy("transaction kind", "Capture")),
        (VOID, pgettext_lazy("transaction kind", "Void")),
    ]


class ChargeStatus:
    """Represents possible statuses of a payment.

    The following statuses are possible:
    - NOT_CHARGED - no funds were take off the customer founding source yet.
    - PARTIALLY_CHARGED - funds were taken off the customer's funding source,
    partly covering the payment amount.
    - FULLY_CHARGED - funds were taken off the customer founding source,
    partly or completely covering the payment amount.
    - PARTIALLY_REFUNDED - part of charged funds were returned to the customer.
    - FULLY_REFUNDED - all charged funds were returned to the customer.
    """

    NOT_CHARGED = "not-charged"
    PARTIALLY_CHARGED = "partially-charged"
    FULLY_CHARGED = "fully-charged"
    PARTIALLY_REFUNDED = "partially-refunded"
    FULLY_REFUNDED = "fully-refunded"

    CHOICES = [
        (NOT_CHARGED, pgettext_lazy("payment status", "Not charged")),
        (PARTIALLY_CHARGED, pgettext_lazy("payment status", "Partially charged")),
        (FULLY_CHARGED, pgettext_lazy("payment status", "Fully charged")),
        (PARTIALLY_REFUNDED, pgettext_lazy("payment status", "Partially refunded")),
        (FULLY_REFUNDED, pgettext_lazy("payment status", "Fully refunded")),
    ]


GATEWAYS_ENUM = Enum(  # type:ignore
    "GatewaysEnum", {key.upper(): key.lower() for key in settings.PAYMENT_GATEWAYS}
)


def get_payment_gateway(gateway_name):
    if gateway_name not in settings.CHECKOUT_PAYMENT_GATEWAYS:
        raise ValueError("%s is not allowed gateway" % gateway_name)
    if gateway_name not in settings.PAYMENT_GATEWAYS:
        raise ImproperlyConfigured(
            "Payment gateway %s is not configured." % gateway_name
        )

    gateway_module = importlib.import_module(
        settings.PAYMENT_GATEWAYS[gateway_name]["module"]
    )

    if "config" not in settings.PAYMENT_GATEWAYS[gateway_name]:
        raise ImproperlyConfigured(
            "Payment gateway %s should have own configuration" % gateway_name
        )

    gateway_config = settings.PAYMENT_GATEWAYS[gateway_name]["config"]
    config = GatewayConfig(
        auto_capture=gateway_config["auto_capture"],
        template_path=gateway_config["template_path"],
        connection_params=gateway_config["connection_params"],
    )

    return gateway_module, config
