using System.Text;
using Dapper;
using GmRelay.Shared.Domain;
using Npgsql;

namespace GmRelay.Web.Services;

public sealed class CalendarSubscriptionService(NpgsqlDataSource dataSource)
{
    private const string IcsProdId = "-//GM-Relay//TTRPG Schedule//EN";

    public string GenerateToken() => Guid.NewGuid().ToString("N");

    public async Task<string> CreateSubscriptionAsync(
        long userTelegramId,
        Guid? groupId,
        CalendarSubscriptionFilter filter,
        CancellationToken ct = default)
    {
        var token = GenerateToken();
        await using var connection = await dataSource.OpenConnectionAsync(ct);
        await connection.ExecuteAsync(
            @"INSERT INTO calendar_subscriptions (id, token, user_telegram_id, group_id, filter_type, created_at, expires_at)
              VALUES (gen_random_uuid(), @token, @userTelegramId, @groupId, @filterType, now(), NULL)",
            new { token, userTelegramId, groupId, filterType = (int)filter });
        return token;
    }

    public async Task<string> GetIcsAsync(string token, CancellationToken ct = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);

        var subscription = await connection.QueryFirstOrDefaultAsync<SubscriptionRecord>(
            @"SELECT id, user_telegram_id as UserTelegramId, group_id as GroupId, filter_type as FilterType
              FROM calendar_subscriptions
              WHERE token = @token
                AND (expires_at IS NULL OR expires_at > now())",
            new { token });

        if (subscription is null)
            throw new SubscriptionNotFoundException();

        var sessions = await connection.QueryAsync<CalendarSessionDto>(
            subscription.FilterType == (int)CalendarSubscriptionFilter.SpecificGroup && subscription.GroupId.HasValue
                ? @"SELECT s.id as Id, s.title as Title, s.scheduled_at as ScheduledAt
                    FROM sessions s
                    WHERE s.group_id = @GroupId
                      AND s.status = @Planned
                      AND s.scheduled_at > NOW()
                    ORDER BY s.scheduled_at ASC"
                : @"SELECT s.id as Id, s.title as Title, s.scheduled_at as ScheduledAt
                    FROM sessions s
                    WHERE s.status = @Planned
                      AND s.scheduled_at > NOW()
                    ORDER BY s.scheduled_at ASC",
            new { subscription.GroupId, Planned = SessionStatus.Planned });

        var sb = new StringBuilder();
        sb.AppendLine("BEGIN:VCALENDAR");
        sb.AppendLine("VERSION:2.0");
        sb.AppendLine($"PRODID:{IcsProdId}");
        sb.AppendLine("CALSCALE:GREGORIAN");
        sb.AppendLine("METHOD:PUBLISH");

        foreach (var s in sessions)
        {
            var dtStart = FormatIcsDate(s.ScheduledAt);
            var dtEnd = FormatIcsDate(s.ScheduledAt.AddHours(4));
            sb.AppendLine("BEGIN:VEVENT");
            sb.AppendLine($"UID:{s.Id}@gmrelay");
            sb.AppendLine($"DTSTAMP:{FormatIcsDate(DateTime.UtcNow)}");
            sb.AppendLine($"DTSTART:{dtStart}");
            sb.AppendLine($"DTEND:{dtEnd}");
            sb.AppendLine($"SUMMARY:{EscapeIcsText(s.Title)}");
            sb.AppendLine("END:VEVENT");
        }

        sb.AppendLine("END:VCALENDAR");
        return sb.ToString();
    }

    private static string FormatIcsDate(DateTime dt) => dt.ToUniversalTime().ToString("yyyyMMddTHHmmssZ");

    private static string EscapeIcsText(string text) => text
        .Replace("\\", "\\\\")
        .Replace(";", "\\;")
        .Replace(",", "\\,")
        .Replace("\n", "\\n")
        .Replace("\r", "");

    private sealed record SubscriptionRecord(Guid Id, long UserTelegramId, Guid? GroupId, int FilterType);
    private sealed record CalendarSessionDto(Guid Id, string Title, DateTime ScheduledAt);
}
