#nullable enable
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Baya.Application.Contracts.Payments;
using Baya.Domain.Entities.Bnpl;
using Microsoft.Extensions.Logging;

namespace Baya.Infrastructure.CrossCutting.Seams.Real;

/// <summary>
/// Real <see cref="IBnplProvider"/> for <b>Digipay</b> (refinement-phase-8, 6.2) over the UPG installment flow
/// (<c>type=13</c>): OAuth → <c>tickets/business</c> (create) → <c>purchases/verify</c> → <c>purchases/deliver</c>
/// (settle) → <c>refunds</c> (revert). Resolved for <c>provider_code = digipay</c>. Same currency boundary as the
/// SnappPay adapter (conversion only via the base's <c>ToWire</c>/<c>FromWire</c>); the merchant commission on
/// settle is read from the deliver response, never hardcoded. Credentials come from the encrypted
/// <c>payment_gateways.config_json</c> in a full deployment; base URL + non-secret facts from
/// <c>Seams:Bnpl:Providers[digipay]</c>.
/// </summary>
public sealed class DigipayBnplProvider : HttpBnplProviderBase, IBnplProvider
{
    private const string DefaultBaseUrl = "https://uat.mydigipay.info";
    private readonly BnplProviderConnection _connection;
    private readonly ILogger _logger;

    private string? _token;
    private DateTime _tokenExpiresAt;
    private readonly SemaphoreSlim _tokenGate = new(1, 1);

    public DigipayBnplProvider(
        HttpClient httpClient,
        ICurrencyNormalizer currency,
        BnplProviderConnection connection,
        string wireCurrency,
        ILogger logger)
        : base(httpClient, currency, wireCurrency)
    {
        _connection = connection;
        _logger = logger;
    }

    private string BaseUrl => string.IsNullOrWhiteSpace(_connection.BaseUrl) ? DefaultBaseUrl : _connection.BaseUrl.TrimEnd('/');

    // Digipay decides eligibility on its own hosted page; the UPG has no standalone pre-check, so the order is
    // offered and the provider declines at the page if the customer is ineligible.
    public ValueTask<BnplEligibilityResult> CheckEligibilityAsync(string customerMobile, long orderAmountIrr, CancellationToken cancellationToken = default)
        => ValueTask.FromResult(new BnplEligibilityResult(
            BnplEligibilityStatus.Eligible, InstallmentCount: 4, CreditCeilingIrr: null,
            PlanSummary: "Interest-free installments, provider-financed (Digipay)."));

