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

namespace Meezi.Infrastructure.ExternalServices;

/// <summary>
/// Snapp Pay online merchant API (BNPL / installment).
/// Docs: https://snapppay.ir — OAuth + payment token + verify/settle.
/// </summary>
public class SnappPayGateway : ISnappPayGateway
{
    private const string DefaultProdBase = "https://api.snapppay.ir";
    private const string DefaultSandboxBase = "https://fms-gateway-staging.apps.public.okd4.teh-1.snappcloud.io";

    private readonly HttpClient _httpClient;
    private readonly IPlatformRuntimeConfig _platform;
    private readonly ILogger<SnappPayGateway> _logger;

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

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

    public async Task<SnappPayInitResult> RequestPaymentAsync(
        long amountRials,
        string transactionId,
        string returnUrl,
        CancellationToken cancellationToken = default)
    {
        var creds = await LoadCredentialsAsync(cancellationToken);
        if (!creds.IsConfigured)
        {
            var mockToken = "MOCK-SNAPP-" + transactionId;
            var mockUrl = $"{returnUrl}?paymentToken={mockToken}&transactionId={transactionId}&state=OK";
            _logger.LogInformation("SnappPay mock payment {TransactionId} amount {Amount} Rials", transactionId, amountRials);
            return new SnappPayInitResult(true, mockToken, mockUrl, null);
        }

        var bearer = await GetBearerTokenAsync(creds, cancellationToken);
        if (bearer is null)
            return new SnappPayInitResult(false, null, null, "Could not obtain Snapp Pay access token.");

        var baseUrl = creds.BaseUrl.TrimEnd('/');
        var payload = new
        {
            amount = amountRials,
            paymentMethodTypeDto = "INSTALLMENT",
            returnURL = returnUrl,
            transactionId,
            externalSourceAmount = 0,
            discountAmount = 0,
            cartList = new[]
            {
                new
                {
                    cartId = transactionId,
                    cartItems = new[]
                    {
                        new
                        {
                            id = 1,
                            name = "Meezi subscription",
                            amount = amountRials,
                            count = 1,
                            category = "subscription",
                            commissionType = 0
                        }
                    }
                }
            }
        };

        using var req = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}/api/online/payment/v1/token");
        req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearer);
        req.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");

        var response = await _httpClient.SendAsync(req, cancellationToken);
        var json = await response.Content.ReadAsStringAsync(cancellationToken);
        if (!response.IsSuccessStatusCode)
        {
            _logger.LogWarning("SnappPay token request failed {Status}: {Body}", response.StatusCode, json);
            return new SnappPayInitResult(false, null, null, "Snapp Pay payment token request failed.");
        }

        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;
        var paymentToken = FindString(root, "paymentToken", "payment_token", "token");
        var paymentUrl = FindString(root, "paymentPageUrl", "payment_page_url", "redirectUrl", "redirect_url", "url");
        if (string.IsNullOrEmpty(paymentToken) || string.IsNullOrEmpty(paymentUrl))
            return new SnappPayInitResult(false, null, null, "Snapp Pay returned an incomplete response.");

        return new SnappPayInitResult(true, paymentToken, paymentUrl, null);
    }

    public async Task<SnappPayVerifyResult> VerifyAndSettleAsync(
        string paymentToken,
        CancellationToken cancellationToken = default)
    {
        if (paymentToken.StartsWith("MOCK-SNAPP-", StringComparison.Ordinal))
        {
            _logger.LogInformation("SnappPay mock verify {Token}", paymentToken);
            return new SnappPayVerifyResult(true, paymentToken, null);
        }

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

        var bearer = await GetBearerTokenAsync(creds, cancellationToken);
        if (bearer is null)
            return new SnappPayVerifyResult(false, null, "Could not obtain Snapp Pay access token.");

        var baseUrl = creds.BaseUrl.TrimEnd('/');
        var verify = await PostBearerAsync($"{baseUrl}/api/online/payment/v1/verify", bearer,
            new { paymentToken }, cancellationToken);
        if (!verify.Success)
            return new SnappPayVerifyResult(false, null, verify.Error);

        var settle = await PostBearerAsync($"{baseUrl}/api/online/payment/v1/settle", bearer,
            new { paymentToken }, cancellationToken);
        if (!settle.Success)
            return new SnappPayVerifyResult(false, null, settle.Error);

        return new SnappPayVerifyResult(true, paymentToken, null);
    }

    private async Task<(bool Success, string? Error)> PostBearerAsync(
        string url,
        string bearer,
        object body,
        CancellationToken cancellationToken)
    {
        using var req = new HttpRequestMessage(HttpMethod.Post, url);
        req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearer);
        req.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
        var response = await _httpClient.SendAsync(req, cancellationToken);
        var json = await response.Content.ReadAsStringAsync(cancellationToken);
        if (!response.IsSuccessStatusCode)
        {
            _logger.LogWarning("SnappPay POST {Url} failed {Status}: {Body}", url, response.StatusCode, json);
            return (false, "Snapp Pay API call failed.");
        }

        try
        {
            using var doc = JsonDocument.Parse(json);
            if (doc.RootElement.TryGetProperty("successful", out var ok) && ok.ValueKind == JsonValueKind.False)
                return (false, FindString(doc.RootElement, "errorData", "message") ?? "Snapp Pay rejected the request.");
        }
        catch
        {
            // Some responses are plain success HTTP only
        }

        return (true, null);
    }

    private async Task<string?> GetBearerTokenAsync(SnappPayCredentials creds, CancellationToken cancellationToken)
    {
        var baseUrl = creds.BaseUrl.TrimEnd('/');
        var basic = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{creds.ClientId}:{creds.ClientSecret}"));
        using var req = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}/api/online/v1/oauth/token");
        req.Headers.Authorization = new AuthenticationHeaderValue("Basic", basic);
        req.Content = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["grant_type"] = "password",
            ["scope"] = "online-merchant",
            ["username"] = creds.Username,
            ["password"] = creds.Password
        });

        var response = await _httpClient.SendAsync(req, cancellationToken);
        var json = await response.Content.ReadAsStringAsync(cancellationToken);
        if (!response.IsSuccessStatusCode)
        {
            _logger.LogWarning("SnappPay OAuth failed {Status}: {Body}", response.StatusCode, json);
            return null;
        }

        var token = JsonSerializer.Deserialize<OAuthTokenResponse>(json);
        return token?.AccessToken;
    }

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

        return new SnappPayCredentials(
            await _platform.GetAsync("payment.snapppay.clientId", cancellationToken) ?? "",
            await _platform.GetAsync("payment.snapppay.clientSecret", cancellationToken) ?? "",
            await _platform.GetAsync("payment.snapppay.username", cancellationToken) ?? "",
            await _platform.GetAsync("payment.snapppay.password", cancellationToken) ?? "",
            baseUrl);
    }

    private static string? FindString(JsonElement el, params string[] names)
    {
        foreach (var name in names)
        {
            if (el.TryGetProperty(name, out var prop) && prop.ValueKind == JsonValueKind.String)
                return prop.GetString();
        }
        foreach (var prop in el.EnumerateObject())
        {
            if (prop.Value.ValueKind == JsonValueKind.Object)
            {
                var nested = FindString(prop.Value, names);
                if (nested is not null) return nested;
            }
        }
        return null;
    }

    private sealed record SnappPayCredentials(
        string ClientId,
        string ClientSecret,
        string Username,
        string Password,
        string BaseUrl)
    {
        public bool IsConfigured =>
            !string.IsNullOrWhiteSpace(ClientId)
            && !string.IsNullOrWhiteSpace(ClientSecret)
            && !string.IsNullOrWhiteSpace(Username)
            && !string.IsNullOrWhiteSpace(Password);
    }

    private sealed class OAuthTokenResponse
    {
        [JsonPropertyName("access_token")]
        public string? AccessToken { get; set; }
    }
}
