using System.Text.Json;
using Senparc.Weixin;
using Senparc.Weixin.Entities;
using Senparc.Weixin.TenPayV3.Apis;
using Senparc.Weixin.TenPayV3.Apis.BasePay;
using Senparc.Weixin.TenPayV3.Helpers;
using Tiku.Application.Commerce;

namespace Tiku.Infrastructure.Commerce;

internal sealed class WechatPayProvider : IPaymentProvider
{
    public string Provider => PaymentProviders.WechatPay;

    public Task<CreatePaymentProviderResult> CreatePaymentAsync(
        PaymentProviderAccount account,
        CreatePaymentProviderRequest request,
        CancellationToken cancellationToken = default)
    {
        return CreatePaymentCoreAsync(account, request, cancellationToken);
    }

    public Task<PaymentNotificationResult> ParsePaymentNotificationAsync(
        PaymentProviderAccount account,
        PaymentNotificationRequest request,
        CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        _ = BuildTenPaySettings(account);
        var signatureValid = HasWechatPaySignatureHeaders(request.Headers);
        var payload = request.Body;
        var eventId = GetString(request.Body, "id") ??
                      GetString(payload, "transaction_id") ?? Guid.NewGuid().ToString("N");
        var orderNo = GetString(payload, "out_trade_no", "outTradeNo")
                      ?? throw new PaymentProviderException("WeChat Pay notification order number is missing.",
                          "wechat_pay_notify_order_missing");
        var tradeNo = GetString(payload, "transaction_id", "transactionId");
        var tradeState = GetString(payload, "trade_state", "tradeState") ?? GetString(request.Body, "event_type");
        var amount = GetInt(payload, "amount", "total") ?? GetInt(payload, "amountCents") ?? 0;
        var paidAt = GetDateTimeOffset(payload, "success_time", "successTime");

        return Task.FromResult(new PaymentNotificationResult(
            Provider,
            GetString(request.Body, "event_type") ?? "TRANSACTION.SUCCESS",
            eventId,
            orderNo,
            tradeNo,
            amount,
            string.Equals(tradeState, "SUCCESS", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(tradeState, "TRANSACTION.SUCCESS", StringComparison.OrdinalIgnoreCase),
            signatureValid,
            paidAt,
            payload));
    }

    private static WechatPaySettings BuildTenPaySettings(PaymentProviderAccount account)
    {
        return new WechatPaySettings
        {
            TenPayV3_AppId = Required(account.ConfigPublic, "appId"),
            TenPayV3_AppSecret = Optional(account.SecretPayload, "appSecret") ?? string.Empty,
            TenPayV3_MchId = Required(account.ConfigPublic, "merchantId", "mchId", "mchid"),
            TenPayV3_Key = Optional(account.SecretPayload, "apiKey", "key") ?? string.Empty,
            TenPayV3_CertPath = Optional(account.SecretPayload, "certPath") ?? string.Empty,
            TenPayV3_CertSecret = Optional(account.SecretPayload, "certSecret") ?? string.Empty,
            TenPayV3_TenpayNotify = Optional(account.ConfigPublic, "notifyUrl") ?? string.Empty,
            TenPayV3_WxOpenTenpayNotify = Optional(account.ConfigPublic, "wxOpenNotifyUrl") ?? string.Empty,
            TenPayV3_PrivateKey = Required(account.SecretPayload, "privateKey", "merchantCertificatePrivateKey"),
            TenPayV3_SerialNumber = Required(account.ConfigPublic, "certificateSerialNumber", "serialNo"),
            TenPayV3_APIv3Key = Required(account.SecretPayload, "apiV3Key", "merchantV3Secret"),
            TenPayV3_TenPayPubKey = Optional(account.ConfigPublic, "tenPayPublicKey") ?? string.Empty,
            TenPayV3_TenPayPubKeyID = Optional(account.ConfigPublic, "tenPayPublicKeyId") ?? string.Empty,
            TenPayV3_TenPayPubKeyEnable = !string.IsNullOrWhiteSpace(Optional(account.ConfigPublic, "tenPayPublicKey")),
            EncryptionType = CertType.RSA
        };
    }

    private async Task<CreatePaymentProviderResult> CreatePaymentCoreAsync(
        PaymentProviderAccount account,
        CreatePaymentProviderRequest request,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(request.OpenId))
            throw new PaymentProviderException("WeChat Pay JSAPI requires openId.", "wechat_pay_openid_required");

        var tenPaySettings = BuildTenPaySettings(account);
        var appId = Required(account.ConfigPublic, "appId");
        var mchId = Required(account.ConfigPublic, "merchantId", "mchId", "mchid");
        var payApis = new BasePayApis(tenPaySettings);
        var wxRequest = new TransactionsRequestData(
            appId,
            mchId,
            request.Subject,
            request.OrderNo,
            null,
            null,
            request.NotifyUrl,
            null,
            new TransactionsRequestData.Amount(request.AmountCents, "CNY"),
            new TransactionsRequestData.Payer(request.OpenId));
        object response = string.Equals(request.Method, "h5", StringComparison.OrdinalIgnoreCase)
            ? await payApis.H5Async(wxRequest).WaitAsync(cancellationToken)
            : await payApis.JsApiAsync(wxRequest).WaitAsync(cancellationToken);
        var rawPayload = JsonSerializer.SerializeToElement(response);
        var prepayId = GetPropertyValue<string?>(response, "prepay_id", "PrepayId");
        var h5Url = GetPropertyValue<string?>(response, "h5_url", "H5Url");
        if (string.IsNullOrWhiteSpace(prepayId) && string.IsNullOrWhiteSpace(h5Url))
            throw new PaymentProviderException(
                "WeChat Pay create transaction failed.",
                "wechat_pay_create_failed");

        var clientPayload = string.IsNullOrWhiteSpace(prepayId)
            ? JsonSerializer.SerializeToElement(new { h5Url })
            : JsonSerializer.SerializeToElement(TenPaySignHelper.GetJsApiUiPackage(appId, prepayId, tenPaySettings));
        var providerPayload = JsonSerializer.SerializeToElement(new
        {
            PrepayId = prepayId,
            H5Url = h5Url,
            request.OrderNo,
            request.AmountCents
        });

        return new CreatePaymentProviderResult(
            Provider,
            request.Method,
            "pending",
            null,
            clientPayload,
            MergeRawPayload(rawPayload, providerPayload));
    }

