using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

namespace MBN_STOCK_WEBVIEW.Core.Tests;

public sealed class TrustedManualSceneDataLoader5025Tests
{
    [Theory]
    [InlineData(S5025ManualAudience.Individual, "개인")]
    [InlineData(S5025ManualAudience.Foreign, "외국인")]
    [InlineData(S5025ManualAudience.Institution, "기관")]
    public async Task Loader_uses_only_closed_audience_and_proves_all_ten_slot_mutations(
        S5025ManualAudience audience,
        string expectedTitle)
    {
        var source = new RecordingSource(Rows());

        var data = await new S5025SceneDataLoader(source).LoadAsync(
            new S5025ManualSceneLoadRequest(audience),
            CancellationToken.None);
        var mutations = new S5025SceneMutationBuilder().Build(data);

        Assert.Equal(audience, Assert.Single(source.Calls));
        Assert.Equal(expectedTitle, data.Title);
        Assert.Equal(5, data.Rows.Count);
        Assert.Equal(21, mutations.Count);
        Assert.Contains(mutations, mutation =>
            mutation is PlayoutSetValue
            {
                ObjectName: "title",
                Value: var value
            } && value == expectedTitle + " 순매도 상위");
        Assert.Contains(mutations, mutation =>
            mutation is PlayoutSetValue { ObjectName: "종목1", Value: "L1" });
        Assert.Contains(mutations, mutation =>
            mutation is PlayoutSetValue { ObjectName: "금액10", Value: "R5A" });
    }

    [Fact]
    public void Public_manual_contract_exposes_no_path_or_free_form_selector()
    {
        Assert.Equal(
            [typeof(S5025ManualAudience)],
            typeof(S5025ManualSceneLoadRequest).GetProperties().Select(property => property.PropertyType));

        var method = Assert.Single(typeof(IS5025TrustedManualDataSource).GetMethods());
        Assert.Equal("ReadAsync", method.Name);
        Assert.DoesNotContain(
            method.GetParameters(),
            parameter => parameter.ParameterType == typeof(string));
        Assert.DoesNotContain(
            typeof(S5025SceneDataLoader).GetMethods(),
            methodInfo => methodInfo.GetParameters().Any(parameter =>
                parameter.ParameterType == typeof(string)));
    }

    [Theory]
    [InlineData(4)]
    [InlineData(6)]
    public async Task Loader_requires_exactly_five_trusted_rows(int rowCount)
    {
        var rows = Rows().Take(rowCount).ToList();
        while (rows.Count < rowCount)
        {
            rows.Add(new("X", "1", "Y", "2"));
        }

        await Assert.ThrowsAsync<LegacySceneDataException>(() =>
            new S5025SceneDataLoader(new RecordingSource(rows)).LoadAsync(
                new S5025ManualSceneLoadRequest(S5025ManualAudience.Individual)));
    }

    [Fact]
    public async Task Loader_rejects_null_control_and_oversized_cells_before_mutations()
    {
        var nullRows = Rows();
        nullRows[0] = new(null!, "1", "R", "2");
        await Assert.ThrowsAsync<LegacySceneDataException>(() =>
            new S5025SceneDataLoader(new RecordingSource(nullRows)).LoadAsync(
                new S5025ManualSceneLoadRequest(S5025ManualAudience.Individual)));

        var controlRows = Rows();
        controlRows[0] = new("bad\nname", "1", "R", "2");
        await Assert.ThrowsAsync<LegacySceneDataException>(() =>
            new S5025SceneDataLoader(new RecordingSource(controlRows)).LoadAsync(
                new S5025ManualSceneLoadRequest(S5025ManualAudience.Individual)));

        var oversizedRows = Rows();
        oversizedRows[0] = new(new string('X', 257), "1", "R", "2");
        await Assert.ThrowsAsync<LegacySceneDataException>(() =>
            new S5025SceneDataLoader(new RecordingSource(oversizedRows)).LoadAsync(
                new S5025ManualSceneLoadRequest(S5025ManualAudience.Individual)));
    }

    [Fact]
    public async Task Unknown_audience_fails_before_trusted_source_is_called()
    {
        var source = new RecordingSource(Rows());

        await Assert.ThrowsAsync<LegacySceneDataException>(() =>
            new S5025SceneDataLoader(source).LoadAsync(
                new S5025ManualSceneLoadRequest((S5025ManualAudience)99)));

        Assert.Empty(source.Calls);
    }

    private static List<S5025TrustedManualRow> Rows() =>
    [
        new("L1", "L1A", "R1", "R1A"),
        new("L2", "L2A", "R2", "R2A"),
        new("L3", "L3A", "R3", "R3A"),
        new("L4", "L4A", "R4", "R4A"),
        new("L5", "L5A", "R5", "R5A")
    ];

    private sealed class RecordingSource(IReadOnlyList<S5025TrustedManualRow> rows)
        : IS5025TrustedManualDataSource
    {
        public List<S5025ManualAudience> Calls { get; } = [];

        public Task<IReadOnlyList<S5025TrustedManualRow>> ReadAsync(
            S5025ManualAudience audience,
            CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            Calls.Add(audience);
            return Task.FromResult(rows);
        }
    }
}
