from __future__ import annotations

from dataclasses import dataclass
from decimal import ROUND_HALF_UP, Decimal, InvalidOperation
from typing import Any, Callable, Optional

from aiohttp import web
from sqlalchemy.ext.asyncio import AsyncSession

from db.dal import payment_dal
from db.models import Payment

from ..base import WebAppPaymentContext

Translator = Callable[..., str]


def make_translator(i18n: Any, language: str) -> Translator:
    """Return a ``_(key, **kw)`` callable that falls back to the key when i18n is absent."""

    def _(key: str, **kwargs: Any) -> str:
        if i18n is None:
            return key
        return i18n.gettext(language, key, **kwargs)

    return _


def format_decimal_amount(amount: Any, places: int = 2) -> Decimal:
    """Quantize ``amount`` to the given decimal places using bank rounding."""
    return Decimal(str(amount)).quantize(Decimal(10) ** -places, rounding=ROUND_HALF_UP)


def decimal_amounts_equal(left: Any, right: Any, places: int = 2) -> bool:
    """True when both values round to the same fixed-point representation."""
    return format_decimal_amount(left, places) == format_decimal_amount(right, places)


def parse_positive_int_units(value: Any) -> Optional[int]:
    """Return a positive integer only when the input represents whole units exactly."""
    if isinstance(value, bool):
        return None
    try:
        decimal_value = Decimal(str(value).strip())
    except (InvalidOperation, ValueError):
        return None
    if not decimal_value.is_finite() or decimal_value != decimal_value.to_integral_value():
        return None
    integer_value = int(decimal_value)
    return integer_value if integer_value > 0 else None


def format_human_units(value: Any) -> str:
    """Render numeric units the way the UI expects: integers w/o decimals, floats with %g."""
    numeric = float(value)
    return str(int(numeric)) if numeric.is_integer() else f"{numeric:g}"


def build_payment_description(
    translator: Translator,
    *,
    months: Any,
    sale_mode: str,
    human_value: Optional[str] = None,
) -> str:
    """Render the standard user-visible payment description.

    Mirrors the branching every callback handler used to repeat
    (traffic / hwid_devices / subscription).
    """
    base = sale_mode_base(sale_mode)
    if base in {"traffic", "traffic_package", "topup", "premium_topup"}:
        return translator(
            "payment_description_traffic",
            traffic_gb=human_value if human_value is not None else format_human_units(months),
        )
    if base in {"hwid_device", "hwid_devices", "hwid_devices_renewal"}:
        return translator("payment_description_hwid_devices", count=int(float(months)))
    return translator("payment_description_subscription", months=int(float(months)))


def build_payment_record_payload(
    *,
    user_id: int,
    amount: float,
    currency: str,
    status: str,
    description: str,
    months: Any,
    provider: str,
    sale_mode: str,
    hwid_quote: Optional[dict] = None,
) -> dict:
    """Assemble the payment-record dict that every callback handler used to inline.

    For the ``traffic`` sale modes, ``purchased_gb`` is taken from ``months``
    (callbacks encode the GB amount in the ``months`` slot); webapp creators
    use the ``payment_record_amounts`` helper directly to split the two.
    """
    base = sale_mode_base(sale_mode)
    is_traffic = sale_mode_is_traffic(sale_mode)
    is_hwid = sale_mode_is_hwid_devices(sale_mode)
    payload = {
        "user_id": user_id,
        "amount": amount,
        "currency": currency,
        "status": status,
        "description": description,
        "subscription_duration_months": int(float(months)) if base == "subscription" else None,
        "provider": provider,
        "sale_mode": sale_mode,
        "tariff_key": sale_mode_tariff_key(sale_mode),
        "purchased_gb": float(months) if is_traffic else None,
        "purchased_hwid_devices": int(float(months)) if is_hwid else None,
    }
    if hwid_quote and is_hwid:
        payload.update(
            {
                "hwid_valid_from": hwid_quote.get("valid_from"),
                "hwid_valid_until": hwid_quote.get("valid_until"),
                "hwid_pricing_period_months": hwid_quote.get("pricing_period_months"),
                "hwid_proration_ratio": hwid_quote.get("proration_ratio"),
                "hwid_full_price": hwid_quote.get("full_price"),
            }
        )
    return payload


@dataclass(frozen=True)
class PaymentRecordAmounts:
    months: int
    purchased_gb: Optional[float]
    purchased_hwid_devices: Optional[int]
    tariff_key: Optional[str]
    traffic_sale: bool
    hwid_devices_sale: bool


def sale_mode_base(sale_mode: str) -> str:
    return str(sale_mode or "").split("@", 1)[0].split("|", 1)[0]


def sale_mode_is_traffic(sale_mode: str) -> bool:
    return sale_mode_base(sale_mode) in {"traffic", "traffic_package", "topup", "premium_topup"}


def sale_mode_is_hwid_devices(sale_mode: str) -> bool:
    return sale_mode_base(sale_mode) in {"hwid_device", "hwid_devices", "hwid_devices_renewal"}


