using KArtSell.BuildingBlocks.Data;
using KArtSell.BuildingBlocks.Time;
using Microsoft.Extensions.Logging;
using System.Net.Http;

namespace KArtSell.Host.Jobs;

/// <summary>
/// Generates and sends algorithm-based recommendation reports (Daily/Weekly/Monthly).
/// Reports summarize recent sell decisions from SignalEngine.
/// Sends via Telegram with formatted markdown output.
/// Idempotency is handled by Hangfire's recurring job scheduling (same job name = no duplicates).
/// </summary>
public sealed class RecommendationReportGenerator
{
    private readonly IDbConnectionFactory _connectionFactory;
    private readonly HttpClient _httpClient;
    private readonly ILogger<RecommendationReportGenerator> _logger;
    private readonly IClock _clock;
    private readonly string _telegramBotToken;
    private readonly string _telegramChatId;

    public RecommendationReportGenerator(
        IDbConnectionFactory connectionFactory,
        HttpClient httpClient,
        ILogger<RecommendationReportGenerator> logger,
        IClock clock)
    {
        _connectionFactory = connectionFactory;
        _httpClient = httpClient;
        _logger = logger;
        _clock = clock;
        _telegramBotToken = Environment.GetEnvironmentVariable("TELEGRAM_BOT") ?? string.Empty;
        _telegramChatId = Environment.GetEnvironmentVariable("CHAT_ID") ?? string.Empty;
    }

    public async Task<RecommendationReport> GenerateDailyRecommendationAsync(
        DateTime reportDate,
        CancellationToken cancellationToken)
    {
        var startDate = reportDate.Date;
        var endDate = startDate.AddDays(1).AddTicks(-1);

        var recommendations = await GetSellDecisionsAsync(startDate, endDate, cancellationToken);

        return new RecommendationReport
        {
            ReportType = "Daily",
            ReportDate = reportDate,
            PeriodStart = startDate,
            PeriodEnd = endDate,
            Recommendations = recommendations
        };
    }

    public async Task<RecommendationReport> GenerateWeeklyRecommendationAsync(
        DateTime weekStart,
        CancellationToken cancellationToken)
    {
        var startDate = weekStart.Date;
        var endDate = startDate.AddDays(7).AddTicks(-1);

        var recommendations = await GetSellDecisionsAsync(startDate, endDate, cancellationToken);

        return new RecommendationReport
        {
            ReportType = "Weekly",
            ReportDate = _clock.UtcNow.DateTime,
            PeriodStart = startDate,
            PeriodEnd = endDate,
            Recommendations = recommendations
        };
    }

    public async Task<RecommendationReport> GenerateMonthlyRecommendationAsync(
        DateTime monthStart,
        CancellationToken cancellationToken)
    {
        var startDate = monthStart.Date;
        var endDate = startDate.AddMonths(1).AddTicks(-1);

        var recommendations = await GetSellDecisionsAsync(startDate, endDate, cancellationToken);

        return new RecommendationReport
        {
            ReportType = "Monthly",
            ReportDate = _clock.UtcNow.DateTime,
            PeriodStart = startDate,
            PeriodEnd = endDate,
            Recommendations = recommendations
        };
    }

    public async Task SendRecommendationReportAsync(
        RecommendationReport report,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrEmpty(_telegramBotToken) || string.IsNullOrEmpty(_telegramChatId))
        {
            _logger.LogWarning("Telegram credentials not configured. Report not sent");
            return;
        }