    public async ValueTask<BnplTokenResult> CreatePaymentTokenAsync(string customerMobile, long orderAmountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
    {
        var body = new { amount = ToWire(orderAmountIrr), cellNumber = customerMobile, providerId = idempotencyKey, callbackUrl = string.Empty };
        using var doc = await SendAsync(HttpMethod.Post, "digipay/api/tickets/business?type=13", body, cancellationToken);
        if (doc is null)
            return new BnplTokenResult(PaymentProviderStatus.Failed, string.Empty, string.Empty, null);

        var root = doc.RootElement;
        var ticket = root.TryGetProperty("ticket", out var t) ? t.GetString() ?? string.Empty : string.Empty;
        var redirect = root.TryGetProperty("redirectUrl", out var u) ? u.GetString() ?? string.Empty : string.Empty;

        return string.IsNullOrEmpty(ticket)
            ? new BnplTokenResult(PaymentProviderStatus.Failed, string.Empty, string.Empty, null)
            : new BnplTokenResult(PaymentProviderStatus.Succeeded, ticket, redirect, idempotencyKey);
    }

    public async ValueTask<BnplVerifyResult> VerifyAsync(string externalPaymentToken, long expectedOrderAmountIrr, CancellationToken cancellationToken = default)
    {
        using var doc = await SendAsync(HttpMethod.Post, "digipay/api/purchases/verify", new { trackingCode = externalPaymentToken }, cancellationToken);
        var ok = doc?.RootElement.TryGetProperty("result", out var r) == true
                 && r.TryGetProperty("status", out var st) && st.GetInt32() == 0;
        return new BnplVerifyResult(ok ? PaymentProviderStatus.Succeeded : PaymentProviderStatus.Failed, expectedOrderAmountIrr, externalPaymentToken);
    }

    public async ValueTask<BnplSettleResult> SettleAsync(string externalPaymentToken, long orderAmountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
    {
        using var doc = await SendAsync(HttpMethod.Post, "digipay/api/purchases/deliver?type=13",
            new { invoiceNumber = externalPaymentToken, deliveryDate = DateTime.UtcNow.ToString("yyyy-MM-dd") }, cancellationToken);
        var ok = doc?.RootElement.TryGetProperty("result", out var r) == true
                 && r.TryGetProperty("status", out var st) && st.GetInt32() == 0;
        if (!ok)
            return new BnplSettleResult(PaymentProviderStatus.Failed, 0, 0, null, externalPaymentToken);

        var commissionWire = doc!.RootElement.TryGetProperty("feeAmount", out var fee) && fee.TryGetInt64(out var f) ? f : 0;
        var settledWire = doc.RootElement.TryGetProperty("settlementAmount", out var s) && s.TryGetInt64(out var sv)
            ? sv
            : ToWire(orderAmountIrr) - commissionWire;

        return new BnplSettleResult(
            PaymentProviderStatus.Succeeded, FromWire(settledWire), FromWire(commissionWire), DateTime.UtcNow, externalPaymentToken);
    }

    public async ValueTask<BnplStatusResult> GetStatusAsync(string externalPaymentToken, CancellationToken cancellationToken = default)
    {
        using var doc = await SendAsync(HttpMethod.Get, $"digipay/api/purchases/track?trackingCode={Uri.EscapeDataString(externalPaymentToken)}", null, cancellationToken);
        var status = doc?.RootElement.TryGetProperty("status", out var st) == true ? st.GetString() ?? "unknown" : "unknown";
        return new BnplStatusResult(status);
    }

    public ValueTask<BnplRevertResult> CancelAsync(string externalPaymentToken, string idempotencyKey, CancellationToken cancellationToken = default)
        => RefundAsync(externalPaymentToken, null, cancellationToken);

    public ValueTask<BnplRevertResult> RevertAsync(string providerOrderReference, long amountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
        => RefundAsync(providerOrderReference, ToWire(amountIrr), cancellationToken);

    public ValueTask<BnplRevertResult> UpdateAsync(string providerOrderReference, long newAmountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
        => RefundAsync(providerOrderReference, ToWire(newAmountIrr), cancellationToken);

    private async ValueTask<BnplRevertResult> RefundAsync(string reference, long? wireAmount, CancellationToken cancellationToken)
    {
        object body = wireAmount is null
            ? new { trackingCode = reference }
            : new { trackingCode = reference, amount = wireAmount.Value };
        using var doc = await SendAsync(HttpMethod.Post, "digipay/api/refunds", body, cancellationToken);
        var ok = doc?.RootElement.TryGetProperty("result", out var r) == true
                 && r.TryGetProperty("status", out var st) && st.GetInt32() == 0;
        if (!ok)
            return new BnplRevertResult(PaymentProviderStatus.Failed, null, null);

        var refundRef = doc!.RootElement.TryGetProperty("refundTrackingCode", out var rt) ? rt.GetString() : null;
        return new BnplRevertResult(PaymentProviderStatus.Succeeded, refundRef, null);
    }

    private async ValueTask<JsonDocument?> SendAsync(HttpMethod method, string path, object? body, CancellationToken cancellationToken)
    {
        using var request = new HttpRequestMessage(method, $"{BaseUrl}/{path}");
        if (body is not null)
            request.Content = JsonContent.Create(body);
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await GetTokenAsync(cancellationToken));

        using var response = await Http.SendAsync(request, cancellationToken);
        var raw = await response.Content.ReadAsStringAsync(cancellationToken);
        if (!response.IsSuccessStatusCode)
        {
            _logger.LogWarning("Digipay {Path} returned http {Http}", path, (int)response.StatusCode);
            return null;
        }

        return JsonDocument.Parse(raw);
    }

    private async ValueTask<string> GetTokenAsync(CancellationToken cancellationToken)
    {
        if (_token is not null && DateTime.UtcNow < _tokenExpiresAt)
            return _token;

        await _tokenGate.WaitAsync(cancellationToken);
        try
        {
            if (_token is not null && DateTime.UtcNow < _tokenExpiresAt)
                return _token;

            using var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/digipay/api/oauth/token")
            {
                Content = new FormUrlEncodedContent(new Dictionary<string, string> { ["grant_type"] = "client_credentials" }),
            };
            // Digipay authenticates the token call with HTTP Basic (merchant client id:secret from the gateway config).
            request.Headers.Authorization = new AuthenticationHeaderValue("Basic",
                Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_connection.MerchantId}:")));

            using var response = await Http.SendAsync(request, cancellationToken);
            response.EnsureSuccessStatusCode();
            using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync(cancellationToken));
            _token = doc.RootElement.GetProperty("access_token").GetString();
            var ttl = doc.RootElement.TryGetProperty("expires_in", out var exp) && exp.TryGetInt32(out var seconds) ? seconds : 3600;
            _tokenExpiresAt = DateTime.UtcNow.AddSeconds(Math.Max(60, ttl - 60));
            return _token!;
        }
        finally
        {
            _tokenGate.Release();
        }
    }
}
