#nullable enable
using System.Net.Http.Headers;
using System.Net.Http.Json;
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>SnappPay</b> (refinement-phase-8, 6.2) — the canonical superset the
/// seam was designed around: OAuth token → <c>offer/eligible</c> → <c>payment/token|verify|settle|revert|
/// cancel|update|status</c>. Resolved for <c>provider_code = snapppay</c> by <see cref="Real.ConfiguredBnplProviderResolver"/>.
/// Amounts cross the wire in the provider's currency (converted only at this boundary via the base's
/// <c>ToWire</c>/<c>FromWire</c>); the settle reads the <b>merchant commission from the actual response</b>, never
/// hardcoded, and the <c>settled_at</c> is whatever the provider reports (nullable — never assumed instant).
/// Money always flows <c>customer ↔ provider ↔ Balinyaar</c>.
///
/// <para><b>Warn:</b> this is the Iranian SnappPay provider-financed BNPL, <i>not</i> the unrelated Canadian
/// <c>SnapPayInc/open-api-java-sdk</c>. Client id/secret come from the encrypted <c>payment_gateways.config_json</c>
/// in a full deployment; the base URL + non-secret facts are in <c>Seams:Bnpl:Providers[snapppay]</c>.</para>
/// </summary>
public sealed class SnappPayBnplProvider : HttpBnplProviderBase, IBnplProvider
{
    private const string DefaultBaseUrl = "https://fms-gateway-staging.apps.public.teh-1.snappcloud.io";
    private readonly BnplProviderConnection _connection;
    private readonly ILogger _logger;

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