    private static string Required(JsonElement element, params string[] keys)
    {
        if (element.ValueKind == JsonValueKind.Object)
            foreach (var key in keys)
                if (element.TryGetProperty(key, out var property) &&
                    property.ValueKind == JsonValueKind.String &&
                    !string.IsNullOrWhiteSpace(property.GetString()))
                    return property.GetString()!;

        throw new PaymentProviderException(
            "WeChat Pay provider configuration is incomplete.",
            "wechat_pay_config_incomplete");
    }

    private static string? Optional(JsonElement element, params string[] keys)
    {
        if (element.ValueKind == JsonValueKind.Object)
            foreach (var key in keys)
                if (element.TryGetProperty(key, out var property) &&
                    property.ValueKind == JsonValueKind.String &&
                    !string.IsNullOrWhiteSpace(property.GetString()))
                    return property.GetString();

        return null;
    }

    private static bool HasWechatPaySignatureHeaders(IReadOnlyDictionary<string, string> headers)
    {
        return ContainsHeader(headers, "Wechatpay-Timestamp") &&
               ContainsHeader(headers, "Wechatpay-Nonce") &&
               ContainsHeader(headers, "Wechatpay-Signature") &&
               ContainsHeader(headers, "Wechatpay-Serial");
    }

    private static bool ContainsHeader(IReadOnlyDictionary<string, string> headers, string name)
    {
        return headers.Keys.Any(key => string.Equals(key, name, StringComparison.OrdinalIgnoreCase));
    }

    private static T? GetPropertyValue<T>(object value, params string[] names)
    {
        var type = value.GetType();
        foreach (var name in names)
        {
            var property = type.GetProperty(name);
            if (property?.GetValue(value) is T typed) return typed;
        }

        return default;
    }

    private static JsonElement MergeRawPayload(JsonElement response, JsonElement summary)
    {
        return JsonSerializer.SerializeToElement(new
        {
            response,
            summary
        });
    }

    private static string? GetString(JsonElement element, params string[] keys)
    {
        if (element.ValueKind != JsonValueKind.Object) return null;

        foreach (var key in keys)
            if (element.TryGetProperty(key, out var property) &&
                property.ValueKind == JsonValueKind.String)
                return property.GetString();

        return null;
    }

    private static int? GetInt(JsonElement element, string parentKey, string childKey)
    {
        if (element.ValueKind == JsonValueKind.Object &&
            element.TryGetProperty(parentKey, out var parent) &&
            parent.ValueKind == JsonValueKind.Object &&
            parent.TryGetProperty(childKey, out var child) &&
            child.TryGetInt32(out var value))
            return value;

        return null;
    }

    private static int? GetInt(JsonElement element, string key)
    {
        return element.ValueKind == JsonValueKind.Object &&
               element.TryGetProperty(key, out var property) &&
               property.TryGetInt32(out var value)
            ? value
            : null;
    }

    private static DateTimeOffset? GetDateTimeOffset(JsonElement element, params string[] keys)
    {
        var value = GetString(element, keys);
        return DateTimeOffset.TryParse(value, out var parsed) ? parsed : null;
    }

    private sealed class WechatPaySettings : ISenparcWeixinSettingForTenpayV3
    {
        public string ItemKey { get; set; } = string.Empty;
        public string TenPayV3_MchId { get; set; } = string.Empty;
        public string TenPayV3_SubMchId { get; set; } = string.Empty;
        public string TenPayV3_Key { get; set; } = string.Empty;
        public string TenPayV3_CertPath { get; set; } = string.Empty;
        public string TenPayV3_CertSecret { get; set; } = string.Empty;
        public string TenPayV3_AppId { get; set; } = string.Empty;
        public string TenPayV3_AppSecret { get; set; } = string.Empty;
        public string TenPayV3_SubAppId { get; set; } = string.Empty;
        public string TenPayV3_SubAppSecret { get; set; } = string.Empty;
        public string TenPayV3_TenpayNotify { get; set; } = string.Empty;
        public string TenPayV3_PrivateKey { get; set; } = string.Empty;
        public string TenPayV3_SerialNumber { get; set; } = string.Empty;
        public string TenPayV3_APIv3Key { get; set; } = string.Empty;
        public string TenPayV3_TenPayPubKey { get; set; } = string.Empty;
        public string TenPayV3_TenPayPubKeyID { get; set; } = string.Empty;
        public bool TenPayV3_TenPayPubKeyEnable { get; set; }
        public string TenPayV3_WxOpenTenpayNotify { get; set; } = string.Empty;
        public CertType? EncryptionType { get; set; }
    }
}