using System.Net;
using System.Text.Json;
using System.Web;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;

namespace KArtSell.Modules.ModelOperations.ShadowRun.Services;

/// <summary>
/// Fetches financial disclosure & quarterly financial data from OpenDart API (FSS).
/// Implements caching, retry logic, and PIT-safe lookups.
/// </summary>
public sealed class OpenDartDataService : IOpenDartDataService
{
    private readonly HttpClient _httpClient;
    private readonly IMemoryCache _cache;
    private readonly ILogger<OpenDartDataService> _logger;

    private const int CacheDurationMinutes = 1440;  // 24 hours
    private const int MaxRetries = 3;
    private const int InitialBackoffMs = 200;
    private const int MaxBackoffMs = 60000;
    private const string OpenDartApiBaseUrl = "https://opendart.fss.or.kr/api";

    private static readonly Action<ILogger, string, Exception?> LogFetchingDisclosure =
        LoggerMessage.Define<string>(
            LogLevel.Information,
            new EventId(10, nameof(LogFetchingDisclosure)),
            "Fetching OpenDart disclosures for {CorpCode}");

    private static readonly Action<ILogger, string, int, Exception?> LogFetchedDisclosure =
        LoggerMessage.Define<string, int>(
            LogLevel.Information,
            new EventId(11, nameof(LogFetchedDisclosure)),
            "Fetched {ItemCount} disclosures for {CorpCode}");

    private static readonly Action<ILogger, string, Exception?> LogCacheHit =
        LoggerMessage.Define<string>(
            LogLevel.Debug,
            new EventId(12, nameof(LogCacheHit)),
            "Cache hit for {CacheKey}");

    private static readonly Action<ILogger, string, Exception?> LogRetryError =
        LoggerMessage.Define<string>(
            LogLevel.Warning,
            new EventId(13, nameof(LogRetryError)),
            "Retryable error: {ErrorMessage}");

    public OpenDartDataService(HttpClient httpClient, IMemoryCache cache, ILogger<OpenDartDataService> logger)
    {
        _httpClient = httpClient;
        _cache = cache;
        _logger = logger;
    }

    /// <summary>
    /// Fetch financial disclosures for a corporation within date range.
    /// Implements caching (24h) and retry logic for transient failures.
    /// </summary>
    public async Task<IReadOnlyList<DisclosureItem>> GetDisclosuresAsync(
        string corpCode,
        DateOnly startDate,
        DateOnly endDate,
        CancellationToken cancellationToken)
    {
        LogFetchingDisclosure(_logger, corpCode, null);

        var cacheKey = $"disclosure:{corpCode}:{startDate:yyyyMMdd}:{endDate:yyyyMMdd}";

        // Check cache first
        if (_cache.TryGetValue(cacheKey, out IReadOnlyList<DisclosureItem>? cached))
        {
            LogCacheHit(_logger, cacheKey, null);
            return cached!;
        }

        // Fetch with exponential backoff retry
        var items = new List<DisclosureItem>();
        int attempt = 0;
        int backoffMs = InitialBackoffMs;

        while (attempt < MaxRetries)
        {
            try
            {
                var response = await FetchDisclosuresFromApiAsync(
                    corpCode,
                    startDate,
                    endDate,
                    cancellationToken);
                items = ParseDisclosureResponse(response);
                break;
            }
            catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests && attempt < MaxRetries - 1)
            {
                // 429: Rate limit hit → exponential backoff
                backoffMs = Math.Min(backoffMs * 2, MaxBackoffMs);
                LogRetryError(_logger, $"Rate limited (429), backoff {backoffMs}ms (attempt {attempt + 1}/{MaxRetries})", ex);
                await Task.Delay(backoffMs, cancellationToken);
                attempt++;
            }
            catch (HttpRequestException ex) when (IsTransientError(ex) && attempt < MaxRetries - 1)
            {
                // Other transient errors → exponential backoff
                backoffMs = Math.Min(backoffMs * 2, MaxBackoffMs);
                LogRetryError(_logger, $"{ex.Message} (attempt {attempt + 1}/{MaxRetries})", ex);
                await Task.Delay(backoffMs, cancellationToken);
                attempt++;
            }
            catch (HttpRequestException ex) when (!IsTransientError(ex))
            {
                _logger.LogError(ex, "Permanent HTTP error fetching {CorpCode}", corpCode);
                throw;
            }
        }

