using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using CMSMicroservice.Application.Common.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace CMSMicroservice.Infrastructure.Services.Payment;

/// <summary>
/// پیاده‌سازی درگاه پرداخت زرین‌پال
/// ساپورت Sandbox (تست) و Production
/// </summary>
public class ZarinPalPaymentService : IPaymentGatewayService
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<ZarinPalPaymentService> _logger;
    private readonly string _merchantId;
    private readonly bool _useSandbox;

    // آدرس‌های Production
    private const string ProductionApiBase = "https://api.zarinpal.com";
    private const string ProductionStartPayBase = "https://www.zarinpal.com";

    // آدرس‌های Sandbox
    private const string SandboxApiBase = "https://sandbox.zarinpal.com";
    private const string SandboxStartPayBase = "https://sandbox.zarinpal.com";

    // مسیرهای API (مشترک)
    private const string RequestEndpoint = "/pg/v4/payment/request.json";
    private const string VerifyEndpoint = "/pg/v4/payment/verify.json";
    private const string UnverifiedEndpoint = "/pg/v4/payment/unVerified.json";
    private const string StartPayPath = "/pg/StartPay/";

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    public ZarinPalPaymentService(
        HttpClient httpClient,
        IConfiguration configuration,
        ILogger<ZarinPalPaymentService> logger)
    {
        _httpClient = httpClient;
        _logger = logger;

        _merchantId = configuration["ZarinPal:MerchantId"]
            ?? throw new InvalidOperationException("ZarinPal:MerchantId is not configured.");
        _useSandbox = configuration.GetValue<bool>("ZarinPal:UseSandbox", true);

        var apiBase = _useSandbox ? SandboxApiBase : ProductionApiBase;
        _httpClient.BaseAddress = new Uri(apiBase);

        _logger.LogInformation("ZarinPal payment service initialized. Mode: {Mode}",
            _useSandbox ? "🧪 Sandbox" : "🏦 Production");
    }

    /// <summary>
    /// مرحله ۱: ارسال درخواست پرداخت به زرین‌پال و دریافت Authority
    /// </summary>
    public async Task<PaymentInitiateResult> InitiatePaymentAsync(
        PaymentRequest request,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // مبلغ از caller به تومان می‌رسد — تبدیل به ریال برای زرین‌پال (×10)
            var amountInToman = (long)request.Amount;
            var amountInRials = amountInToman * 10;

            var zarinPalRequest = new ZarinPalPaymentRequest
            {
                MerchantId = _merchantId,
                Amount = amountInRials,
                Description = request.Description,
                CallbackUrl = request.CallbackUrl,
                Metadata = new ZarinPalMetadata
                {
                    Mobile = string.IsNullOrWhiteSpace(request.Mobile) ? null : request.Mobile
                }
            };

            var jsonContent = JsonSerializer.Serialize(zarinPalRequest, JsonOptions);
            var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");

            _logger.LogInformation(
                "ZarinPal payment request: Amount={AmountToman} Toman ({AmountRial} Rial), User={UserId}, Sandbox={Sandbox}",
                amountInToman, amountInRials, request.UserId, _useSandbox);

            var response = await _httpClient.PostAsync(RequestEndpoint, content, cancellationToken);
            var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);

            // لاگ HTTP status — مهم برای دیباگ
            if (!response.IsSuccessStatusCode)
            {
                _logger.LogError(
                    "ZarinPal HTTP error: StatusCode={StatusCode}, Body={Body}",
                    (int)response.StatusCode, responseBody);

                return new PaymentInitiateResult
                {
                    IsSuccess = false,
                    ErrorMessage = $"خطای ارتباط با زرین‌پال (HTTP {(int)response.StatusCode}). لطفاً MerchantId و IP سرور در پنل زرین‌پال بررسی شود."
                };
            }

            _logger.LogDebug("ZarinPal request response: {StatusCode} - {Body}",
                response.StatusCode, responseBody);

            var result = JsonSerializer.Deserialize<ZarinPalResponse>(responseBody, JsonOptions);

            if (result?.Data?.Code == 100 && !string.IsNullOrEmpty(result.Data.Authority))
            {
                var startPayBase = _useSandbox ? SandboxStartPayBase : ProductionStartPayBase;
                var gatewayUrl = $"{startPayBase}{StartPayPath}{result.Data.Authority}";

                _logger.LogInformation(
                    "ZarinPal payment initiated successfully: Authority={Authority}, GatewayUrl={Url}",
                    result.Data.Authority, gatewayUrl);

                return new PaymentInitiateResult
                {
                    IsSuccess = true,
                    RefId = result.Data.Authority,
                    GatewayUrl = gatewayUrl
                };
            }

            // خطا
            var errorCode = result?.Errors?.Code ?? result?.Data?.Code ?? -1;
            var errorMessage = result?.Errors?.Message ?? "خطای ناشناخته از زرین‌پال";

            _logger.LogError(
                "ZarinPal payment request failed: Code={Code}, Message={Message}",
                errorCode, errorMessage);

            return new PaymentInitiateResult
            {
                IsSuccess = false,
                ErrorMessage = $"خطای درگاه زرین‌پال (کد {errorCode}): {errorMessage}"
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "ZarinPal InitiatePayment exception");
            return new PaymentInitiateResult
            {
                IsSuccess = false,
                ErrorMessage = $"خطا در ارتباط با درگاه زرین‌پال: {ex.Message}"
            };
        }
    }

    /// <summary>
    /// تأیید پرداخت بدون مبلغ — برای سازگاری با اینترفیس.
    /// ⚠ زرین‌پال مبلغ را در Verify نیاز دارد. از overload با amount استفاده کنید.
    /// </summary>
    public Task<PaymentVerificationResult> VerifyPaymentAsync(
        string refId,
        string verificationToken,
        CancellationToken cancellationToken = default)
    {
        _logger.LogWarning("ZarinPal VerifyPaymentAsync called without amount — verification may fail!");
        return VerifyPaymentWithAmountAsync(refId, verificationToken, 0, cancellationToken);
    }

    /// <summary>
    /// تأیید پرداخت با مبلغ — نسخه اصلی برای زرین‌پال
    /// refId = Authority، verificationToken = Status (OK/NOK)، amount = مبلغ به تومان
    /// </summary>
    public Task<PaymentVerificationResult> VerifyPaymentAsync(
        string refId,
        string verificationToken,
        decimal amount,
        CancellationToken cancellationToken = default)
    {
        return VerifyPaymentWithAmountAsync(refId, verificationToken, amount, cancellationToken);
    }

    private async Task<PaymentVerificationResult> VerifyPaymentWithAmountAsync(
        string refId,
        string verificationToken,
        decimal amountInToman,
        CancellationToken cancellationToken)
    {
        try
        {
            // verificationToken باید "OK" باشد — در غیر اینصورت کاربر لغو کرده
            if (!string.Equals(verificationToken, "OK", StringComparison.OrdinalIgnoreCase))
            {
                _logger.LogWarning("ZarinPal payment cancelled by user: Authority={Authority}", refId);
                return new PaymentVerificationResult
                {
                    IsSuccess = false,
                    RefId = refId,
                    Message = "پرداخت توسط کاربر لغو شد"
                };
            }

            // تبدیل تومان به ریال برای زرین‌پال (×10)
            var amountInRials = (long)(amountInToman * 10);

            var verifyRequest = new ZarinPalVerifyRequest
            {
                MerchantId = _merchantId,
                Authority = refId,
                Amount = amountInRials
            };

            var jsonContent = JsonSerializer.Serialize(verifyRequest, JsonOptions);
            var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");

            _logger.LogInformation("ZarinPal verify request: Authority={Authority}, Amount={AmountToman} Toman ({AmountRial} Rial)", 
                refId, (long)amountInToman, amountInRials);

            var response = await _httpClient.PostAsync(VerifyEndpoint, content, cancellationToken);
            var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);

            _logger.LogDebug("ZarinPal verify response: {StatusCode} - {Body}",
                response.StatusCode, responseBody);

            var result = JsonSerializer.Deserialize<ZarinPalResponse>(responseBody, JsonOptions);

            // code 100 = موفق | code 101 = قبلاً تأیید شده
            if (result?.Data?.Code is 100 or 101)
            {
                _logger.LogInformation(
                    "ZarinPal payment verified: Authority={Authority}, RefId={RefId}, CardPan={CardPan}",
                    refId, result.Data.RefId, result.Data.CardPan);

                return new PaymentVerificationResult
                {
                    IsSuccess = true,
                    RefId = refId,
                    TrackingCode = result.Data.RefId?.ToString(),
                    Amount = (result.Data.Amount ?? 0) / 10, // تبدیل ریال → تومان
                    CardPan = result.Data.CardPan,
                    CardHash = result.Data.CardHash,
                    VerificationCode = result.Data.Code,
                    Message = result.Data.Code == 101
                        ? "تراکنش قبلاً تأیید شده"
                        : "تراکنش موفق"
                };
            }

            var errorCode = result?.Errors?.Code ?? result?.Data?.Code ?? -1;
            var errorMessage = result?.Errors?.Message ?? "تأیید تراکنش ناموفق";

            _logger.LogError(
                "ZarinPal verify failed: Authority={Authority}, Code={Code}, Message={Message}",
                refId, errorCode, errorMessage);

            return new PaymentVerificationResult
            {
                IsSuccess = false,
                RefId = refId,
                Message = $"تأیید پرداخت ناموفق (کد {errorCode}): {errorMessage}"
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "ZarinPal VerifyPayment exception: Authority={Authority}", refId);
            return new PaymentVerificationResult
            {
                IsSuccess = false,
                RefId = refId,
                Message = $"خطا در تأیید تراکنش: {ex.Message}"
            };
        }
    }

    /// <summary>
    /// فهرست Authority‌هایی که کاربر پرداخت کرده ولی Verify نشده‌اند
    /// زرین‌پال endpoint: POST /pg/v4/payment/unVerified.json
    /// </summary>
    public async Task<IReadOnlyList<string>> GetUnverifiedAuthoritiesAsync(
        CancellationToken cancellationToken = default)
    {
        try
        {
            var requestBody = new ZarinPalUnverifiedRequest { MerchantId = _merchantId };
            var json = JsonSerializer.Serialize(requestBody, JsonOptions);
            var content = new StringContent(json, Encoding.UTF8, "application/json");

            var response = await _httpClient.PostAsync(UnverifiedEndpoint, content, cancellationToken);
            var body = await response.Content.ReadAsStringAsync(cancellationToken);

            _logger.LogDebug("ZarinPal unVerified response: {StatusCode} - {Body}", response.StatusCode, body);

            var result = JsonSerializer.Deserialize<ZarinPalUnverifiedResponse>(body, JsonOptions);

            if (result?.Data?.Code == 100 && result.Data.Authorities?.Count > 0)
            {
                var authorities = result.Data.Authorities
                    .Where(a => !string.IsNullOrEmpty(a.Authority))
                    .Select(a => a.Authority!)
                    .ToList();

                _logger.LogInformation(
                    "ZarinPal unVerified: {Count} unverified payment(s) found", authorities.Count);

                return authorities;
            }

            _logger.LogDebug("ZarinPal unVerified: no pending payments. Code={Code}", result?.Data?.Code);
            return Array.Empty<string>();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "ZarinPal GetUnverifiedAuthorities failed — will fallback to DB scan");
            return Array.Empty<string>();
        }
    }

    /// <summary>
    /// زرین‌پال Payout مستقیم ندارد — این متد NotSupported برمی‌گرداند
    /// برای Payout باید از سرویس دیگری (مثل دایا) استفاده شود
    /// </summary>
    public Task<PayoutResult> ProcessPayoutAsync(
        PayoutRequest request,
        CancellationToken cancellationToken = default)
    {
        _logger.LogWarning("ZarinPal does not support direct payout. Use a different provider for payouts.");
        return Task.FromResult(new PayoutResult
        {
            IsSuccess = false,
            Message = "درگاه زرین‌پال از قابلیت واریز مستقیم پشتیبانی نمی‌کند",
            ProcessedAt = DateTime.UtcNow
        });
    }

    // ── ZarinPal Request/Response DTOs ──

    private class ZarinPalPaymentRequest
    {
        public string MerchantId { get; set; } = string.Empty;
        public long Amount { get; set; }
        public string Description { get; set; } = string.Empty;
        public string CallbackUrl { get; set; } = string.Empty;
        public ZarinPalMetadata? Metadata { get; set; }
    }

    private class ZarinPalMetadata
    {
        public string? Mobile { get; set; }
        public string? Email { get; set; }
    }

    private class ZarinPalVerifyRequest
    {
        public string MerchantId { get; set; } = string.Empty;
        public long Amount { get; set; }
        public string Authority { get; set; } = string.Empty;
    }

    private class ZarinPalResponse
    {
        public ZarinPalResponseData? Data { get; set; }
        
        [JsonConverter(typeof(ZarinPalErrorsConverter))]
        public ZarinPalResponseErrors? Errors { get; set; }
    }

    private class ZarinPalResponseData
    {
        public int? Code { get; set; }
        public string? Message { get; set; }
        public string? Authority { get; set; }
        public long? RefId { get; set; }
        public long? Amount { get; set; }
        public string? CardPan { get; set; }
        public string? CardHash { get; set; }
        public string? FeeType { get; set; }
        public long? Fee { get; set; }
    }

    private class ZarinPalResponseErrors
    {
        public int? Code { get; set; }
        public string? Message { get; set; }
    }

    private class ZarinPalUnverifiedRequest
    {
        public string MerchantId { get; set; } = string.Empty;
    }

    private class ZarinPalUnverifiedResponse
    {
        public ZarinPalUnverifiedData? Data { get; set; }
    }

    private class ZarinPalUnverifiedData
    {
        public int? Code { get; set; }
        public string? Message { get; set; }
        public List<ZarinPalUnverifiedAuthority>? Authorities { get; set; }
    }

    private class ZarinPalUnverifiedAuthority
    {
        public string? Authority { get; set; }
        public long? Amount { get; set; }
        public string? Channel { get; set; }
        public string? Date { get; set; }
    }

    /// <summary>
    /// ZarinPal returns errors as [] (empty array) when no error, or as {...} object when there's an error.
    /// This converter handles both cases.
    /// </summary>
    private class ZarinPalErrorsConverter : JsonConverter<ZarinPalResponseErrors?>
    {
        public override ZarinPalResponseErrors? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.StartArray)
            {
                // Skip the empty array []
                while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) { }
                return null;
            }

            if (reader.TokenType == JsonTokenType.StartObject)
            {
                return JsonSerializer.Deserialize<ZarinPalResponseErrors>(ref reader);
            }

            if (reader.TokenType == JsonTokenType.Null)
            {
                return null;
            }

            reader.Skip();
            return null;
        }

        public override void Write(Utf8JsonWriter writer, ZarinPalResponseErrors? value, JsonSerializerOptions options)
        {
            JsonSerializer.Serialize(writer, value, options);
        }
    }
}
