using System.Reflection;
using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

namespace MBN_STOCK_WEBVIEW.Core.Tests;

public sealed class LegacySceneMutationBuilderRegistryTests
{
    [Fact]
    public void Default_HasExactOneToOneParityWithThe35SceneCatalog()
    {
        var registry = LegacySceneMutationBuilderRegistry.Default;
        var expectedKeys = LegacySceneCatalog.Definitions
            .Select(definition => definition.BuilderKey)
            .ToArray();

        Assert.Equal(35, registry.Registrations.Count);
        Assert.Equal(LegacySceneCatalog.ExpectedDefinitionCount, registry.Registrations.Count);
        Assert.Equal(expectedKeys, registry.Registrations.Select(item => item.BuilderKey));
        Assert.Equal(
            expectedKeys.OrderBy(key => key, StringComparer.Ordinal),
            registry.Registrations
                .Select(item => item.BuilderKey)
                .Distinct(StringComparer.Ordinal)
                .OrderBy(key => key, StringComparer.Ordinal));

        Assert.All(registry.Registrations, registration =>
        {
            Assert.True(registry.TryGetRegistration(registration.BuilderKey, out var found));
            Assert.Same(registration, found);
            Assert.True(typeof(ILegacySceneData).IsAssignableFrom(registration.DataType));
            Assert.NotEqual(typeof(ILegacySceneData), registration.DataType);
            Assert.False(registration.DataType.IsInterface);
            Assert.False(registration.DataType.ContainsGenericParameters);
            Assert.False(typeof(ILegacySceneDtoMarker).IsAssignableFrom(registration.DataType));
        });
    }

    [Fact]
    public void CoreAssembly_ContainsExactly35PublicParameterlessTypedBuilders()
    {
        var builderContract = typeof(ILegacySceneMutationBuilder<>);
        var builders = typeof(LegacySceneMutationBuilderRegistry)
            .Assembly
            .DefinedTypes
            .Where(type =>
                type.IsClass &&
                !type.IsAbstract &&
                type.ImplementedInterfaces.Any(contract =>
                    contract.IsGenericType &&
                    contract.GetGenericTypeDefinition() == builderContract))
            .ToArray();

        Assert.Equal(35, builders.Length);
        Assert.All(builders, builder =>
        {
            Assert.True(builder.IsPublic);
            Assert.NotNull(builder.GetConstructor(Type.EmptyTypes));
            Assert.Single(builder.ImplementedInterfaces, contract =>
                contract.IsGenericType &&
                contract.GetGenericTypeDefinition() == builderContract);
        });
    }

    [Fact]
    public void Build_DispatchesToTheRegisteredTypedBuilderAndReturnsAReadOnlySnapshot()
    {
        var registry = LegacySceneMutationBuilderRegistry.Default;
        var data = new S5085SceneData(
        [
            new ProgramTradingMetricData(ProgramTradingMetric.NetBuy, 120.5)
        ]);

        var expected = new S5085SceneMutationBuilder().Build(data);
        var actual = registry.Build("s5085", data);

        Assert.Equal(expected, actual);
        var mutableView = Assert.IsAssignableFrom<IList<PlayoutMutation>>(actual);
        Assert.Throws<NotSupportedException>(() => mutableView.Add(
            new PlayoutSetValue("untrusted", "untrusted")));
    }

    [Fact]
    public void Build_RejectsUnknownKeysAndDataRegisteredForAnotherScene()
    {
        var registry = LegacySceneMutationBuilderRegistry.Default;
        var wrongData = new S6067SceneData([]);

        Assert.Throws<ArgumentException>(() => registry.Build("s5085", wrongData));
        Assert.Throws<ArgumentException>(() => registry.Build("s9999", wrongData));
        Assert.Throws<ArgumentException>(() => registry.Build("../s5085", wrongData));
        Assert.Throws<ArgumentNullException>(() => registry.Build("s5085", null!));

        Assert.False(registry.TryGetRegistration("s9999", out var unknown));
        Assert.Null(unknown);
        Assert.False(registry.TryGetRegistration("../s5085", out var invalid));
        Assert.Null(invalid);
    }

    [Fact]
    public void PublicRegistryInputSurface_HasNoRawMethodObjectMutationOrPathControls()
    {
        var prohibitedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            "methodName",
            "objectName",
            "assetPath",
            "sceneFile",
            "rawMutation"
        };
        var allowedInputTypes = new HashSet<Type>
        {
            typeof(string),
            typeof(ILegacySceneData),
            typeof(LegacySceneMutationBuilderRegistration).MakeByRefType()
        };

        var publicMethods = typeof(LegacySceneMutationBuilderRegistry)
            .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
        Assert.All(publicMethods.SelectMany(method => method.GetParameters()), parameter =>
        {
            Assert.DoesNotContain(parameter.Name!, prohibitedNames);
            Assert.Contains(parameter.ParameterType, allowedInputTypes);
            if (parameter.ParameterType == typeof(string))
            {
                Assert.Equal("builderKey", parameter.Name);
            }
        });

        Assert.Equal(
            new[] { "BuilderKey", "DataType" },
            typeof(LegacySceneMutationBuilderRegistration)
                .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
                .Select(property => property.Name)
                .OrderBy(name => name, StringComparer.Ordinal));
        Assert.Empty(typeof(LegacySceneMutationBuilderRegistration)
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public));

        Assert.All(LegacySceneMutationBuilderRegistry.Default.Registrations, registration =>
        {
            var rootDataProperties = registration.DataType
                .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
            Assert.DoesNotContain(rootDataProperties, property => prohibitedNames.Contains(property.Name));
        });
    }
}
