using Meezi.API.Models.Billing;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;

namespace Meezi.API.Services;

public record PaymentInitResult(bool Success, string? Authority, string? PaymentUrl, string? ErrorMessage);

public record PaymentVerifyResult(bool Success, string? RefId, string? ErrorMessage);

public interface IBillingPaymentOrchestrator
{
    Task<IReadOnlyList<PaymentMethodDto>> GetEnabledMethodsAsync(CancellationToken cancellationToken = default);
    Task<PaymentInitResult> InitiateAsync(
        PaymentProvider provider,
        long amountRials,
        string paymentId,
        string description,
        string callbackUrl,
        CancellationToken cancellationToken = default);
    Task<PaymentVerifyResult> VerifyAsync(
        PaymentProvider provider,
        string externalId,
        long amountRials,
        CancellationToken cancellationToken = default);
}

public class BillingPaymentOrchestrator : IBillingPaymentOrchestrator
{
    private readonly IZarinPalGateway _zarinPal;
    private readonly ITaraPaymentGateway _tara;
    private readonly ISnappPayGateway _snappPay;
    private readonly IPlatformRuntimeConfig _platform;

    private static readonly (string Id, string NameFa)[] AllMethods =
    [
        (PaymentProviderIds.ZarinPal, "زرین‌پال"),
        (PaymentProviderIds.Tara, "تارا (اعتباری)"),
        (PaymentProviderIds.SnappPay, "اسنپ‌پی (اقساطی)")
    ];

    public BillingPaymentOrchestrator(
        IZarinPalGateway zarinPal,
        ITaraPaymentGateway tara,
        ISnappPayGateway snappPay,
        IPlatformRuntimeConfig platform)
    {
        _zarinPal = zarinPal;
        _tara = tara;
        _snappPay = snappPay;
        _platform = platform;
    }

    public async Task<IReadOnlyList<PaymentMethodDto>> GetEnabledMethodsAsync(
        CancellationToken cancellationToken = default)
    {
        var defaultId = await _platform.GetAsync("payment.activeGateway", cancellationToken) ?? PaymentProviderIds.ZarinPal;
        var list = new List<PaymentMethodDto>();

        if (await _zarinPal.IsEnabledAsync(cancellationToken))
            list.Add(new PaymentMethodDto(PaymentProviderIds.ZarinPal, "زرین‌پال", defaultId == PaymentProviderIds.ZarinPal));
        if (await _tara.IsEnabledAsync(cancellationToken))
            list.Add(new PaymentMethodDto(PaymentProviderIds.Tara, "تارا (اعتباری)", defaultId == PaymentProviderIds.Tara));
        if (await _snappPay.IsEnabledAsync(cancellationToken))
            list.Add(new PaymentMethodDto(PaymentProviderIds.SnappPay, "اسنپ‌پی (اقساطی)", defaultId == PaymentProviderIds.SnappPay));

        if (list.Count == 0)
            list.Add(new PaymentMethodDto(PaymentProviderIds.ZarinPal, "زرین‌پال", true));

        if (list.All(m => !m.IsDefault))
            list[0] = list[0] with { IsDefault = true };

        return list;
    }

    public async Task<PaymentInitResult> InitiateAsync(
        PaymentProvider provider,
        long amountRials,
        string paymentId,
        string description,
        string callbackUrl,
        CancellationToken cancellationToken = default)
    {
        return provider switch
        {
            PaymentProvider.Tara => await InitiateTaraAsync(amountRials, paymentId, callbackUrl, cancellationToken),
            PaymentProvider.SnappPay => await InitiateSnappPayAsync(amountRials, paymentId, callbackUrl, cancellationToken),
            _ => await InitiateZarinPalAsync(amountRials, description, callbackUrl, cancellationToken)
        };
    }

    public async Task<PaymentVerifyResult> VerifyAsync(
        PaymentProvider provider,
        string externalId,
        long amountRials,
        CancellationToken cancellationToken = default)
    {
        return provider switch
        {
            PaymentProvider.Tara =>
                Map(await _tara.VerifyPaymentAsync(externalId, cancellationToken)),
            PaymentProvider.SnappPay =>
                Map(await _snappPay.VerifyAndSettleAsync(externalId, cancellationToken)),
            _ =>
                Map(await _zarinPal.VerifyPaymentAsync(externalId, amountRials, cancellationToken))
        };
    }

    private async Task<PaymentInitResult> InitiateZarinPalAsync(
        long amountRials,
        string description,
        string callbackUrl,
        CancellationToken cancellationToken)
    {
        if (!await _zarinPal.IsEnabledAsync(cancellationToken))
            return new PaymentInitResult(false, null, null, "ZarinPal is disabled.");

        var r = await _zarinPal.RequestPaymentAsync(amountRials, description, callbackUrl, cancellationToken);
        return new PaymentInitResult(r.Success, r.Authority, r.PaymentUrl, r.ErrorMessage);
    }

    private async Task<PaymentInitResult> InitiateTaraAsync(
        long amountRials,
        string paymentId,
        string callbackUrl,
        CancellationToken cancellationToken)
    {
        if (!await _tara.IsEnabledAsync(cancellationToken))
            return new PaymentInitResult(false, null, null, "Tara is disabled.");

        var r = await _tara.RequestPaymentAsync(amountRials, paymentId, callbackUrl, cancellationToken);
        return new PaymentInitResult(r.Success, r.TraceNumber, r.PaymentUrl, r.ErrorMessage);
    }

    private async Task<PaymentInitResult> InitiateSnappPayAsync(
        long amountRials,
        string paymentId,
        string callbackUrl,
        CancellationToken cancellationToken)
    {
        if (!await _snappPay.IsEnabledAsync(cancellationToken))
            return new PaymentInitResult(false, null, null, "Snapp Pay is disabled.");

        var r = await _snappPay.RequestPaymentAsync(amountRials, paymentId, callbackUrl, cancellationToken);
        return new PaymentInitResult(r.Success, r.PaymentToken, r.PaymentUrl, r.ErrorMessage);
    }

    private static PaymentVerifyResult Map(ZarinPalVerifyResult r) =>
        new(r.Success, r.RefId, r.ErrorMessage);

    private static PaymentVerifyResult Map(TaraVerifyResult r) =>
        new(r.Success, r.RefId, r.ErrorMessage);

    private static PaymentVerifyResult Map(SnappPayVerifyResult r) =>
        new(r.Success, r.RefId, r.ErrorMessage);
}