    public SnappPayBnplProvider(
        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('/');

    public async ValueTask<BnplEligibilityResult> CheckEligibilityAsync(string customerMobile, long orderAmountIrr, CancellationToken cancellationToken = default)
    {
        var body = new { amount = ToWire(orderAmountIrr), mobile = customerMobile };
        using var doc = await PostAsync("api/online/offer/v1/eligible", body, cancellationToken);

        var eligible = doc?.RootElement.TryGetProperty("successful", out var s) == true && s.ValueKind == JsonValueKind.True;
        long? ceiling = null;
        if (doc?.RootElement.TryGetProperty("response", out var resp) == true
            && resp.TryGetProperty("titleAmount", out var ceilingEl) && ceilingEl.TryGetInt64(out var c))
            ceiling = FromWire(c);

        return new BnplEligibilityResult(
            eligible ? BnplEligibilityStatus.Eligible : BnplEligibilityStatus.NotEligible,
            InstallmentCount: 4, CreditCeilingIrr: ceiling,
            PlanSummary: "4 interest-free installments, provider-financed (SnappPay).");
    }

    public async ValueTask<BnplTokenResult> CreatePaymentTokenAsync(string customerMobile, long orderAmountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
    {
        var body = new
        {
            amount = ToWire(orderAmountIrr),
            mobile = customerMobile,
            paymentMethodTypeDto = "INSTALLMENT",
            transactionId = idempotencyKey,
            returnURL = string.Empty,
        };
        using var doc = await PostAsync("api/online/payment/v1/token", body, cancellationToken);

        var response = doc?.RootElement.TryGetProperty("response", out var r) == true ? r : default;
        var token = response.ValueKind == JsonValueKind.Object && response.TryGetProperty("paymentToken", out var t)
            ? t.GetString() ?? string.Empty
            : string.Empty;
        var redirect = response.ValueKind == JsonValueKind.Object && response.TryGetProperty("paymentPageUrl", out var u)
            ? u.GetString() ?? string.Empty
            : string.Empty;

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

    public async ValueTask<BnplVerifyResult> VerifyAsync(string externalPaymentToken, long expectedOrderAmountIrr, CancellationToken cancellationToken = default)
    {
        using var doc = await PostAsync("api/online/payment/v1/verify", new { paymentToken = externalPaymentToken }, cancellationToken);
        var ok = doc?.RootElement.TryGetProperty("successful", out var s) == true && s.ValueKind == JsonValueKind.True;
        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 PostAsync("api/online/payment/v1/settle", new { paymentToken = externalPaymentToken }, cancellationToken);
        var ok = doc?.RootElement.TryGetProperty("successful", out var s) == true && s.ValueKind == JsonValueKind.True;
        if (!ok)
            return new BnplSettleResult(PaymentProviderStatus.Failed, 0, 0, null, externalPaymentToken);

        var response = doc!.RootElement.GetProperty("response");
        // The merchant discount is read from the settlement response — never hardcoded.
        var commissionWire = response.TryGetProperty("feeAmount", out var fee) && fee.TryGetInt64(out var f) ? f : 0;
        var settledWire = response.TryGetProperty("settleAmount", out var st) && st.TryGetInt64(out var stv)
            ? stv
            : ToWire(orderAmountIrr) - commissionWire;

        return new BnplSettleResult(
            PaymentProviderStatus.Succeeded,
            SettledAmountIrr: FromWire(settledWire),
            BnplCommissionIrr: FromWire(commissionWire),
            SettledAt: DateTime.UtcNow,
            ExternalTransactionId: externalPaymentToken);
    }

    public async ValueTask<BnplStatusResult> GetStatusAsync(string externalPaymentToken, CancellationToken cancellationToken = default)
    {
        using var doc = await PostAsync("api/online/payment/v1/status", new { paymentToken = externalPaymentToken }, cancellationToken);
        var status = doc?.RootElement.TryGetProperty("response", out var r) == true && r.TryGetProperty("status", out var st)
            ? st.GetString() ?? "unknown"
            : "unknown";
        return new BnplStatusResult(status);
    }

    public ValueTask<BnplRevertResult> CancelAsync(string externalPaymentToken, string idempotencyKey, CancellationToken cancellationToken = default)
        => ReverseAsync("api/online/payment/v1/cancel", new { paymentToken = externalPaymentToken }, cancellationToken);

    public ValueTask<BnplRevertResult> RevertAsync(string providerOrderReference, long amountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
        => ReverseAsync("api/online/payment/v1/revert", new { paymentToken = providerOrderReference }, cancellationToken);

    public ValueTask<BnplRevertResult> UpdateAsync(string providerOrderReference, long newAmountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
        => ReverseAsync("api/online/payment/v1/update", new { paymentToken = providerOrderReference, amount = ToWire(newAmountIrr) }, cancellationToken);

    private async ValueTask<BnplRevertResult> ReverseAsync(string path, object body, CancellationToken cancellationToken)
    {
        using var doc = await PostAsync(path, body, cancellationToken);
        var ok = doc?.RootElement.TryGetProperty("successful", out var s) == true && s.ValueKind == JsonValueKind.True;
        if (!ok)
            return new BnplRevertResult(PaymentProviderStatus.Failed, null, null);

        long? reversedCommission = null;
        if (doc!.RootElement.TryGetProperty("response", out var r)
            && r.TryGetProperty("reversedFeeAmount", out var rf) && rf.TryGetInt64(out var rfv))
            reversedCommission = FromWire(rfv);

        var reference = doc.RootElement.TryGetProperty("response", out var rr) && rr.TryGetProperty("trackingCode", out var tc)
            ? tc.GetString()
            : null;

        return new BnplRevertResult(PaymentProviderStatus.Succeeded, reference, reversedCommission);
    }

    private async ValueTask<JsonDocument?> PostAsync(string path, object body, CancellationToken cancellationToken)
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/{path}")
        {
            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("SnappPay {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 response = await Http.PostAsJsonAsync($"{BaseUrl}/api/online/v1/oauth/token",
                new { grant_type = "client_credentials", scope = "online-merchant" }, cancellationToken);
            response.EnsureSuccessStatusCode();
            using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync(cancellationToken));
            var root = doc.RootElement;
            _token = root.GetProperty("access_token").GetString();
            var ttl = root.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();
        }
    }
}
