using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ScadaLink.Host;

namespace ScadaLink.Host.Tests;

public class HostStartupTests : IDisposable
{
    private readonly List<IDisposable> _disposables = new();

    public void Dispose()
    {
        foreach (var d in _disposables)
        {
            try { d.Dispose(); } catch { /* best effort */ }
        }
    }

    [Fact]
    public void CentralRole_StartsWithoutError()
    {
        // WebApplicationFactory replays Program.Main, which reads config from files.
        // Set the environment to Central so appsettings.Central.json is loaded,
        // and set DOTNET_ENVIRONMENT before the factory creates the host.
        var previousEnv = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
        // Host-003: connection strings are externalised; supply them via env vars.
        using var dbEnv = new CentralDbTestEnvironment();
        try
        {
            Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", "Central");

            var factory = new WebApplicationFactory<Program>()
                .WithWebHostBuilder(builder =>
                {
                    builder.ConfigureAppConfiguration((_, config) =>
                    {
                        config.AddInMemoryCollection(new Dictionary<string, string?>
                        {
                            ["ScadaLink:Node:NodeHostname"] = "localhost",
                            ["ScadaLink:Node:RemotingPort"] = "0",
                            ["ScadaLink:Cluster:SeedNodes:0"] = "akka.tcp://scadalink@localhost:2551",
                            ["ScadaLink:Cluster:SeedNodes:1"] = "akka.tcp://scadalink@localhost:2552",
                            ["ScadaLink:Database:SkipMigrations"] = "true",
                        });
                    });
                    builder.UseSetting("ScadaLink:Node:Role", "Central");
                    builder.UseSetting("ScadaLink:Database:SkipMigrations", "true");
                });
            _disposables.Add(factory);

            // Creating the server exercises the full DI container build and startup pipeline
            var client = factory.CreateClient();
            _disposables.Add(client);

            // If we get here without exception, the central host started successfully
            Assert.NotNull(client);
        }
        finally
        {
            Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", previousEnv);
        }
    }

    [Fact]
    public void SiteRole_StartsWithoutError()
    {
        var builder = WebApplication.CreateBuilder();
        builder.Configuration.Sources.Clear();
        builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
        {
            ["ScadaLink:Node:Role"] = "Site",
            ["ScadaLink:Node:NodeHostname"] = "test-site",
            ["ScadaLink:Node:SiteId"] = "TestSite",
            ["ScadaLink:Node:RemotingPort"] = "0",
            ["ScadaLink:Node:GrpcPort"] = "0",
        });

        builder.Services.AddGrpc();
        builder.Services.AddSingleton<ScadaLink.Communication.Grpc.SiteStreamGrpcServer>();
        SiteServiceRegistration.Configure(builder.Services, builder.Configuration);

        // Remove AkkaHostedService from running
        AkkaHostedServiceRemover.RemoveAkkaHostedServiceOnly(builder.Services);

        var app = builder.Build();
        _disposables.Add(app);

        // Build succeeds = DI container is valid and all services resolve
        Assert.NotNull(app);
        Assert.NotNull(app.Services);
    }

    [Fact]
    public void SiteRole_ConfiguresKestrelForGrpc()
    {
        var builder = WebApplication.CreateBuilder();
        builder.Configuration.Sources.Clear();
        builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
        {
            ["ScadaLink:Node:Role"] = "Site",
            ["ScadaLink:Node:NodeHostname"] = "test-site",
            ["ScadaLink:Node:SiteId"] = "TestSite",
            ["ScadaLink:Node:RemotingPort"] = "0",
            ["ScadaLink:Node:GrpcPort"] = "0",
        });

        builder.WebHost.ConfigureKestrel(options =>
        {
            options.ListenAnyIP(0, listenOptions =>
            {
                listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http2;
            });
        });

        builder.Services.AddGrpc();
        builder.Services.AddSingleton<ScadaLink.Communication.Grpc.SiteStreamGrpcServer>();
        SiteServiceRegistration.Configure(builder.Services, builder.Configuration);

        // Remove AkkaHostedService from running
        AkkaHostedServiceRemover.RemoveAkkaHostedServiceOnly(builder.Services);

        var app = builder.Build();

        // Verify Kestrel IS configured (site now hosts gRPC via WebApplicationBuilder)
        var serverType = Type.GetType(
            "Microsoft.AspNetCore.Hosting.Server.IServer, Microsoft.AspNetCore.Hosting.Server.Abstractions");

        if (serverType != null)
        {
            var server = app.Services.GetService(serverType);
            Assert.NotNull(server);
        }

        (app as IDisposable)?.Dispose();
    }

    [Fact]
    public void HostProject_DoesNotUseConditionalCompilation()
    {
        var hostProjectDir = FindHostProjectDirectory();
        Assert.NotNull(hostProjectDir);

        var sourceFiles = Directory.GetFiles(hostProjectDir, "*.cs", SearchOption.TopDirectoryOnly);
        Assert.NotEmpty(sourceFiles);

        foreach (var file in sourceFiles)
        {
            var content = File.ReadAllText(file);

            Assert.DoesNotContain("#if", content);
            Assert.DoesNotContain("#ifdef", content);
            Assert.DoesNotContain("#ifndef", content);
            Assert.DoesNotContain("#elif", content);
            Assert.DoesNotContain("#else", content);
            Assert.DoesNotContain("#endif", content);
        }
    }

    private static string? FindHostProjectDirectory()
    {
        // Walk up from the test assembly location to find the src directory
        var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
        var dir = new DirectoryInfo(assemblyDir);

        while (dir != null)
        {
            var hostPath = Path.Combine(dir.FullName, "src", "ScadaLink.Host");
            if (Directory.Exists(hostPath))
                return hostPath;
            dir = dir.Parent;
        }

        return null;
    }
}
