from __future__ import annotations

import hashlib
import hmac
from decimal import Decimal, InvalidOperation
from typing import Any
from urllib.parse import urlencode, urlparse

import httpx

from mode.payment.base import (
    PaymentCreateRequest,
    PaymentCreateResult,
    PaymentNotifyResult,
    PaymentProviderError,
    PaymentQueryResult,
)


def _clean_text(value: Any) -> str:
    return str(value or "").strip()


def _first_config_value(config: Any, *names: str) -> str:
    for name in names:
        value = _clean_text(getattr(config, name, None))
        if value:
            return value
    return ""


def _join_url(base: str, path: str) -> str:
    base = base.rstrip("/")
    path = path.strip()
    if path.startswith("http://") or path.startswith("https://"):
        return path
    return f"{base}/{path.lstrip('/')}"


def _derive_submit_path(mapi_path: str) -> str:
    path = (mapi_path or "").strip() or "/mapi.php"
    lower = path.lower()
    if lower.endswith("mapi.php"):
        return f"{path[:-len('mapi.php')]}submit.php"
    return "/submit.php"


def _decimal(value: Any) -> Decimal:
    try:
        return Decimal(str(value or "0")).quantize(Decimal("0.01"))
    except (InvalidOperation, ValueError):
        return Decimal("0.00")


