using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using StellaOps.Configuration;
using Xunit;

namespace StellaOps.Configuration.Tests;

public class StellaOpsAuthorityOptionsTests
{
    [Fact]
    public void Validate_Throws_When_IssuerMissing()
    {
        var options = new StellaOpsAuthorityOptions();

        var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());

        Assert.Contains("issuer", exception.Message, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Validate_Normalises_Collections()
    {
        var options = new StellaOpsAuthorityOptions
        {
            Issuer = new Uri("https://authority.stella-ops.test"),
            SchemaVersion = 1
        };
        options.Storage.ConnectionString = "mongodb://localhost:27017/authority";
        options.Signing.ActiveKeyId = "test-key";
        options.Signing.KeyPath = "/tmp/test-key.pem";

        options.PluginDirectories.Add("  ./plugins ");
        options.PluginDirectories.Add("./plugins");
        options.PluginDirectories.Add("./other");

        options.BypassNetworks.Add(" 10.0.0.0/24 ");
        options.BypassNetworks.Add("10.0.0.0/24");
        options.BypassNetworks.Add("192.168.0.0/16");

        options.Validate();

        Assert.Equal(new[] { "./plugins", "./other" }, options.PluginDirectories);
        Assert.Equal(new[] { "10.0.0.0/24", "192.168.0.0/16" }, options.BypassNetworks);
    }

    [Fact]
    public void Validate_Normalises_PluginDescriptors()
    {
        var options = new StellaOpsAuthorityOptions
        {
            Issuer = new Uri("https://authority.stella-ops.test"),
            SchemaVersion = 1
        };
        options.Storage.ConnectionString = "mongodb://localhost:27017/authority";
        options.Signing.ActiveKeyId = "test-key";
        options.Signing.KeyPath = "/tmp/test-key.pem";

        var descriptor = new AuthorityPluginDescriptorOptions
        {
            AssemblyName = "StellaOps.Authority.Plugin.Standard",
            ConfigFile = "  standard.yaml  ",
            Enabled = true
        };

        descriptor.Capabilities.Add("password");
        descriptor.Capabilities.Add("PASSWORD");
        options.Plugins.Descriptors["standard"] = descriptor;

        options.Validate();

        var normalized = options.Plugins.Descriptors["standard"];
        Assert.Equal("standard.yaml", normalized.ConfigFile);
        Assert.Single(normalized.Capabilities);
        Assert.Equal("password", normalized.Capabilities[0]);
    }

    [Fact]
    public void Validate_Throws_When_StorageConnectionStringMissing()
    {
        var options = new StellaOpsAuthorityOptions
        {
            Issuer = new Uri("https://authority.stella-ops.test"),
            SchemaVersion = 1
        };
        options.Signing.ActiveKeyId = "test-key";
        options.Signing.KeyPath = "/tmp/test-key.pem";

        var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());

        Assert.Contains("Mongo connection string", exception.Message, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Build_Binds_From_Configuration()
    {
        var context = StellaOpsAuthorityConfiguration.Build(options =>
        {
            options.ConfigureBuilder = builder =>
            {
                builder.AddInMemoryCollection(new Dictionary<string, string?>
                {
                    ["Authority:SchemaVersion"] = "2",
                    ["Authority:Issuer"] = "https://authority.internal",
                    ["Authority:AccessTokenLifetime"] = "00:30:00",
                    ["Authority:RefreshTokenLifetime"] = "30.00:00:00",
                    ["Authority:Storage:ConnectionString"] = "mongodb://example/stellaops",
                    ["Authority:Storage:DatabaseName"] = "overrideDb",
                    ["Authority:Storage:CommandTimeout"] = "00:01:30",
                    ["Authority:PluginDirectories:0"] = "/var/lib/stellaops/plugins",
                    ["Authority:BypassNetworks:0"] = "127.0.0.1/32",
                    ["Authority:Security:RateLimiting:Token:PermitLimit"] = "25",
                    ["Authority:Security:RateLimiting:Token:Window"] = "00:00:30",
                    ["Authority:Security:RateLimiting:Authorize:Enabled"] = "true",
                    ["Authority:Security:RateLimiting:Internal:Enabled"] = "true",
                    ["Authority:Security:RateLimiting:Internal:PermitLimit"] = "3",
                    ["Authority:Signing:Enabled"] = "true",
                    ["Authority:Signing:ActiveKeyId"] = "authority-signing-dev",
                    ["Authority:Signing:KeyPath"] = "../certificates/authority-signing-dev.pem",
                    ["Authority:Signing:KeySource"] = "file"
                });
            };
        });

        var options = context.Options;

        Assert.Equal(2, options.SchemaVersion);
        Assert.Equal(new Uri("https://authority.internal"), options.Issuer);
        Assert.Equal(TimeSpan.FromMinutes(30), options.AccessTokenLifetime);
        Assert.Equal(TimeSpan.FromDays(30), options.RefreshTokenLifetime);
        Assert.Equal(new[] { "/var/lib/stellaops/plugins" }, options.PluginDirectories);
        Assert.Equal(new[] { "127.0.0.1/32" }, options.BypassNetworks);
        Assert.Equal("mongodb://example/stellaops", options.Storage.ConnectionString);
        Assert.Equal("overrideDb", options.Storage.DatabaseName);
        Assert.Equal(TimeSpan.FromMinutes(1.5), options.Storage.CommandTimeout);
        Assert.Equal(25, options.Security.RateLimiting.Token.PermitLimit);
        Assert.Equal(TimeSpan.FromSeconds(30), options.Security.RateLimiting.Token.Window);
        Assert.True(options.Security.RateLimiting.Authorize.Enabled);
        Assert.True(options.Security.RateLimiting.Internal.Enabled);
        Assert.Equal(3, options.Security.RateLimiting.Internal.PermitLimit);
        Assert.True(options.Signing.Enabled);
        Assert.Equal("authority-signing-dev", options.Signing.ActiveKeyId);
        Assert.Equal("../certificates/authority-signing-dev.pem", options.Signing.KeyPath);
        Assert.Equal("file", options.Signing.KeySource);
    }

    [Fact]
    public void Validate_Throws_When_RateLimitingInvalid()
    {
        var options = new StellaOpsAuthorityOptions
        {
            Issuer = new Uri("https://authority.stella-ops.test"),
            SchemaVersion = 1
        };
        options.Storage.ConnectionString = "mongodb://localhost:27017/authority";
        options.Security.RateLimiting.Token.PermitLimit = 0;
        options.Signing.ActiveKeyId = "test-key";
        options.Signing.KeyPath = "/tmp/test-key.pem";

        var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());

        Assert.Contains("permitLimit", exception.Message, StringComparison.OrdinalIgnoreCase);
    }
}
