using Dapper;
using Hangfire;
using KArtSell.BuildingBlocks.Time;
using KArtSell.Host.Observability;
using Microsoft.Extensions.Logging;
using Npgsql;

namespace KArtSell.Host.Jobs;

/// <summary>
/// Daily batch job: Refresh OpenDart financial data for all tracked tickers.
/// Runs at 09:00 KST (market open), idempotent per batch_date.
/// </summary>
[Queue("q-fundamentals")]
public class OpenDartDailyBatchJob
{
    private readonly OpenDartService _openDart;
    private readonly NpgsqlDataSource _dataSource;
    private readonly IClock _clock;
    private readonly ILogger<OpenDartDailyBatchJob> _logger;

    private static readonly Action<ILogger, int, int, Exception?> LogBatchStart =
        LoggerMessage.Define<int, int>(
            LogLevel.Information,
            new EventId(1, nameof(LogBatchStart)),
            "OpenDart daily batch started: {TickerCount} tickers, quota {QuotaLimit}");

    private static readonly Action<ILogger, int, Exception?> LogBatchComplete =
        LoggerMessage.Define<int>(
            LogLevel.Information,
            new EventId(2, nameof(LogBatchComplete)),
            "OpenDart daily batch completed: {SuccessCount} tickers fetched");

    public OpenDartDailyBatchJob(
        OpenDartService openDart,
        NpgsqlDataSource dataSource,
        IClock clock,
        ILogger<OpenDartDailyBatchJob> logger)
    {
        _openDart = openDart;
        _dataSource = dataSource;
        _clock = clock;
        _logger = logger;
    }

    public async Task ExecuteAsync(CancellationToken cancellationToken = default)
    {
        var batchDate = DateOnly.FromDateTime(_clock.UtcNow.UtcDateTime);
        var quotaLimit = 1000;

        // 1. Check if batch already ran today (idempotent)
        var existing = await GetBatchLogAsync(batchDate, cancellationToken);
        if (existing?.Status == "success")
        {
            _logger.LogInformation("OpenDart batch already completed for {Date}", batchDate);
            return;
        }

        // 2. Get all tickers to refresh
        var tickers = await GetTrackedTickersAsync(cancellationToken);
        LogBatchStart(_logger, tickers.Count, quotaLimit, null);

        // 3. Create batch log entry (or update existing)
        await InitializeBatchLogAsync(batchDate, quotaLimit, cancellationToken);

        // 4. Fetch latest quarterly data for each ticker
        var successCount = 0;
        var quarter = (int)(_clock.UtcNow.UtcDateTime.Month - 1) / 3 + 1;
        var currentQuarter = $"{_clock.UtcNow.UtcDateTime.Year}-Q{quarter}";

        foreach (var ticker in tickers)
        {
            try
            {
                // Fetch cached or new data
                var data = await _openDart.GetQuarterlyFinancialDataAsync(
                    ticker,
                    currentQuarter,
                    cancellationToken);

                if (data != null)
                    successCount++;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to fetch OpenDart data for {Ticker}", ticker);
            }
        }

        // 5. Mark batch complete
        await CompleteBatchLogAsync(batchDate, successCount, cancellationToken);
        LogBatchComplete(_logger, successCount, null);
    }

    private async Task<List<string>> GetTrackedTickersAsync(CancellationToken cancellationToken)
    {
        const string sql = """
            SELECT DISTINCT ticker
            FROM model_operations.models
            WHERE published_at <= @now
            ORDER BY ticker
            LIMIT 100  -- Safety limit
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        var tickers = await connection.QueryAsync<string>(
            sql,
            new { now = _clock.UtcNow.UtcDateTime },
            commandTimeout: 5);

        return tickers.ToList();
    }

    private async Task<dynamic?> GetBatchLogAsync(
        DateOnly batchDate,
        CancellationToken cancellationToken)
    {
        const string sql = """
            SELECT id, batch_date, quota_limit, quota_used, status, error_message, executed_at, published_at
            FROM opendata.opendart_batch_log
            WHERE batch_date = @batchDate
            ORDER BY published_at DESC
            LIMIT 1
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        return await connection.QueryFirstOrDefaultAsync(
            sql,
            new { batchDate },
            commandTimeout: 5);
    }

    private async Task InitializeBatchLogAsync(
        DateOnly batchDate,
        int quotaLimit,
        CancellationToken cancellationToken)
    {
        const string sql = """
            INSERT INTO opendata.opendart_batch_log (batch_date, quota_limit, status, quota_used)
            VALUES (@batchDate, @quotaLimit, 'in_progress', 0)
            ON CONFLICT (batch_date) DO NOTHING
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        await connection.ExecuteAsync(
            sql,
            new { batchDate, quotaLimit },
            commandTimeout: 5);
    }

    private async Task CompleteBatchLogAsync(
        DateOnly batchDate,
        int successCount,
        CancellationToken cancellationToken)
    {
        const string sql = """
            UPDATE opendata.opendart_batch_log
            SET status = @status, quota_used = @successCount
            WHERE batch_date = @batchDate
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        await connection.ExecuteAsync(
            sql,
            new { status = "success", successCount, batchDate },
            commandTimeout: 5);
    }

}