        var message = FormatReportAsMarkdown(report);
        await SendTelegramMessageAsync(message, cancellationToken);
    }

    public async Task<bool> HasReportBeenSentAsync(string idempotencyKey, CancellationToken cancellationToken)
    {
        const string sql = @"
            SELECT COUNT(1)
            FROM recommendation_sent_log
            WHERE idempotency_key = $1";

        await using var connection = await _connectionFactory.OpenAsync(cancellationToken);
        await using var command = connection.CreateCommand();
        command.CommandText = sql;
        command.Parameters.Add(command.CreateParameter());
        command.Parameters[0].Value = idempotencyKey;

        var result = await command.ExecuteScalarAsync(cancellationToken);
        return (long?)result > 0;
    }

    public async Task MarkReportSentAsync(string idempotencyKey, CancellationToken cancellationToken)
    {
        const string createTableSql = @"
            CREATE TABLE IF NOT EXISTS recommendation_sent_log (
                id BIGSERIAL PRIMARY KEY,
                idempotency_key TEXT NOT NULL UNIQUE,
                sent_at TIMESTAMP NOT NULL DEFAULT NOW()
            )";

        await using var connection = await _connectionFactory.OpenAsync(cancellationToken);
        await using var createCmd = connection.CreateCommand();
        createCmd.CommandText = createTableSql;
        await createCmd.ExecuteNonQueryAsync(cancellationToken);

        const string insertSql = @"
            INSERT INTO recommendation_sent_log (idempotency_key, sent_at)
            VALUES ($1, NOW())
            ON CONFLICT (idempotency_key) DO NOTHING";

        await using var insertCmd = connection.CreateCommand();
        insertCmd.CommandText = insertSql;
        insertCmd.Parameters.Add(insertCmd.CreateParameter());
        insertCmd.Parameters[0].Value = idempotencyKey;
        await insertCmd.ExecuteNonQueryAsync(cancellationToken);
    }

    private async Task<IReadOnlyList<SellRecommendation>> GetSellDecisionsAsync(
        DateTime startDate,
        DateTime endDate,
        CancellationToken cancellationToken)
    {
        var sql = @"
            SELECT
                id as DecisionId,
                action as Action,
                sell_ratio_of_lot as SellRatio,
                policy_id as PolicyId,
                reason_code as ReasonCode,
                created_at as CreatedAt
            FROM signal_engine.sell_decisions
            WHERE created_at >= $1
              AND created_at < $2
              AND action = 'SELL'
            ORDER BY created_at DESC";

        await using var connection = await _connectionFactory.OpenAsync(cancellationToken);
        await using var command = connection.CreateCommand();
        command.CommandText = sql;
        command.Parameters.Add(command.CreateParameter());
        command.Parameters[0].Value = startDate;
        command.Parameters.Add(command.CreateParameter());
        command.Parameters[1].Value = endDate;

        var recommendations = new List<SellRecommendation>();

        await using (var reader = await command.ExecuteReaderAsync(cancellationToken))
        {
            while (await reader.ReadAsync(cancellationToken))
            {
                recommendations.Add(new SellRecommendation
                {
                    DecisionId = reader.GetGuid(0),
                    Action = reader.GetString(1),
                    SellRatio = reader.GetDecimal(2),
                    PolicyId = reader.GetString(3),
                    ReasonCode = reader.GetString(4),
                    CreatedAt = reader.GetDateTime(5)
                });
            }
        }

        return recommendations.AsReadOnly();
    }

    private static string FormatReportAsMarkdown(RecommendationReport report)
    {
        var message = $@"📊 *{report.ReportType} Recommendation Report* - K-ArtSell Aegis

Period: {report.PeriodStart:yyyy-MM-dd} → {report.PeriodEnd:yyyy-MM-dd}
Total Recommendations: {report.Recommendations.Count}

";

        if (report.Recommendations.Count == 0)
        {
            message += "_No sell recommendations for this period._";
            return message;
        }

        // Group by policy
        var byPolicy = report.Recommendations
            .GroupBy(r => r.PolicyId)
            .OrderByDescending(g => g.Count());

        foreach (var group in byPolicy.Take(5))
        {
            message += $@"
*{group.Key}* ({group.Count()})";
            foreach (var rec in group.Take(3))
            {
                message += $@"
  • {rec.ReasonCode} (Ratio: {rec.SellRatio:P2}) @ {rec.CreatedAt:HH:mm}";
            }
            if (group.Count() > 3)
            {
                message += $@"
  • +{group.Count() - 3} more";
            }
        }

        message += @"

_Generated by K-ArtSell Aegis Algorithm_
_Evidence Preserved · Audit Logged_";

        return message;
    }

    private async Task SendTelegramMessageAsync(string message, CancellationToken cancellationToken)
    {
        var url = $"https://api.telegram.org/bot{_telegramBotToken}/sendMessage";

        var content = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            { "chat_id", _telegramChatId },
            { "text", message },
            { "parse_mode", "Markdown" }
        });

        try
        {
            var response = await _httpClient.PostAsync(url, content, cancellationToken);
            response.EnsureSuccessStatusCode();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to send recommendation report to Telegram");
            throw;
        }
    }
}

public sealed record RecommendationReport
{
    public required string ReportType { get; init; }
    public required DateTime ReportDate { get; init; }
    public required DateTime PeriodStart { get; init; }
    public required DateTime PeriodEnd { get; init; }
    public required IReadOnlyList<SellRecommendation> Recommendations { get; init; }
}

public sealed record SellRecommendation
{
    public required Guid DecisionId { get; init; }
    public required string Action { get; init; }
    public required decimal SellRatio { get; init; }
    public required string PolicyId { get; init; }
    public required string ReasonCode { get; init; }
    public required DateTime CreatedAt { get; init; }
}