class EpayProvider:
    def __init__(self, config: Any, *, name: str, display_name: str, method: str):
        self.name = name
        self.display_name = display_name
        self.method = method
        self.pid = _first_config_value(config, "PAYMENT_EPAY_PID", "EPAY_PID", "PID")
        self.key = _first_config_value(config, "PAYMENT_EPAY_KEY", "EPAY_KEY", "KEY")
        self.api_url = _first_config_value(config, "PAYMENT_EPAY_API_URL", "EPAY_API_URL", "API_URL").rstrip("/")
        self.mapi_path = _first_config_value(config, "PAYMENT_EPAY_MAPI_PATH", "EPAY_MAPI_PATH", "MAPI_PATH") or "/mapi.php"
        self.api_path = _first_config_value(config, "PAYMENT_EPAY_API_PATH", "EPAY_API_PATH", "API_PATH") or "/api.php"
        self.submit_path = _first_config_value(config, "PAYMENT_EPAY_SUBMIT_PATH", "EPAY_SUBMIT_PATH", "SUBMIT_PATH") or _derive_submit_path(self.mapi_path)
        create_mode = _first_config_value(config, "PAYMENT_EPAY_CREATE_MODE", "EPAY_CREATE_MODE", "CREATE_MODE") or "redirect"
        create_mode = create_mode.lower()
        if create_mode in {"submit", "direct"}:
            create_mode = "redirect"
        if create_mode not in {"redirect", "mapi"}:
            raise PaymentProviderError("易支付创建模式只能是 redirect 或 mapi", status_code=400)
        self.create_mode = create_mode
        self.timeout = float(getattr(config, "PAYMENT_EPAY_TIMEOUT", 15) or 15)

        if not self.pid or not self.key or not self.api_url:
            raise PaymentProviderError("易支付插件未配置 API 地址、商户 PID 或商户密钥", status_code=400)

    def _sign(self, params: dict[str, Any]) -> str:
        filtered = {
            key: value
            for key, value in params.items()
            if value is not None and value != "" and key not in ("sign", "sign_type", "token")
        }
        query = "&".join(f"{key}={value}" for key, value in sorted(filtered.items(), key=lambda item: item[0]))
        return hashlib.md5(f"{query}{self.key}".encode("utf-8")).hexdigest().lower()

    def _verify_sign(self, params: dict[str, Any]) -> bool:
        return hmac.compare_digest(_clean_text(params.get("sign")).lower(), self._sign(params))

    def _cashier_url(self, trade_no: str) -> str:
        parsed = urlparse(self.api_url)
        if parsed.scheme and parsed.netloc:
            return f"{parsed.scheme}://{parsed.netloc}/pay/{trade_no}"
        return f"{self.api_url}/pay/{trade_no}"

    def _create_params(self, request: PaymentCreateRequest) -> dict[str, Any]:
        params: dict[str, Any] = {
            "pid": self.pid,
            "type": self.method,
            "out_trade_no": request.out_trade_no,
            "notify_url": request.notify_url,
            "return_url": request.return_url,
            "name": request.subject[:127],
            "money": request.amount_yuan,
            "clientip": request.client_ip,
            "device": "pc",
            "param": str(request.user_id),
        }
        params["sign"] = self._sign(params)
        params["sign_type"] = "MD5"
        return params

    def _submit_url(self, params: dict[str, Any]) -> str:
        return f"{_join_url(self.api_url, self.submit_path)}?{urlencode(params)}"

    async def create_payment(self, request: PaymentCreateRequest) -> PaymentCreateResult:
        params = self._create_params(request)

        if self.create_mode == "redirect":
            pay_url = self._submit_url(params)
            return PaymentCreateResult(
                provider=self.name,
                method=self.method,
                platform_trade_no=None,
                pay_url=pay_url,
                qr_code=pay_url,
                raw={"mode": "redirect", "submit_path": self.submit_path},
            )

        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                response = await client.post(_join_url(self.api_url, self.mapi_path), data=params)
                response.raise_for_status()
                data = response.json()
        except httpx.HTTPError as exc:
            raise PaymentProviderError(f"易支付创建订单失败：{exc}", raw={"provider": self.name}) from exc
        except ValueError as exc:
            raise PaymentProviderError("易支付创建订单返回非 JSON 响应", raw={"provider": self.name}) from exc

        if data.get("code") != 1:
            message = _clean_text(data.get("msg")) or "易支付通道返回错误"
            raise PaymentProviderError(message, raw=data)

        trade_no = _clean_text(data.get("trade_no")) or None
        qr_code = _clean_text(data.get("qrcode")) or None
        if not qr_code and trade_no:
            qr_code = self._cashier_url(trade_no)

        return PaymentCreateResult(
            provider=self.name,
            method=self.method,
            platform_trade_no=trade_no,
            pay_url=_clean_text(data.get("payurl")) or None,
            qr_code=qr_code,
            url_scheme=_clean_text(data.get("urlscheme")) or None,
            raw=data if isinstance(data, dict) else {"raw": data},
        )

    async def parse_notify(self, params: dict[str, Any]) -> PaymentNotifyResult:
        if not self._verify_sign(params):
            raise PaymentProviderError("易支付回调签名不匹配", status_code=400, raw=params)
        status = _clean_text(params.get("trade_status"))
        return PaymentNotifyResult(
            paid=status == "TRADE_SUCCESS",
            out_trade_no=_clean_text(params.get("out_trade_no")),
            trade_no=_clean_text(params.get("trade_no")),
            amount_yuan=_decimal(params.get("money")),
            status=status,
            raw=params,
        )

    async def query_order(self, out_trade_no: str) -> PaymentQueryResult:
        params = {
            "act": "order",
            "pid": self.pid,
            "key": self.key,
            "out_trade_no": out_trade_no,
        }
        try:
            async with httpx.AsyncClient(timeout=min(self.timeout, 10)) as client:
                response = await client.get(_join_url(self.api_url, self.api_path), params=params)
                response.raise_for_status()
                data = response.json()
        except httpx.HTTPError as exc:
            raise PaymentProviderError(f"易支付查单失败：{exc}", raw={"provider": self.name}) from exc
        except ValueError as exc:
            raise PaymentProviderError("易支付查单返回非 JSON 响应", raw={"provider": self.name}) from exc

        paid = data.get("code") == 1 and data.get("status") == 1
        return PaymentQueryResult(
            paid=paid,
            out_trade_no=out_trade_no,
            trade_no=_clean_text(data.get("trade_no")),
            amount_yuan=_decimal(data.get("money")),
            status=str(data.get("status") if data.get("status") is not None else data.get("code")),
            raw=data if isinstance(data, dict) else {"raw": data},
        )
