using System.Net.Http.Json;
using System.Text.Json;
using Meezi.Core.Interfaces;
using Microsoft.Extensions.Logging;

namespace Meezi.Infrastructure.ExternalServices;

/// <summary>
/// Tara360 merchant club API (credit / installment).
/// Docs: https://api.tara-club.ir/club/api/v1 — login, trace, request, verify.
/// </summary>
public class TaraPaymentGateway : ITaraPaymentGateway
{
    private const string DefaultProdBase = "https://api.tara-club.ir/club/api/v1";
    private const string DefaultSandboxBase = "https://stage.tara-club.ir/club/api/v1";

    private readonly HttpClient _httpClient;
    private readonly IPlatformRuntimeConfig _platform;
    private readonly ILogger<TaraPaymentGateway> _logger;
    private string? _cachedToken;
    private DateTime _tokenExpiresUtc = DateTime.MinValue;

    public TaraPaymentGateway(
        HttpClient httpClient,
        IPlatformRuntimeConfig platform,
        ILogger<TaraPaymentGateway> logger)
    {
        _httpClient = httpClient;
        _platform = platform;
        _logger = logger;
    }

    public async Task<bool> IsEnabledAsync(CancellationToken cancellationToken = default)
    {
        var enabled = await _platform.GetAsync("payment.tara.enabled", cancellationToken);
        return enabled is "true";
    }

    public async Task<TaraInitResult> RequestPaymentAsync(
        long amountRials,
        string invoiceNumber,
        string callbackUrl,
        CancellationToken cancellationToken = default)
    {
        var creds = await LoadCredentialsAsync(cancellationToken);
        if (!creds.IsConfigured)
        {
            var mockTrace = "MOCK-TARA-" + invoiceNumber;
            var mockUrl = $"{callbackUrl}?traceNumber={mockTrace}&status=OK";
            _logger.LogInformation("Tara mock payment trace {Trace} amount {Amount} Rials", mockTrace, amountRials);
            return new TaraInitResult(true, mockTrace, mockUrl, null);
        }

        var token = await LoginAsync(creds, cancellationToken);
        if (token is null)
            return new TaraInitResult(false, null, null, "Tara login failed.");

        var terminalCode = creds.TerminalCode;
        if (string.IsNullOrWhiteSpace(terminalCode))
        {
            terminalCode = await ResolveTerminalCodeAsync(creds, token, cancellationToken);
            if (string.IsNullOrWhiteSpace(terminalCode))
                return new TaraInitResult(false, null, null, "No Tara terminal available.");
        }

        var traceResp = await PostAsync<JsonElement>(
            creds,
            $"purchase/trace/{terminalCode}",
            new { },
            token,
            cancellationToken);
        var traceNumber = FindString(traceResp, "traceNumber", "trace_number", "data");
        if (string.IsNullOrEmpty(traceNumber))
            return new TaraInitResult(false, null, null, "Tara trace creation failed.");

        var amountToman = amountRials / 10;
        var purchaseBody = new
        {
            amount = amountToman,
            invoiceNumber,
            additionalInfo = "Meezi subscription",
            invoiceItems = new[]
            {
                new
                {
                    name = "Meezi subscription",
                    code = "MEEZI-SUB",
                    count = 1,
                    unit = 1,
                    price = amountToman,
                    group = "GENERAL",
                    groupTitle = "عمومی"
                }
            }
        };

        var requestResp = await PostAsync<JsonElement>(
            creds,
            $"purchase/request/{traceNumber}",
            purchaseBody,
            token,
            cancellationToken);

        var paymentUrl = FindString(requestResp, "paymentUrl", "payment_url", "redirectUrl", "url");
        if (string.IsNullOrEmpty(paymentUrl))
            paymentUrl = $"{callbackUrl}?traceNumber={traceNumber}&status=OK";

        return new TaraInitResult(true, traceNumber, paymentUrl, null);
    }

    public async Task<TaraVerifyResult> VerifyPaymentAsync(
        string traceNumber,
        CancellationToken cancellationToken = default)
    {
        if (traceNumber.StartsWith("MOCK-TARA-", StringComparison.Ordinal))
        {
            _logger.LogInformation("Tara mock verify {Trace}", traceNumber);
            return new TaraVerifyResult(true, traceNumber, null);
        }

        var creds = await LoadCredentialsAsync(cancellationToken);
        if (!creds.IsConfigured)
            return new TaraVerifyResult(false, null, "Tara is not configured.");

        var token = await LoginAsync(creds, cancellationToken);
        if (token is null)
            return new TaraVerifyResult(false, null, "Tara login failed.");

        var verifyResp = await PostAsync<JsonElement>(
            creds,
            $"purchase/verify/{traceNumber}",
            new { },
            token,
            cancellationToken);

        var success = FindBool(verifyResp, "success", "isSuccess") ?? true;
        if (!success)
            return new TaraVerifyResult(false, null, "Tara verify failed.");

        var refId = FindString(verifyResp, "referenceNumber", "refId", "id") ?? traceNumber;
        return new TaraVerifyResult(true, refId, null);
    }