def sale_mode_tariff_key(sale_mode: str) -> Optional[str]:
    if "@" not in str(sale_mode or ""):
        return None
    return str(sale_mode).split("@", 1)[1].split("|", 1)[0] or None


def format_number_for_payload(value: Any) -> str:
    value_float = float(value)
    return str(int(value_float)) if value_float.is_integer() else f"{value_float:g}"


def payment_record_amounts(
    *,
    months: Any,
    sale_mode: str,
    traffic_gb: Optional[float] = None,
) -> PaymentRecordAmounts:
    traffic_sale = sale_mode_is_traffic(sale_mode)
    hwid_devices_sale = sale_mode_is_hwid_devices(sale_mode)
    units = traffic_gb if traffic_sale and traffic_gb is not None else months
    return PaymentRecordAmounts(
        months=int(float(units)) if traffic_sale else int(float(months)),
        purchased_gb=float(units) if traffic_sale else None,
        purchased_hwid_devices=int(float(months)) if hwid_devices_sale else None,
        tariff_key=sale_mode_tariff_key(sale_mode),
        traffic_sale=traffic_sale,
        hwid_devices_sale=hwid_devices_sale,
    )


def payment_units_for_activation(payment: Any, sale_mode: str) -> Any:
    """Resolve purchased units from a payment record for webhook activation."""
    base = sale_mode_base(sale_mode)
    if sale_mode_is_traffic(base):
        return (
            getattr(payment, "purchased_gb", None)
            or getattr(payment, "subscription_duration_months", None)
            or 1
        )
    if sale_mode_is_hwid_devices(base):
        return (
            getattr(payment, "purchased_hwid_devices", None)
            or getattr(payment, "subscription_duration_months", None)
            or 1
        )
    return getattr(payment, "subscription_duration_months", None) or 1


def json_error(status: int, code: str, message: str) -> web.Response:
    return web.json_response({"ok": False, "error": code, "message": message}, status=status)


def payment_unavailable() -> web.Response:
    return json_error(400, "payment_unavailable", "Payment method unavailable")


def payment_failed(message: str = "Failed to create payment") -> web.Response:
    return json_error(502, "payment_failed", message)


def payment_link_response(
    *,
    payment_url: str,
    payment_id: Optional[int],
    action: str = "open_link",
) -> web.Response:
    return web.json_response(
        {
            "ok": True,
            "action": action,
            "payment_url": payment_url,
            "payment_id": payment_id,
        }
    )


async def create_base_payment_record(
    session: AsyncSession,
    *,
    user_id: int,
    amount: float,
    currency: str,
    status: str,
    description: str,
    months: int,
    provider: str,
    sale_mode: Optional[str] = None,
    tariff_key: Optional[str] = None,
    purchased_gb: Optional[float] = None,
    purchased_hwid_devices: Optional[int] = None,
    hwid_valid_from: Optional[Any] = None,
    hwid_valid_until: Optional[Any] = None,
    hwid_pricing_period_months: Optional[int] = None,
    hwid_proration_ratio: Optional[float] = None,
    hwid_full_price: Optional[float] = None,
) -> Payment:
    payment = await payment_dal.create_payment_record(
        session,
        {
            "user_id": user_id,
            "amount": amount,
            "currency": currency,
            "status": status,
            "description": description,
            "subscription_duration_months": months,
            "provider": provider,
            "sale_mode": sale_mode,
            "tariff_key": tariff_key,
            "purchased_gb": purchased_gb,
            "purchased_hwid_devices": purchased_hwid_devices,
            "hwid_valid_from": hwid_valid_from,
            "hwid_valid_until": hwid_valid_until,
            "hwid_pricing_period_months": hwid_pricing_period_months,
            "hwid_proration_ratio": hwid_proration_ratio,
            "hwid_full_price": hwid_full_price,
        },
    )
    await session.commit()
    return payment


async def create_webapp_payment_record(
    ctx: WebAppPaymentContext,
    *,
    amount: float,
    currency: str,
    status: str,
    provider: str,
) -> Payment:
    amounts = payment_record_amounts(
        months=ctx.months,
        sale_mode=ctx.sale_mode,
        traffic_gb=ctx.traffic_gb,
    )
    return await create_base_payment_record(
        ctx.session,
        user_id=ctx.user_id,
        amount=amount,
        currency=currency,
        status=status,
        description=ctx.description,
        months=amounts.months,
        provider=provider,
        sale_mode=ctx.sale_mode,
        tariff_key=amounts.tariff_key,
        purchased_gb=amounts.purchased_gb,
        purchased_hwid_devices=amounts.purchased_hwid_devices,
        hwid_valid_from=ctx.hwid_valid_from,
        hwid_valid_until=ctx.hwid_valid_until,
        hwid_pricing_period_months=ctx.hwid_pricing_period_months,
        hwid_proration_ratio=ctx.hwid_proration_ratio,
        hwid_full_price=ctx.hwid_full_price,
    )


async def mark_payment_failed_creation(session: AsyncSession, payment_id: int) -> None:
    await payment_dal.update_payment_status_by_db_id(session, payment_id, "failed_creation")
    await session.commit()
