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

namespace MBN_STOCK_WEBVIEW.Core.Tests;

public sealed class LegacySceneCatalogTests
{
    private const LegacySceneMutationCapability Value =
        LegacySceneMutationCapability.Value;
    private const LegacySceneMutationCapability Asset =
        LegacySceneMutationCapability.AssetValue;
    private const LegacySceneMutationCapability Visible =
        LegacySceneMutationCapability.Visibility;
    private const LegacySceneMutationCapability Color =
        LegacySceneMutationCapability.FaceColor;
    private const LegacySceneMutationCapability Position =
        LegacySceneMutationCapability.Position;
    private const LegacySceneMutationCapability PositionKey =
        LegacySceneMutationCapability.PositionKey;
    private const LegacySceneMutationCapability Scale =
        LegacySceneMutationCapability.Scale;
    private const LegacySceneMutationCapability Crop =
        LegacySceneMutationCapability.Crop;
    private const LegacySceneMutationCapability CircleAngle =
        LegacySceneMutationCapability.CircleAngleKey;
    private const LegacySceneMutationCapability Path =
        LegacySceneMutationCapability.PathPoints;
    private const LegacySceneMutationCapability PathShape =
        LegacySceneMutationCapability.PathShapePoints;
    private const LegacySceneMutationCapability BackgroundVideo =
        LegacySceneMutationCapability.BackgroundVideo;