    private async Task<string?> LoginAsync(TaraCredentials creds, CancellationToken cancellationToken)
    {
        if (_cachedToken is not null && DateTime.UtcNow < _tokenExpiresUtc)
            return _cachedToken;

        var resp = await PostAsync<JsonElement>(
            creds,
            "merchant/login",
            new { username = creds.Username, password = creds.Password },
            null,
            cancellationToken);

        var token = FindString(resp, "token", "accessToken", "access_token");
        if (string.IsNullOrEmpty(token))
            return null;

        _cachedToken = token;
        _tokenExpiresUtc = DateTime.UtcNow.AddMinutes(25);
        return token;
    }

    private async Task<string?> ResolveTerminalCodeAsync(
        TaraCredentials creds,
        string token,
        CancellationToken cancellationToken)
    {
        var resp = await PostAsync<JsonElement>(
            creds,
            $"merchant/access/code/{creds.BranchCode}",
            new { },
            token,
            cancellationToken);

        if (resp.ValueKind == JsonValueKind.Array && resp.GetArrayLength() > 0)
        {
            var first = resp[0];
            return FindString(first, "terminalCode", "code");
        }

        return FindString(resp, "terminalCode", "code");
    }

    private async Task<T> PostAsync<T>(
        TaraCredentials creds,
        string path,
        object body,
        string? bearer,
        CancellationToken cancellationToken)
    {
        using var req = new HttpRequestMessage(HttpMethod.Post, $"{creds.BaseUrl.TrimEnd('/')}/{path.TrimStart('/')}");
        if (!string.IsNullOrEmpty(bearer))
            req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {bearer}");
        req.Content = JsonContent.Create(body);
        var response = await _httpClient.SendAsync(req, cancellationToken);
        var json = await response.Content.ReadAsStringAsync(cancellationToken);
        if (!response.IsSuccessStatusCode)
        {
            _logger.LogWarning("Tara POST {Path} failed {Status}: {Body}", path, response.StatusCode, json);
            return default!;
        }

        return JsonSerializer.Deserialize<T>(json)!;
    }

    private async Task<TaraCredentials> LoadCredentialsAsync(CancellationToken cancellationToken)
    {
        var sandbox = await _platform.GetAsync("payment.tara.sandbox", cancellationToken);
        var isSandbox = sandbox is not "false";
        var baseFromDb = await _platform.GetAsync("payment.tara.baseUrl", cancellationToken);
        var baseUrl = !string.IsNullOrWhiteSpace(baseFromDb)
            ? baseFromDb
            : isSandbox ? DefaultSandboxBase : DefaultProdBase;

        return new TaraCredentials(
            await _platform.GetAsync("payment.tara.username", cancellationToken) ?? "",
            await _platform.GetAsync("payment.tara.password", cancellationToken) ?? "",
            await _platform.GetAsync("payment.tara.branchCode", cancellationToken) ?? "",
            await _platform.GetAsync("payment.tara.terminalCode", cancellationToken),
            baseUrl);
    }

    private static string? FindString(JsonElement el, params string[] names)
    {
        if (el.ValueKind == JsonValueKind.String)
            return el.GetString();

        foreach (var name in names)
        {
            if (el.TryGetProperty(name, out var prop))
            {
                if (prop.ValueKind == JsonValueKind.String)
                    return prop.GetString();
                if (prop.ValueKind == JsonValueKind.Number)
                    return prop.GetRawText();
            }
        }

        foreach (var prop in el.EnumerateObject())
        {
            if (prop.Value.ValueKind is JsonValueKind.Object or JsonValueKind.Array)
            {
                if (prop.Value.ValueKind == JsonValueKind.Array && prop.Value.GetArrayLength() > 0)
                {
                    var nested = FindString(prop.Value[0], names);
                    if (nested is not null) return nested;
                }
                var inner = FindString(prop.Value, names);
                if (inner is not null) return inner;
            }
        }

        return null;
    }

    private static bool? FindBool(JsonElement el, params string[] names)
    {
        foreach (var name in names)
        {
            if (el.TryGetProperty(name, out var prop) && prop.ValueKind is JsonValueKind.True or JsonValueKind.False)
                return prop.GetBoolean();
        }
        return null;
    }

    private sealed record TaraCredentials(
        string Username,
        string Password,
        string BranchCode,
        string? TerminalCode,
        string BaseUrl)
    {
        public bool IsConfigured =>
            !string.IsNullOrWhiteSpace(Username)
            && !string.IsNullOrWhiteSpace(Password)
            && !string.IsNullOrWhiteSpace(BranchCode);
    }
}
