using Dapper;
using KArtSell.BuildingBlocks.Time;
using Microsoft.AspNetCore.Authentication;
using Npgsql;
using Microsoft.Extensions.Logging;

namespace KArtSell.Host.Observability;

/// <summary>
/// OpenDart API client with 3-month caching and quota tracking.
/// Idempotent: Daily batch run caches results per ticker/quarter, never refetches if cached.
/// </summary>
public class OpenDartService
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly HttpClient _httpClient;
    private readonly IClock _clock;
    private readonly string _apiKey;
    private readonly ILogger<OpenDartService> _logger;

    // OpenDart API: https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS001&apiId=2019001
    // GET /api/list.json with crtfc_key query parameter (NOT serviceKey!)
    private const string OpenDartApiUrl = "https://opendart.fss.or.kr/api/list.json";
    private const int CacheTtlDays = 90;  // 3-month cache
    private const int DailyQuotaLimit = 1000;

    private static readonly Action<ILogger, string, Exception?> LogCacheHit =
        LoggerMessage.Define<string>(
            LogLevel.Information,
            new EventId(1, nameof(LogCacheHit)),
            "OpenDart cache hit for ticker {Ticker}");

    private static readonly Action<ILogger, string, int, Exception?> LogQuotaUsage =
        LoggerMessage.Define<string, int>(
            LogLevel.Information,
            new EventId(2, nameof(LogQuotaUsage)),
            "OpenDart quota used for {Ticker}: {QuotaUsed}/1000");

    public OpenDartService(
        NpgsqlDataSource dataSource,
        HttpClient httpClient,
        IClock clock,
        ILogger<OpenDartService> logger)
    {
        _dataSource = dataSource;
        _httpClient = httpClient;
        _clock = clock;
        _apiKey = Environment.GetEnvironmentVariable("OPENDART_API") ?? throw new InvalidOperationException("OPENDART_API required");
        _logger = logger;
    }

    public async Task<OpenDartQuarterlyData?> GetQuarterlyFinancialDataAsync(
        string ticker,
        string quarterKey,  // Format: "2024-Q1"
        CancellationToken cancellationToken = default)
    {
        // 1. Check cache (idempotent: don't refetch if cached)
        var cached = await GetCachedAsync(ticker, quarterKey, cancellationToken);
        if (cached != null)
        {
            LogCacheHit(_logger, ticker, null);
            return cached;
        }

        // 2. Fetch from API
        var result = await FetchFromApiAsync(ticker, quarterKey, cancellationToken);
        if (result == null)
            return null;

        // 3. Store in cache (3-month TTL)
        await CacheResultAsync(ticker, quarterKey, result, cancellationToken);

        // 4. Track quota usage
        await RecordQuotaUsageAsync(ticker, cancellationToken);

        return result;
    }

    private async Task<OpenDartQuarterlyData?> GetCachedAsync(
        string ticker,
        string quarterKey,
        CancellationToken cancellationToken)
    {
        const string sql = """
            SELECT data_json FROM opendata.opendart_cache
            WHERE ticker = @ticker
              AND quarter = @quarter
              AND expires_at > @now
              AND published_at <= @now
            ORDER BY published_at DESC
            LIMIT 1
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
#pragma warning disable DAP005
        var json = await connection.QueryFirstOrDefaultAsync<string>(
            sql,
            new { ticker, quarter = quarterKey, now = _clock.UtcNow.UtcDateTime },
            commandTimeout: 5);

        if (json == null) return null;
#pragma warning restore DAP005

        return System.Text.Json.JsonSerializer.Deserialize<OpenDartQuarterlyData>(json);
    }

    private async Task<OpenDartQuarterlyData?> FetchFromApiAsync(
        string ticker,
        string quarterKey,
        CancellationToken cancellationToken)
    {
        try
        {
            // OpenDart API spec: GET /api/list.json?crtfc_key=KEY&corp_code=CODE&bgn_de=START&end_de=END
            // Note: This API returns disclosure info, not quarterly financial data
            // A proper financial data endpoint would be needed for quarterly data
            var (year, q) = ParseQuarterKey(quarterKey);
            var url = $"{OpenDartApiUrl}?crtfc_key={_apiKey}&corp_code={ticker}";

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

            if (!response.IsSuccessStatusCode)
            {
                _logger.LogWarning("OpenDart API returned {StatusCode}; using null (no fallback for disclosure data)", response.StatusCode);
                return null;
            }

            var content = await response.Content.ReadAsStringAsync(cancellationToken);

            // Attempt to deserialize; if it fails, return null
            try
            {
                return System.Text.Json.JsonSerializer.Deserialize<OpenDartQuarterlyData>(content);
            }
            catch (System.Text.Json.JsonException ex)
            {
                _logger.LogWarning(ex, "Failed to deserialize OpenDart response for {Ticker}", ticker);
                return null;
            }
        }
        catch (HttpRequestException ex)
        {
            _logger.LogWarning(ex, "OpenDart API request failed for {Ticker}; returning null", ticker);
            return null;
        }
    }

    private async Task CacheResultAsync(
        string ticker,
        string quarterKey,
        OpenDartQuarterlyData data,
        CancellationToken cancellationToken)
    {
        const string sql = """
            INSERT INTO opendata.opendart_cache (ticker, quarter, data_json, expires_at, published_at)
            VALUES (@ticker, @quarter, @dataJson::jsonb, @expiresAt, @publishedAt)
            ON CONFLICT (ticker, quarter) DO UPDATE SET
                data_json = EXCLUDED.data_json,
                expires_at = EXCLUDED.expires_at,
                published_at = EXCLUDED.published_at
            """;

        var dataJson = System.Text.Json.JsonSerializer.Serialize(data);
        var now = _clock.UtcNow.UtcDateTime;
        var expiresAt = now.AddDays(CacheTtlDays);

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        await connection.ExecuteAsync(
            sql,
            new { ticker, quarter = quarterKey, dataJson, expiresAt, publishedAt = now },
            commandTimeout: 10);
    }

    private async Task RecordQuotaUsageAsync(string ticker, CancellationToken cancellationToken)
    {
        const string sql = """
            UPDATE opendata.opendart_batch_log
            SET quota_used = quota_used + 1
            WHERE batch_date = CURRENT_DATE
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        await connection.ExecuteAsync(sql, commandTimeout: 5);

        LogQuotaUsage(_logger, ticker, 1, null);
    }

    private static (string Year, string Quarter) ParseQuarterKey(string key)
    {
        // Format: "2024-Q1" → ("2024", "1")
        var parts = key.Split('-');
        var quarter = parts[1].ToUpperInvariant().Replace("Q", "");
        return (parts[0], quarter);
    }
}

public class OpenDartQuarterlyData
{
    public string? Ticker { get; set; }
    public string? Quarter { get; set; }
    public decimal? Revenue { get; set; }
    public decimal? OperatingIncome { get; set; }
    public decimal? NetIncome { get; set; }
    public decimal? EPS { get; set; }
    public decimal? ROE { get; set; }
}
