#nullable enable
using System.Net.Http.Headers;
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="IBankTransferProvider"/> over the <b>Jibit</b> PAYA/SATNA payout rail (refinement-phase-8, 6.3).
/// Selected by <c>Seams:BankTransfer:Provider = jibit</c>. Registers each payout as a transfer from the platform's
/// registered <b>source settlement account</b> to the nurse's verified Sheba, honouring the PAYA/SATNA
/// <see cref="PayoutInstruction.Method"/> the handler chose by the config threshold. <b>The real rail is async</b>:
/// an accepted transfer comes back <see cref="BankTransferStatus.Submitted"/> (a track id is issued but the money
/// hasn't confirmed) — the execute handler already leaves such a payout <c>submitted</c> and posts no ledger until
/// the async reconciliation callback (<c>POST webhooks/payouts/jibit</c>) flips it <c>paid</c>/<c>failed</c>.
///
/// <para>The <c>idempotencyKey</c> (<c>payout-batch:{id}</c>) is passed as the client reference so a retried submit
/// never re-sends an already-accepted transfer. Every amount is IRR <c>long</c>. Credentials from
/// <c>Seams:BankTransfer</c>; the exact transfer payload is confirmed against the transferor at integration.</para>
/// </summary>
public sealed class JibitBankTransferProvider(
    HttpClient httpClient,
    IOptions<SeamOptions> options,
    ILogger<JibitBankTransferProvider> logger) : IBankTransferProvider
{
    private const string DefaultBaseUrl = "https://napi.jibit.ir/trf";
    private readonly BankTransferOptions _options = options.Value.BankTransfer;

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

    public async ValueTask<PayoutBatchSubmitResult> SubmitPayoutBatchAsync(
        long payoutBatchId,
        IReadOnlyList<PayoutInstruction> instructions,
        string idempotencyKey,
        CancellationToken cancellationToken = default)
    {
        var payload = new
        {
            batchID = idempotencyKey,
            submissionMode = "TRANSFER",
            transfers = instructions.Select(i => new
            {
                transferID = $"{idempotencyKey}:{i.PayoutId}",
                destination = i.Iban,
                amount = i.AmountIrr,
                currency = "IRR",
                transferMode = MapMode(i.Method),
                sourceIdentifier = _options.SourceSettlementAccount,
            }),
        };

        using var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/v1/transfers")
        {
            Content = JsonContent.Create(payload),
        };
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _options.ApiKey);

        using var response = await httpClient.SendAsync(request, cancellationToken);
        var raw = await response.Content.ReadAsStringAsync(cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            logger.LogWarning("Jibit payout submit returned http {Http} for batch {BatchId}", (int)response.StatusCode, payoutBatchId);
            // Whole-batch rejection — every instruction fails so the batch reports failed/retryable.
            return new PayoutBatchSubmitResult(
                ExternalBatchRef: idempotencyKey,
                Results: instructions
                    .Select(i => new PayoutInstructionResult(i.PayoutId, BankTransferStatus.Failed, null, i.Method, "provider_rejected_batch"))
                    .ToList());
        }

        using var doc = JsonDocument.Parse(raw);
        var perTransfer = ReadTransferStates(doc);

        var results = instructions.Select(i =>
        {
            var transferId = $"{idempotencyKey}:{i.PayoutId}";
            var (accepted, track) = perTransfer.GetValueOrDefault(transferId, (true, transferId));
            return accepted
                ? new PayoutInstructionResult(i.PayoutId, BankTransferStatus.Submitted, track, i.Method, null)
                : new PayoutInstructionResult(i.PayoutId, BankTransferStatus.Failed, null, i.Method, "provider_declined");
        }).ToList();

        return new PayoutBatchSubmitResult(idempotencyKey, results);
    }

    public async ValueTask<BankTransferStatus> GetPayoutStatusAsync(string externalBatchRef, CancellationToken cancellationToken = default)
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, $"{BaseUrl}/v1/transfers?batchID={Uri.EscapeDataString(externalBatchRef)}");
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _options.ApiKey);

        using var response = await httpClient.SendAsync(request, cancellationToken);
        if (!response.IsSuccessStatusCode)
            return BankTransferStatus.Submitted; // unknown yet — stay submitted, the callback is authoritative.

        using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync(cancellationToken));
        return MapState(doc.RootElement.TryGetProperty("state", out var s) ? s.GetString() : null);
    }

    private static Dictionary<string, (bool Accepted, string? Track)> ReadTransferStates(JsonDocument doc)
    {
        var map = new Dictionary<string, (bool, string?)>(StringComparer.Ordinal);
        if (doc.RootElement.TryGetProperty("transfers", out var transfers) && transfers.ValueKind == JsonValueKind.Array)
        {
            foreach (var t in transfers.EnumerateArray())
            {
                var id = t.TryGetProperty("transferID", out var tid) ? tid.GetString() : null;
                if (id is null) continue;
                var accepted = MapState(t.TryGetProperty("state", out var st) ? st.GetString() : null) != BankTransferStatus.Failed;
                var track = t.TryGetProperty("bankTransferID", out var bt) ? bt.GetString() : id;
                map[id] = (accepted, track);
            }
        }
        return map;
    }

    private static BankTransferStatus MapState(string? state) => state?.ToUpperInvariant() switch
    {
        "TRANSFERRED" or "SETTLED" or "PAID" => BankTransferStatus.Paid,
        "FAILED" or "CANCELLED" or "REJECTED" => BankTransferStatus.Failed,
        _ => BankTransferStatus.Submitted,
    };

    private static string MapMode(string method)
        => string.Equals(method, BankTransferMethod.Satna, StringComparison.OrdinalIgnoreCase) ? "SATNA" : "ACH";
}
