using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Cli.Configuration;
using StellaOps.Cli.Services;
using Xunit;

namespace StellaOps.Cli.Tests.Services;

public sealed class AuthorityDiagnosticsReporterTests : IDisposable
{
    private readonly string _originalDirectory = Directory.GetCurrentDirectory();
    private readonly string _tempDirectory = Path.Combine(Path.GetTempPath(), $"stellaops-cli-tests-{Guid.NewGuid():N}");

    public AuthorityDiagnosticsReporterTests()
    {
        Directory.CreateDirectory(_tempDirectory);
        Directory.SetCurrentDirectory(_tempDirectory);
    }

    [Fact]
    public void Emit_LogsWarning_WhenPasswordPolicyWeakened()
    {
        WriteAuthorityConfiguration(minimumLength: 8);

        var (_, configuration) = CliBootstrapper.Build(Array.Empty<string>());
        var logger = new ListLogger();

        AuthorityDiagnosticsReporter.Emit(configuration, logger);

        var warning = Assert.Single(logger.Entries, entry => entry.Level == LogLevel.Warning);
        Assert.Contains("minimum length 8 < 12", warning.Message, StringComparison.OrdinalIgnoreCase);
        Assert.Contains("standard.yaml", warning.Message, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Emit_EmitsNoWarnings_WhenPasswordPolicyMeetsBaseline()
    {
        WriteAuthorityConfiguration(minimumLength: 12);

        var (_, configuration) = CliBootstrapper.Build(Array.Empty<string>());
        var logger = new ListLogger();

        AuthorityDiagnosticsReporter.Emit(configuration, logger);

        Assert.DoesNotContain(logger.Entries, entry => entry.Level >= LogLevel.Warning);
    }

    public void Dispose()
    {
        Directory.SetCurrentDirectory(_originalDirectory);
        try
        {
            if (Directory.Exists(_tempDirectory))
            {
                Directory.Delete(_tempDirectory, recursive: true);
            }
        }
        catch
        {
            // Ignored.
        }
    }

    private static void WriteAuthorityConfiguration(int minimumLength)
    {
        var payload = new
        {
            Authority = new
            {
                Plugins = new
                {
                    ConfigurationDirectory = "plugins",
                    Descriptors = new
                    {
                        standard = new
                        {
                            AssemblyName = "StellaOps.Authority.Plugin.Standard",
                            Enabled = true,
                            ConfigFile = "standard.yaml"
                        }
                    }
                }
            }
        };

        var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
        File.WriteAllText("appsettings.json", json);

        var pluginDirectory = Path.Combine(Directory.GetCurrentDirectory(), "plugins");
        Directory.CreateDirectory(pluginDirectory);

        var pluginConfig = $"""
bootstrapUser:
  username: "admin"
  password: "changeme"

passwordPolicy:
  minimumLength: {minimumLength}
  requireUppercase: true
  requireLowercase: true
  requireDigit: true
  requireSymbol: true
""";

        File.WriteAllText(Path.Combine(pluginDirectory, "standard.yaml"), pluginConfig);
    }

    private sealed class ListLogger : ILogger
    {
        public readonly record struct LogEntry(LogLevel Level, string Message);

        public List<LogEntry> Entries { get; } = new();

        public IDisposable? BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;

        public bool IsEnabled(LogLevel logLevel) => true;

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
        {
            var message = formatter(state, exception);
            Entries.Add(new LogEntry(logLevel, message));
        }
    }

    private sealed class NullScope : IDisposable
    {
        public static NullScope Instance { get; } = new();

        public void Dispose()
        {
        }
    }
}
