
from decimal import Decimal
from typing import TYPE_CHECKING, List, Optional
from urllib.parse import urljoin

from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.urls import reverse
from django.utils.module_loading import import_string

if TYPE_CHECKING:
    from .abstracts import AbstractOrder, AbstractPayment
    from .types import OrderAdapter, ItemInfo, BuyerInfo
    from django.http import HttpRequest


def get_real_ip(request: 'HttpRequest') -> Optional['str']:
    if getattr(settings, 'GETPAID_REAL_IP', None):
        return import_string(settings.GETPAID_REAL_IP)(request)

    try:
        from ipware import get_client_ip
        ip, is_routable = get_client_ip(request)
        return ip
    except ImportError:
        pass

    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip


def get_items(order: 'AbstractOrder') -> List['ItemInfo']:
    """
    There are backends that require some sort of item list to be attached
    to the payment. But it's up to you if the list is real or contains only
    one item called "Payment for stuff in {myshop}" ;)
    :return: List of {"name": str, "quantity": Decimal, "unit_price": Decimal} dicts.
    """

    if getattr(settings, 'GETPAID_ORDER_ITEMS', None):
        return import_string(settings.GETPAID_ORDER_ITEMS)(order)

    return order.get_items()


def get_buyer_info(order: 'AbstractOrder') -> 'BuyerInfo':
    """
    This method should return dict with necessary user info.
    For most backends email should be sufficient.
    Expected field names: `email`, `first_name`, `last_name`, `phone`

    """
    if getattr(settings, 'GETPAID_ORDER_BUYER_INFO', None):
        return import_string(settings.GETPAID_ORDER_BUYER_INFO)(order)

    return order.get_buyer_info()


def get_return_url(
        *,
        payment: 'AbstractPayment',
        success: Optional[bool] = None,
        request: Optional['HttpRequest'] = None) -> str:
    """
    Method used to determine the final url the client should see after
    returning from gateway. Client will be redirected to this url after
    backend handled the original callback (i.e. updated payment status)
    and only if SUCCESS_URL or FAILURE_URL settings are NOT set.
    By default it returns the result of `get_absolute_url`
    """
    if getattr(settings, 'GETPAID_ORDER_RETURN_URL', None):
        return import_string(settings.GETPAID_ORDER_RETURN_URL)(
            payment=payment,
            success=success,
            request=request
        )

    if success:
        return urljoin(
            get_base_url(request),
            reverse("getpaid:payment-success", kwargs={"pk": payment.pk}),
        )
    if success is False:
        return urljoin(
            get_base_url(request),
            reverse("getpaid:payment-failure", kwargs={"pk": payment.pk}),
        )

    # not defined, just go to payment or order
    return payment.order.get_absolute_url()


def get_base_url(request: Optional['HttpRequest'] = None) -> str:
    if getattr(settings, 'GETPAID_BASE_URL', None):
        return import_string(settings.GETPAID_BASE_URL)(request)

    if request is None:
        return "http://127.0.0.1/"
    scheme = "http" if settings.DEBUG else "https"
    return f"{scheme}://{get_current_site(request).domain}/"


def get_callback_url(
        *,
        payment: 'AbstractPayment',
        request: 'HttpRequest') -> str:

    if getattr(settings, 'GETPAID_CALLBACK_URL', None):
        return import_string(settings.GETPAID_CALLBACK_URL)(request)

    return urljoin(
        get_base_url(request),
        reverse("getpaid:callback", kwargs={"pk": payment.pk})
    )

def get_currency(order: 'AbstractOrder') -> str:
    if getattr(settings, 'GETPAID_ORDER_CURRENCY', None):
        return import_string(settings.GETPAID_ORDER_CURRENCY)(order)

    return order.get_currency()


def is_ready_for_payment(order: 'AbstractOrder') -> bool:
    if getattr(settings, 'GETPAID_ORDER_IS_READY_FOR_PAYMENT', None):
        return import_string(settings.GETPAID_ORDER_IS_READY_FOR_PAYMENT)(order)

    if hasattr(order, "is_ready_for_payment"):
        return order.is_ready_for_payment()
    return True


def get_total_amount(order: 'AbstractOrder') -> 'Decimal':
    if getattr(settings, 'GETPAID_ORDER_TOTAL_AMOUNT', None):
        return import_string(settings.GETPAID_ORDER_TOTAL_AMOUNT)(order)

    return order.total_to_pay


def get_description(order: 'AbstractOrder') -> str:
    if getattr(settings, 'GETPAID_ORDER_DESCRIPTION', None):
        return import_string(settings.GETPAID_ORDER_DESCRIPTION)(order)

    return order.get_order_display()
