using Dapper;
using GmRelay.Shared.Domain;
using GmRelay.Bot.Features.Confirmation.SendConfirmation;
using GmRelay.Bot.Features.Reminders.SendJoinLink;
using GmRelay.Bot.Features.Reminders.SendOneHourReminder;
using Npgsql;

namespace GmRelay.Bot.Infrastructure.Scheduling;

/// <summary>
/// Stateless scheduler: wakes every 60 seconds, queries PostgreSQL for actionable sessions.
/// Two triggers:
///   T-24h: send confirmation request with inline keyboard
///   T-5min: send join link to all confirmed players
///
/// If the Raspberry Pi reboots, nothing is lost — all state is in the DB.
/// </summary>
public sealed class SessionSchedulerService(
    NpgsqlDataSource dataSource,
    SendConfirmationHandler confirmationHandler,
    SendOneHourReminderHandler oneHourReminderHandler,
    SendJoinLinkHandler joinLinkHandler,
    ILogger<SessionSchedulerService> logger) : BackgroundService
{
    private static readonly TimeSpan TickInterval = TimeSpan.FromMinutes(1);
    private static readonly TimeSpan ConfirmationLeadTime = TimeSpan.FromHours(24);
    private static readonly TimeSpan OneHourReminderLeadTime = TimeSpan.FromHours(1);
    private static readonly TimeSpan JoinLinkLeadTime = TimeSpan.FromMinutes(5);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation("Session scheduler started (interval: {Interval})", TickInterval);

        using var timer = new PeriodicTimer(TickInterval);

        // Run immediately on startup, then on each tick
        do
        {
            try
            {
                await ProcessConfirmationTriggers(stoppingToken);
                await ProcessOneHourReminderTriggers(stoppingToken);
                await ProcessJoinLinkTriggers(stoppingToken);
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Scheduler tick failed, will retry next tick");
            }
        }
        while (await timer.WaitForNextTickAsync(stoppingToken));

        logger.LogInformation("Session scheduler stopped");
    }

    /// <summary>
    /// T-1h trigger: process direct reminders according to the session notification mode.
    /// </summary>
    private async Task ProcessOneHourReminderTriggers(CancellationToken ct)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);

        var sessionIds = await connection.QueryAsync<Guid>(
            """
            SELECT id
            FROM sessions
            WHERE status IN (@Confirmed, @ConfirmationSent)
              AND scheduled_at - @LeadTime <= now()
              AND one_hour_reminder_processed_at IS NULL
            """,
            new
            {
                Confirmed = SessionStatus.Confirmed,
                ConfirmationSent = SessionStatus.ConfirmationSent,
                LeadTime = OneHourReminderLeadTime
            });

        foreach (var sessionId in sessionIds)
        {
            try
            {
                await oneHourReminderHandler.HandleAsync(sessionId, ct);
                logger.LogInformation("One-hour reminder processed for session {SessionId}", sessionId);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Failed to process one-hour reminder for session {SessionId}", sessionId);
            }
        }
    }

    /// <summary>
    /// T-24h trigger: find sessions that need confirmation requests sent.
    /// Condition: status='Planned' AND scheduled_at minus 24h is in the past.
    /// </summary>
    private async Task ProcessConfirmationTriggers(CancellationToken ct)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);

        var sessionIds = await connection.QueryAsync<Guid>(
            """
            SELECT id
            FROM sessions
            WHERE status = @Planned
              AND scheduled_at - @LeadTime <= now()
            """,
            new { Planned = SessionStatus.Planned, LeadTime = ConfirmationLeadTime });

        foreach (var sessionId in sessionIds)
        {
            try
            {
                await confirmationHandler.HandleAsync(sessionId, ct);
                logger.LogInformation("Confirmation sent for session {SessionId}", sessionId);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Failed to send confirmation for session {SessionId}", sessionId);
            }
        }
    }

    /// <summary>
    /// T-5min trigger: find confirmed sessions that need join links sent.
    /// Condition: status='Confirmed' AND scheduled_at minus 5min is in the past AND link not yet sent.
    /// </summary>
    private async Task ProcessJoinLinkTriggers(CancellationToken ct)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);

        var sessionIds = await connection.QueryAsync<Guid>(
            """
            SELECT id
            FROM sessions
            WHERE status = @Confirmed
              AND scheduled_at - @LeadTime <= now()
              AND link_message_id IS NULL
            """,
            new { Confirmed = SessionStatus.Confirmed, LeadTime = JoinLinkLeadTime });

        foreach (var sessionId in sessionIds)
        {
            try
            {
                await joinLinkHandler.HandleAsync(sessionId, ct);
                logger.LogInformation("Join link sent for session {SessionId}", sessionId);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Failed to send join link for session {SessionId}", sessionId);
            }
        }
    }
}
