using GmRelay.DiscordBot.Features.Sessions;

namespace GmRelay.Bot.Tests.Discord;

public sealed class DiscordNewSessionHandlerTests
{
    private static string GetRepoRoot()
    {
        var dir = AppContext.BaseDirectory;
        while (!string.IsNullOrEmpty(dir) && !File.Exists(Path.Combine(dir, "Directory.Build.props")))
        {
            dir = Directory.GetParent(dir)?.FullName;
        }

        return dir ?? throw new InvalidOperationException("Could not find repo root");
    }

    // --- Runtime tests for ParseTimeInput (static, no DB) ---

    [Fact]
    public void ParseTimeInput_ShouldTreatInputAsMoscowTime()
    {
        var future = DateTimeOffset.UtcNow.AddDays(7);
        var result = DiscordNewSessionHandler.ParseTimeInput(
            future.ToString("yyyy-MM-dd '15:00'", System.Globalization.CultureInfo.InvariantCulture));

        Assert.True(result.IsSuccess);
        // 15:00 MSK = 12:00 UTC
        Assert.Equal(12, result.Value.Hour);
        Assert.Equal(0, result.Value.Minute);
        Assert.Equal(TimeSpan.Zero, result.Value.Offset);
    }

    [Fact]
    public void ParseTimeInput_ShouldParseDiscordDateFormat()
    {
        var expected = FutureDateAt1930();
        var result = DiscordNewSessionHandler.ParseTimeInput(
            expected.ToString("yyyy-MM-dd HH:mm", System.Globalization.CultureInfo.InvariantCulture));

        Assert.True(result.IsSuccess);
        Assert.Equal(expected.Year, result.Value.Year);
        Assert.Equal(expected.Month, result.Value.Month);
        Assert.Equal(expected.Day, result.Value.Day);
        // Input is treated as Moscow time; 19:30 MSK = 16:30 UTC
        Assert.Equal(16, result.Value.Hour);
        Assert.Equal(30, result.Value.Minute);
    }

    [Fact]
    public void ParseTimeInput_ShouldRejectPastDate()
    {
        var result = DiscordNewSessionHandler.ParseTimeInput("2020-01-01 00:00");
        Assert.False(result.IsSuccess);
    }

    [Fact]
    public void ParseTimeInput_ShouldParseRussianDateFormat()
    {
        var expected = FutureDateAt1930();
        var result = DiscordNewSessionHandler.ParseTimeInput(
            expected.ToString("dd.MM.yyyy HH:mm", System.Globalization.CultureInfo.InvariantCulture));

        Assert.True(result.IsSuccess);
        Assert.Equal(expected.Year, result.Value.Year);
        Assert.Equal(expected.Month, result.Value.Month);
        Assert.Equal(expected.Day, result.Value.Day);
    }

    [Fact]
    public void ParseTimeInput_ShouldRejectInvalidFormat()
    {
        var result = DiscordNewSessionHandler.ParseTimeInput("not-a-date");
        Assert.False(result.IsSuccess);
        Assert.NotNull(result.Error);
    }

    // --- Source-level structural tests ---

    [Fact]
    public void Handler_ShouldExist()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        Assert.True(File.Exists(handlerPath), "DiscordNewSessionHandler should exist.");
    }

    [Fact]
    public void Handler_ShouldUseDapperForDatabaseAccess()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        var source = File.ReadAllText(handlerPath);

        Assert.Contains("QueryAsync", source, StringComparison.Ordinal);
        Assert.Contains("ExecuteAsync", source, StringComparison.Ordinal);
        Assert.Contains("ExecuteScalarAsync", source, StringComparison.Ordinal);
    }

    [Fact]
    public void Handler_ShouldUseNpgsqlDataSource()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        var source = File.ReadAllText(handlerPath);

        Assert.Contains("NpgsqlDataSource", source, StringComparison.Ordinal);
    }

    [Fact]
    public void Handler_ShouldCheckPermissionsViaPermissionChecker()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        var source = File.ReadAllText(handlerPath);

        Assert.Contains("CanManageSchedule", source, StringComparison.Ordinal);
        Assert.Contains("UnauthorizedAccessException", source, StringComparison.Ordinal);
    }

    [Fact]
    public void Handler_ShouldLoadCoGmPermissionsFromDiscordPlayers()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        var source = File.ReadAllText(handlerPath);

        Assert.Matches(
            @"QueryAsync<ulong>[\s\S]*JOIN players p ON p\.id = gm\.player_id[\s\S]*p\.platform = 'Discord'[\s\S]*g\.external_group_id = @GuildId",
            source);
    }

    [Fact]
    public void Handler_ShouldBePlatformNeutral()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        var source = File.ReadAllText(handlerPath);

        Assert.DoesNotContain("telegram_chat_id", source, StringComparison.Ordinal);
        Assert.DoesNotContain("telegram_id", source, StringComparison.Ordinal);
        Assert.Contains("platform = 'Discord'", source, StringComparison.Ordinal);
    }

    [Fact]
    public void Handler_ShouldUseTransactions()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        var source = File.ReadAllText(handlerPath);

        Assert.Contains("BeginTransactionAsync", source, StringComparison.Ordinal);
        Assert.Contains("CommitAsync", source, StringComparison.Ordinal);
        Assert.Contains("RollbackAsync", source, StringComparison.Ordinal);
    }

    [Fact]
    public void Handler_ShouldNotRollbackCommittedTransactionAfterPostCommitFailure()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        var source = File.ReadAllText(handlerPath);

        Assert.Contains("transactionCommitted = false", source, StringComparison.Ordinal);
        Assert.Contains("transactionCommitted = true", source, StringComparison.Ordinal);
        Assert.Contains("if (!transactionCommitted)", source, StringComparison.Ordinal);
    }

    [Fact]
    public void Handler_ShouldRespectCancellationToken()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        var source = File.ReadAllText(handlerPath);

        Assert.Contains("CancellationToken", source, StringComparison.Ordinal);
    }

    [Fact]
    public void Command_ShouldRenderEmbedOnSuccess()
    {
        var repoRoot = GetRepoRoot();
        var commandPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionCommand.cs");
        var source = File.ReadAllText(commandPath);

        Assert.Contains("DiscordSessionBatchRenderer.Render", source, StringComparison.Ordinal);
        Assert.Contains("message.Embeds = embeds", source, StringComparison.Ordinal);
    }

    [Fact]
    public void Handler_ShouldLeaveScheduleMessageCreationToInteractionResponse()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        var source = File.ReadAllText(handlerPath);

        Assert.DoesNotContain("SendScheduleAsync", source, StringComparison.Ordinal);
        Assert.DoesNotContain("PlatformScheduleMessage", source, StringComparison.Ordinal);
    }

    [Fact]
    public void Handler_ShouldStoreReadableDiscordGroupNameForWebCards()
    {
        var repoRoot = GetRepoRoot();
        var handlerPath = Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Features", "Sessions", "DiscordNewSessionHandler.cs");
        var source = File.ReadAllText(handlerPath);

        Assert.Contains("groupName", source, StringComparison.Ordinal);
        Assert.Contains("displayGroupName", source, StringComparison.Ordinal);
        Assert.Contains("VALUES (@GroupName, 'Discord'", source, StringComparison.Ordinal);
    }

    private static DateTimeOffset FutureDateAt1930()
    {
        var future = DateTimeOffset.UtcNow.AddDays(7);
        return new DateTimeOffset(
            future.Year,
            future.Month,
            future.Day,
            19,
            30,
            0,
            TimeSpan.Zero);
    }
}
