using System;
using System.IO;
using System.Reflection;
using GmRelay.DiscordBot.Features.Sessions;
using NetCord.Services.ApplicationCommands;

namespace GmRelay.Bot.Tests.Discord;

public sealed class DiscordStartupTests
{
    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");
    }

    [Fact]
    public void Program_ShouldValidateDiscordTokenBeforeRunning()
    {
        var program = ReadProgram();

        Assert.Contains("GetRequiredSection(\"Discord\")", program);
        Assert.Contains("DiscordOptions", program);
        Assert.Contains(".Validate()", program);
    }

    [Fact]
    public void Program_ShouldRegisterServiceDefaultsAndPostgresDataSource()
    {
        var program = ReadProgram();

        Assert.Contains("builder.AddServiceDefaults()", program);
        Assert.Contains("ConnectionStrings:gmrelaydb is required", program);
        Assert.Contains("NpgsqlDataSource", program);
        Assert.Contains("SecretRedactor.RedactConnectionString", program);
    }

    [Fact]
    public void Program_ShouldRegisterNetCordGatewayApplicationCommandsAndComponents()
    {
        var program = ReadProgram();

        Assert.Contains(".AddDiscordGateway", program);
        Assert.Contains(".AddApplicationCommands", program);
        Assert.Contains(".AddComponentInteractions", program);
        Assert.Contains(".AddGatewayHandlers", program);
        Assert.Contains("AddSlashCommand", program);
        Assert.Contains("AddModules(typeof(Program).Assembly)", program);
    }

    [Theory]
    [InlineData(typeof(DiscordNewSessionCommand), "newsession")]
    [InlineData(typeof(DiscordListSessionsCommand), "listsessions")]
    [InlineData(typeof(DiscordRescheduleCommand), "reschedule")]
    public void DiscordSessionSlashCommands_ShouldBeDeclaredOnModuleMethods(Type moduleType, string commandName)
    {
        var executeMethod = moduleType.GetMethod("ExecuteAsync", BindingFlags.Instance | BindingFlags.Public);

        Assert.NotNull(executeMethod);

        var methodAttribute = Assert.Single(executeMethod.GetCustomAttributes<SlashCommandAttribute>(inherit: false));
        var nameProperty = typeof(SlashCommandAttribute).GetProperty("Name")
            ?? throw new InvalidOperationException("SlashCommandAttribute should expose command name.");

        Assert.Equal(commandName, nameProperty.GetValue(methodAttribute));
        Assert.Empty(moduleType.GetCustomAttributes<SlashCommandAttribute>(inherit: false));
    }

    [Fact]
    public void DiscordSessionSlashCommands_ShouldBeDiscoverableByNetCordService()
    {
        var service = new ApplicationCommandService<SlashCommandContext>();

        service.AddModules(typeof(DiscordNewSessionCommand).Assembly);

        var commandNames = service.GetCommands()
            .Select(command => command.Name)
            .ToArray();

        Assert.Contains("newsession", commandNames);
        Assert.Contains("listsessions", commandNames);
        Assert.Contains("reschedule", commandNames);
    }

    [Fact]
    public void LifecycleLogger_ShouldLogGatewayLifecycleEventsWithoutTokenValues()
    {
        var repoRoot = GetRepoRoot();
        var loggerPath = Path.Combine(
            repoRoot,
            "src",
            "GmRelay.DiscordBot",
            "Infrastructure",
            "Logging",
            "DiscordGatewayLifecycleLogger.cs");

        Assert.True(File.Exists(loggerPath), "Discord gateway lifecycle logger should exist.");

        var logger = File.ReadAllText(loggerPath);
        Assert.Contains("IReadyGatewayHandler", logger);
        Assert.Contains("IDisconnectGatewayHandler", logger);
        Assert.Contains("IResumeGatewayHandler", logger);
        Assert.Contains("LogInformation", logger);
        Assert.DoesNotContain("Token", logger);
    }

    [Fact]
    public void Program_ShouldRegisterDiscordSessionHandlers()
    {
        var program = ReadProgram();
        Assert.Contains("DiscordListSessionsHandler", program);
        Assert.Contains("DiscordNewSessionHandler", program);
        Assert.Contains("JoinSessionHandler", program);
        Assert.Contains("LeaveSessionHandler", program);
        Assert.Contains("DiscordPermissionChecker", program);
        Assert.Contains("DiscordPlatformMessenger", program);
        Assert.Contains("IPlatformMessenger", program);
        Assert.Contains("PlatformSchedulerOptions(PlatformKind.Discord)", program);
        Assert.Contains("AddHostedService<SessionSchedulerService>", program);
        Assert.Contains("HandleRsvpHandler", program);
    }

    [Fact]
    public void Program_ShouldRegisterDiscordHealthCheckHostedService()
    {
        var program = ReadProgram();
        Assert.Contains("DiscordHealthCheckHostedService", program);
        Assert.Contains("AddHostedService<DiscordHealthCheckHostedService>", program);
    }

    private static string ReadProgram()
    {
        var repoRoot = GetRepoRoot();
        return File.ReadAllText(Path.Combine(repoRoot, "src", "GmRelay.DiscordBot", "Program.cs"));
    }
}