    private static readonly ExpectedDefinition[] ExpectedDefinitions =
    [
        E<S5001SceneDtoMarker>("s5001", "5001", ["5001", "N5001"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Asset),
        E<S5006SceneDtoMarker>("s5006", "5006", ["5006"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Color | BackgroundVideo),
        E<S5011SceneDtoMarker>("s5011", "5011", ["5011"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Color),
        E<S5016SceneDtoMarker>("s5016", "5016", ["5016"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible),
        E<S50160SceneDtoMarker>("s50160", "50160", ["50160"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible),
        E<S5023SceneDtoMarker>("s5023", "5023", ["5023"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Color),
        E<S5024SceneDtoMarker>("s5024", "5024", ["5024"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Position | Scale),
        E<S5025SceneDtoMarker>("s5025", "5025", ["5025"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value),
        E<S5026SceneDtoMarker>("s5026", "5026", ["5026"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Color | Crop),
        E<S5029SceneDtoMarker>("s5029", "5029", ["5029"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Position | PathShape),
        E<S5032SceneDtoMarker>("s5032", "5032", ["8018", "8032", "5032"],
            LegacyScenePageSize.None,
            LegacySceneReachability.ConditionalMainFormDispatch,
            Value | Visible | Asset),
        E<S5037SceneDtoMarker>("s5037", "5037", ["5037"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible),
        E<S5074SceneDtoMarker>("s5074", "5074", ["5074"],
            LegacyScenePageSize.Five, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Asset),
        E<S5076SceneDtoMarker>("s5076", "5076", ["5076"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | CircleAngle),
        E<S5077SceneDtoMarker>("s5077", "5077", ["5077"],
            LegacyScenePageSize.Six, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Asset),
        E<S5078SceneDtoMarker>("s5078", "5078", ["5078"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Scale),
        E<S5079SceneDtoMarker>("s5079", "5079", ["5079"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Path),
        E<S5080SceneDtoMarker>("s5080", "5080", ["5080"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | PositionKey | Scale),
        E<S5081SceneDtoMarker>("s5081", "5081", ["5081"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | PositionKey | Scale),
        E<S5082SceneDtoMarker>("s5082", "5082", ["5082"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible),
        E<S5083SceneDtoMarker>("s5083", "5083", ["5083"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Position | Path),
        E<S5084SceneDtoMarker>("s5084", "5084", ["5084"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Position | Path),
        E<S5085SceneDtoMarker>("s5085", "5085", ["5085"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Color),
        E<S5086SceneDtoMarker>("s5086", "5086", ["5086"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Position | PathShape),
        E<S50860SceneDtoMarker>("s50860", "50860", ["50860"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Color | Position | PathShape),
        E<S5087SceneDtoMarker>("s5087", "5087", ["5087"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Position | PathShape),
        E<S5088SceneDtoMarker>("s5088", "5088", ["5088"],
            LegacyScenePageSize.Twelve, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Asset),
        E<S6001SceneDtoMarker>("s6001", "6001", ["6001"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Asset | Visible | Color),
        E<S6067SceneDtoMarker>("s6067", "6067", ["6067"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Color),
        E<S8001SceneDtoMarker>("s8001", "8001", ["8001", "8002"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Color),
        E<S8003SceneDtoMarker>("s8003", "8003", ["8003"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Color | Scale),
        E<S8010SceneDtoMarker>(
            "s8010",
            "8010",
            ["8035", "8061", "8040", "8046", "8051", "8056"],
            LegacyScenePageSize.None,
            LegacySceneReachability.MainFormDispatch,
            Value | Visible | Color | Position | Crop | Path),
        E<S8018SceneDtoMarker>("s8018", "8018", ["8018", "8032", "5032"],
            LegacyScenePageSize.None,
            LegacySceneReachability.ConditionalMainFormDispatch,
            Value | Visible | Asset),
        E<S8067SceneDtoMarker>("s8067", "8067", ["8067", "5068", "5070", "5072"],
            LegacyScenePageSize.None, LegacySceneReachability.MainFormDispatch,
            Value | Visible | Color),
        E<S8086SceneDtoMarker>("s8086", "8086", [],
            LegacyScenePageSize.None, LegacySceneReachability.NoMainFormDispatch,
            Value | Color)
    ];

    [Fact]
    public void Definitions_MatchTheReadOnly35BuilderBaselineExactly()
    {
        var actual = LegacySceneCatalog.Definitions;

        Assert.Equal(LegacySceneCatalog.ExpectedDefinitionCount, actual.Count);
        Assert.Equal(35, actual.Count);
        Assert.Equal(
            ExpectedDefinitions.Select(expected => expected.BuilderKey),
            actual.Select(definition => definition.BuilderKey));

        foreach (var expected in ExpectedDefinitions)
        {
            var definition = Assert.Single(
                actual,
                candidate => candidate.BuilderKey == expected.BuilderKey);

            Assert.Equal(expected.SceneCode, definition.SceneCode);
            Assert.Equal(expected.CutAliases, definition.CutAliases);
            Assert.Equal(expected.PageSize, definition.PageSize);
            Assert.Equal(expected.Reachability, definition.Reachability);
            Assert.Equal(expected.DtoTypeMarker, definition.DtoTypeMarker);
            Assert.Equal(expected.BuilderKey, definition.SourceHashKey);
            Assert.Equal(expected.Capabilities, definition.MutationCapabilities);
        }
    }

    [Fact]
    public void Definitions_HaveUniqueTypedIdentityAndOnlySupportedPageSizes()
    {
        var definitions = LegacySceneCatalog.Definitions;

        Assert.Equal(35, definitions.Select(item => item.BuilderKey).Distinct().Count());
        Assert.Equal(35, definitions.Select(item => item.SceneCode).Distinct().Count());
        Assert.Equal(35, definitions.Select(item => item.SourceHashKey).Distinct().Count());
        Assert.Equal(35, definitions.Select(item => item.DtoTypeMarker).Distinct().Count());
        Assert.All(definitions, definition =>
        {
            Assert.True(typeof(ILegacySceneDtoMarker).IsAssignableFrom(
                definition.DtoTypeMarker));
            Assert.Contains((int)definition.PageSize, new[] { 0, 5, 6, 12 });
            Assert.NotEqual(
                LegacySceneMutationCapability.None,
                definition.MutationCapabilities);
        });

        Assert.Equal(
            new Dictionary<string, LegacyScenePageSize>
            {
                ["s5074"] = LegacyScenePageSize.Five,
                ["s5077"] = LegacyScenePageSize.Six,
                ["s5088"] = LegacyScenePageSize.Twelve
            },
            definitions
                .Where(definition => definition.PageSize != LegacyScenePageSize.None)
                .ToDictionary(definition => definition.BuilderKey, definition => definition.PageSize));
    }

    [Fact]
    public void CutAliasLookup_PreservesConditionalDispatchWithoutGuessing()
    {
        var direct = LegacySceneCatalog.FindByCutAlias("5001");
        var conditional = LegacySceneCatalog.FindByCutAlias("8032");

        Assert.Equal("s5001", Assert.Single(direct).BuilderKey);
        Assert.Equal(
            new[] { "s5032", "s8018" },
            conditional.Select(definition => definition.BuilderKey));
        Assert.All(conditional, definition => Assert.Equal(
            LegacySceneReachability.ConditionalMainFormDispatch,
            definition.Reachability));

        Assert.Empty(LegacySceneCatalog.FindByCutAlias("8086"));
        Assert.Empty(LegacySceneCatalog.FindByCutAlias("../5001.t2s"));
        Assert.Empty(LegacySceneCatalog.FindByCutAlias(null));
    }

    [Fact]
    public void SceneCodeLookup_ReturnsMetadataOnlyAndKeeps8086Unreachable()
    {
        var definition = LegacySceneCatalog.FindBySceneCode("8086");

        Assert.NotNull(definition);
        Assert.Equal("s8086", definition.BuilderKey);
        Assert.Equal(LegacySceneReachability.NoMainFormDispatch, definition.Reachability);
        Assert.Empty(definition.CutAliases);
        Assert.Null(LegacySceneCatalog.FindBySceneCode("N5001"));
        Assert.Null(LegacySceneCatalog.FindBySceneCode("5001.t2s"));
        Assert.Null(LegacySceneCatalog.FindBySceneCode(null));
    }

    [Fact]
    public void PublicCatalogSurface_DoesNotExposeRawObjectMethodOrPathInput()
    {
        var propertyNames = typeof(LegacySceneDefinition)
            .GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Select(property => property.Name)
            .ToArray();
        var disallowedPropertyNames = new[]
        {
            "ObjectName",
            "MethodName",
            "AssetPath",
            "SceneFile",
            "RawMutation"
        };

        Assert.DoesNotContain(
            propertyNames,
            property => disallowedPropertyNames.Contains(property, StringComparer.Ordinal));
        Assert.All(LegacySceneCatalog.Definitions, definition =>
        {
            Assert.DoesNotContain('/', definition.SourceHashKey);
            Assert.DoesNotContain('\\', definition.SourceHashKey);
            Assert.All(definition.CutAliases, alias =>
                Assert.All(alias, character => Assert.True(char.IsLetterOrDigit(character))));
            Assert.Empty(definition.DtoTypeMarker.GetConstructors());
            Assert.Empty(definition.DtoTypeMarker.GetProperties(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly));
        });
    }

    private static ExpectedDefinition E<TDtoMarker>(
        string builderKey,
        string sceneCode,
        string[] cutAliases,
        LegacyScenePageSize pageSize,
        LegacySceneReachability reachability,
        LegacySceneMutationCapability capabilities)
        where TDtoMarker : ILegacySceneDtoMarker =>
        new(
            builderKey,
            sceneCode,
            cutAliases,
            pageSize,
            reachability,
            typeof(TDtoMarker),
            capabilities);

    private sealed record ExpectedDefinition(
        string BuilderKey,
        string SceneCode,
        IReadOnlyList<string> CutAliases,
        LegacyScenePageSize PageSize,
        LegacySceneReachability Reachability,
        Type DtoTypeMarker,
        LegacySceneMutationCapability Capabilities);
}