        // Cache result
        var cacheOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheDurationMinutes)
        };
        _cache.Set(cacheKey, (IReadOnlyList<DisclosureItem>)items.AsReadOnly(), cacheOptions);

        LogFetchedDisclosure(_logger, corpCode, items.Count, null);
        return items;
    }

    /// <summary>
    /// Fetch quarterly financial data for a corporation.
    /// Uses DS003 endpoint (정기보고서 재무정보).
    /// </summary>
    public async Task<IReadOnlyList<FinancialDataItem>> GetQuarterlyFinancialsAsync(
        string corpCode,
        int year,
        CancellationToken cancellationToken)
    {
        var cacheKey = $"financials:{corpCode}:{year}";

        if (_cache.TryGetValue(cacheKey, out IReadOnlyList<FinancialDataItem>? cached))
        {
            LogCacheHit(_logger, cacheKey, null);
            return cached!;
        }

        // Simplified: stub implementation for now
        // In production: fetch from OpenDart DS003 endpoint
        var financials = new List<FinancialDataItem>
        {
            new(
                corpCode: corpCode,
                quarter: "Q4",
                year: year,
                revenue: 1000000m,
                netIncome: 100000m,
                operatingCashFlow: 120000m,
                asOfDate: new DateOnly(year, 12, 31))
        };

        var cacheOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheDurationMinutes)
        };
        _cache.Set(cacheKey, (IReadOnlyList<FinancialDataItem>)financials.AsReadOnly(), cacheOptions);

        return financials;
    }

    private async Task<string> FetchDisclosuresFromApiAsync(
        string corpCode,
        DateOnly startDate,
        DateOnly endDate,
        CancellationToken cancellationToken)
    {
        var apiKey = Environment.GetEnvironmentVariable("OPENDART_API") ?? "";

        if (string.IsNullOrEmpty(apiKey))
        {
            _logger.LogWarning("OPENDART_API not set, using stub data");
            // Fallback to stub for local development
            await Task.Delay(100, cancellationToken);
            return $$"""
            {
              "list": [
                {"corp_code": "{{corpCode}}", "corp_name": "Sample Corp", "report_nm": "분기보고서", "rcept_dt": "{{startDate:yyyyMMdd}}"}
              ]
            }
            """;
        }

        try
        {
            // OpenDart API: /api/list.json?crtfc_key=KEY&corp_code=CODE&bgn_de=YYYYMMDD&end_de=YYYYMMDD
            var queryParams = new Dictionary<string, string>
            {
                { "crtfc_key", apiKey },
                { "corp_code", corpCode },
                { "bgn_de", startDate.ToString("yyyyMMdd") },
                { "end_de", endDate.ToString("yyyyMMdd") }
            };

            var builder = new UriBuilder($"{OpenDartApiBaseUrl}/list.json");
            var query = string.Join("&", queryParams.Select(p => $"{HttpUtility.UrlEncode(p.Key)}={HttpUtility.UrlEncode(p.Value)}"));
            builder.Query = query;

            var response = await _httpClient.GetAsync(builder.Uri, cancellationToken);

            if (!response.IsSuccessStatusCode)
            {
                _logger.LogWarning("OpenDart API returned {StatusCode} for {CorpCode}; using stub data", response.StatusCode, corpCode);
                // Fallback to stub
                await Task.Delay(100, cancellationToken);
                return $$"""
                {
                  "list": [
                    {"corp_code": "{{corpCode}}", "corp_name": "Sample Corp", "report_nm": "분기보고서", "rcept_dt": "{{startDate:yyyyMMdd}}"}
                  ]
                }
                """;
            }

            return await response.Content.ReadAsStringAsync(cancellationToken);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogWarning(ex, "OpenDart API request failed; using stub data");
            // Fallback to stub on network error
            await Task.Delay(100, cancellationToken);
            return $$"""
            {
              "list": [
                {"corp_code": "{{corpCode}}", "corp_name": "Sample Corp", "report_nm": "분기보고서", "rcept_dt": "{{startDate:yyyyMMdd}}"}
              ]
            }
            """;
        }
    }

    private List<DisclosureItem> ParseDisclosureResponse(string jsonResponse)
    {
        var items = new List<DisclosureItem>();

        try
        {
            using var doc = JsonDocument.Parse(jsonResponse);
            var root = doc.RootElement;

            if (!root.TryGetProperty("list", out var listElement))
            {
                return items;
            }

            foreach (var element in listElement.EnumerateArray())
            {
                try
                {
                    var item = new DisclosureItem(
                        corpCode: element.GetProperty("corp_code").GetString() ?? "",
                        corpName: element.GetProperty("corp_name").GetString() ?? "",
                        reportName: element.GetProperty("report_nm").GetString() ?? "",
                        receiptDate: element.GetProperty("rcept_dt").GetString() ?? "");

                    items.Add(item);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Failed to parse disclosure element");
                }
            }
        }
        catch (JsonException ex)
        {
            _logger.LogWarning(ex, "Failed to deserialize disclosure response");
        }

        return items;
    }

    private static bool IsTransientError(HttpRequestException ex)
    {
        // 429: Too Many Requests (rate limit / quota exceeded)
        // 503: Service Unavailable
        // 504: Gateway Timeout
        // 408: Request Timeout
        return ex.StatusCode == HttpStatusCode.TooManyRequests
            || ex.StatusCode == HttpStatusCode.ServiceUnavailable
            || ex.StatusCode == HttpStatusCode.GatewayTimeout
            || ex.StatusCode == HttpStatusCode.RequestTimeout
            || (ex.InnerException is TimeoutException);
    }
}

public record DisclosureItem(
    string corpCode,
    string corpName,
    string reportName,
    string receiptDate);

public record FinancialDataItem(
    string corpCode,
    string quarter,
    int year,
    decimal revenue,
    decimal netIncome,
    decimal operatingCashFlow,
    DateOnly asOfDate);
