#nullable enable
using System.Net.Http.Json;
using System.Text.Json;
using Baya.Application.Contracts.Payments;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Baya.Infrastructure.CrossCutting.Seams.Real;

/// <summary>
/// Real <see cref="IPaymentProvider"/> over the <b>ZarinPal</b> IPG (refinement-phase-8, 6.1). Selected by
/// <c>Seams:Payments:Provider = zarinpal</c>. Opens a v4 payment session (returns the Shaparak-routed redirect +
/// the <c>authority</c> as the gateway reference), performs the <b>mandatory server-side verify</b> (never trusts
/// a callback alone — the amount is re-checked against the gateway), and reverses a captured payment.
///
/// <para>Every amount crossing this seam is IRR <c>long</c>; ZarinPal v4 amounts are Rial, so no conversion
/// happens here. The merchant id defaults from <c>Seams:Payments:MerchantId</c>; a full multi-gateway deployment
/// resolves it per gateway row from the encrypted <c>payment_gateways.config_json</c> via a provider factory —
/// the seam contract is identical either way.</para>
/// </summary>
public sealed class ZarinPalPaymentProvider(
    HttpClient httpClient,
    IOptions<SeamOptions> options,
    ILogger<ZarinPalPaymentProvider> logger) : IPaymentProvider
{
    private const string DefaultBaseUrl = "https://payment.zarinpal.com";
    private readonly PaymentsOptions _options = options.Value.Payments;

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

    public async ValueTask<PaymentInitResult> InitPaymentAsync(long bookingRequestId, long amountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
    {
        var payload = new
        {
            merchant_id = _options.MerchantId,
            amount = amountIrr,
            callback_url = _options.CallbackUrl,
            description = $"Balinyaar booking request {bookingRequestId}",
            metadata = new { idempotency_key = idempotencyKey },
        };

        using var response = await httpClient.PostAsJsonAsync($"{BaseUrl}/pg/v4/payment/request.json", payload, cancellationToken);
        var raw = await response.Content.ReadAsStringAsync(cancellationToken);
        response.EnsureSuccessStatusCode();

        using var doc = JsonDocument.Parse(raw);
        var authority = doc.RootElement.TryGetProperty("data", out var data)
                        && data.TryGetProperty("authority", out var auth)
            ? auth.GetString()
            : null;

        if (string.IsNullOrEmpty(authority))
            throw new InvalidOperationException("ZarinPal did not return a payment authority.");

        return new PaymentInitResult(
            RedirectUrl: $"{BaseUrl}/pg/StartPay/{authority}",
            GatewayReferenceCode: authority);
    }

    public async ValueTask<PaymentVerifyResult> VerifyAsync(string gatewayReferenceCode, long expectedAmountIrr, CancellationToken cancellationToken = default)
    {
        var payload = new { merchant_id = _options.MerchantId, amount = expectedAmountIrr, authority = gatewayReferenceCode };

        using var response = await httpClient.PostAsJsonAsync($"{BaseUrl}/pg/v4/payment/verify.json", payload, cancellationToken);
        var raw = await response.Content.ReadAsStringAsync(cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            logger.LogWarning("ZarinPal verify returned http {Http} for authority {Authority}", (int)response.StatusCode, gatewayReferenceCode);
            return new PaymentVerifyResult(PaymentProviderStatus.Failed, 0);
        }

        using var doc = JsonDocument.Parse(raw);
        // v4 verify: data.code 100 = paid, 101 = already verified (both confirm). Anything else is a non-confirm.
        var code = doc.RootElement.TryGetProperty("data", out var data) && data.TryGetProperty("code", out var c)
            ? c.GetInt32()
            : -1;

        return code is 100 or 101
            ? new PaymentVerifyResult(PaymentProviderStatus.Succeeded, expectedAmountIrr)
            : new PaymentVerifyResult(PaymentProviderStatus.Failed, 0);
    }

    public async ValueTask<PaymentRefundResult> RefundAsync(string gatewayReferenceCode, long amountIrr, string idempotencyKey, CancellationToken cancellationToken = default)
    {
        // ZarinPal reversals go through the authorized refund API; the idempotency key is carried so a retried
        // refund is a no-op at the gateway rather than a double reversal.
        var payload = new { merchant_id = _options.MerchantId, authority = gatewayReferenceCode, amount = amountIrr, idempotency_key = idempotencyKey };

        using var response = await httpClient.PostAsJsonAsync($"{BaseUrl}/pg/v4/payment/refund.json", payload, cancellationToken);
        var raw = await response.Content.ReadAsStringAsync(cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            logger.LogWarning("ZarinPal refund returned http {Http} for authority {Authority}", (int)response.StatusCode, gatewayReferenceCode);
            return new PaymentRefundResult(PaymentProviderStatus.Failed, null);
        }

        using var doc = JsonDocument.Parse(raw);
        string? refundRef = doc.RootElement.TryGetProperty("data", out var data)
                            && data.TryGetProperty("ref_id", out var refId)
            ? refId.ToString()
            : null;

        return new PaymentRefundResult(PaymentProviderStatus.Succeeded, refundRef);
    }
}
