using NSubstitute;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
using ZB.MOM.WW.ScadaBridge.Transport.Export;

namespace ZB.MOM.WW.ScadaBridge.Transport.Tests.Export;

public sealed class DependencyResolverTests
{
    private readonly ITemplateEngineRepository _templates = Substitute.For<ITemplateEngineRepository>();
    private readonly IExternalSystemRepository _externalSystems = Substitute.For<IExternalSystemRepository>();
    private readonly INotificationRepository _notifications = Substitute.For<INotificationRepository>();
    private readonly IInboundApiRepository _inboundApi = Substitute.For<IInboundApiRepository>();

    private DependencyResolver Sut() => new(_templates, _externalSystems, _notifications, _inboundApi);

    private static ExportSelection SelectTemplates(params int[] ids) => new(
        TemplateIds: ids,
        SharedScriptIds: Array.Empty<int>(),
        ExternalSystemIds: Array.Empty<int>(),
        DatabaseConnectionIds: Array.Empty<int>(),
        NotificationListIds: Array.Empty<int>(),
        SmtpConfigurationIds: Array.Empty<int>(),
        ApiMethodIds: Array.Empty<int>(),
        IncludeDependencies: true);

    private static ExportSelection SelectApiMethods(params int[] ids) => new(
        TemplateIds: Array.Empty<int>(),
        SharedScriptIds: Array.Empty<int>(),
        ExternalSystemIds: Array.Empty<int>(),
        DatabaseConnectionIds: Array.Empty<int>(),
        NotificationListIds: Array.Empty<int>(),
        SmtpConfigurationIds: Array.Empty<int>(),
        ApiMethodIds: ids,
        IncludeDependencies: true);

    private void StubTemplate(Template t)
    {
        _templates.GetTemplateWithChildrenAsync(t.Id, Arg.Any<CancellationToken>()).Returns(t);
    }

    private void StubAllSharedScripts(params SharedScript[] scripts)
    {
        _templates.GetAllSharedScriptsAsync(Arg.Any<CancellationToken>()).Returns(scripts);
    }

    private void StubAllExternalSystems(params ExternalSystemDefinition[] systems)
    {
        _externalSystems.GetAllExternalSystemsAsync(Arg.Any<CancellationToken>()).Returns(systems);
        foreach (var es in systems)
        {
            _externalSystems
                .GetMethodsByExternalSystemIdAsync(es.Id, Arg.Any<CancellationToken>())
                .Returns(Array.Empty<ExternalSystemMethod>());
        }
    }

    private void StubAllFolders(params TemplateFolder[] folders)
    {
        _templates.GetAllFoldersAsync(Arg.Any<CancellationToken>()).Returns(folders);
    }

    [Fact]
    public async Task Resolve_includes_base_template_for_composed_template()
    {
        var baseT = new Template("Base") { Id = 10 };
        var composing = new Template("Top") { Id = 11 };
        composing.Compositions.Add(new TemplateComposition("slot") { TemplateId = 11, ComposedTemplateId = 10 });

        StubTemplate(composing);
        StubTemplate(baseT);
        StubAllSharedScripts();
        StubAllExternalSystems();
        StubAllFolders();

        var result = await Sut().ResolveAsync(SelectTemplates(11), CancellationToken.None);

        Assert.Equal(2, result.Templates.Count);
        Assert.Contains(result.Templates, t => t.Id == 10);
        Assert.Contains(result.Templates, t => t.Id == 11);
    }

    [Fact]
    public async Task Resolve_includes_shared_script_referenced_by_template()
    {
        var shared = new SharedScript("UtilHelper", "return 42;") { Id = 100 };
        var t = new Template("UsesUtil") { Id = 1 };
        t.Scripts.Add(new TemplateScript("body", "var x = UtilHelper(); return x;") { TemplateId = 1 });

        StubTemplate(t);
        StubAllSharedScripts(shared, new SharedScript("OtherScript", "return 0;") { Id = 101 });
        StubAllExternalSystems();
        StubAllFolders();

        var result = await Sut().ResolveAsync(SelectTemplates(1), CancellationToken.None);

        Assert.Single(result.SharedScripts);
        Assert.Equal("UtilHelper", result.SharedScripts[0].Name);
    }

    [Fact]
    public async Task Resolve_includes_external_system_referenced_by_template()
    {
        var es = new ExternalSystemDefinition("ErpSystem", "https://erp", "ApiKey") { Id = 7 };
        var t = new Template("UsesErp") { Id = 2 };
        t.Scripts.Add(new TemplateScript("call", "ErpSystem.Call(\"x\");") { TemplateId = 2 });

        StubTemplate(t);
        StubAllSharedScripts();
        StubAllExternalSystems(es, new ExternalSystemDefinition("Other", "https://o", "Basic") { Id = 8 });
        StubAllFolders();

        var result = await Sut().ResolveAsync(SelectTemplates(2), CancellationToken.None);

        Assert.Single(result.ExternalSystems);
        Assert.Equal("ErpSystem", result.ExternalSystems[0].Name);
    }

    [Fact]
    public async Task Resolve_includes_api_method_shared_script_dependency()
    {
        var shared = new SharedScript("Validator", "return true;") { Id = 50 };
        var method = new ApiMethod("submit", "var ok = Validator(input); return ok;") { Id = 5 };

        _inboundApi.GetApiMethodByIdAsync(5, Arg.Any<CancellationToken>()).Returns(method);
        StubAllSharedScripts(shared);
        StubAllExternalSystems();
        StubAllFolders();

        var result = await Sut().ResolveAsync(SelectApiMethods(5), CancellationToken.None);

        Assert.Single(result.ApiMethods);
        Assert.Single(result.SharedScripts);
        Assert.Equal("Validator", result.SharedScripts[0].Name);
    }

    [Fact]
    public async Task Resolve_handles_diamond_dependency_without_duplication()
    {
        // A composes B and C; both B and C compose D. Selection={A}. D must appear once.
        var d = new Template("D") { Id = 4 };
        var b = new Template("B") { Id = 2 };
        b.Compositions.Add(new TemplateComposition("d-in-b") { TemplateId = 2, ComposedTemplateId = 4 });
        var c = new Template("C") { Id = 3 };
        c.Compositions.Add(new TemplateComposition("d-in-c") { TemplateId = 3, ComposedTemplateId = 4 });
        var a = new Template("A") { Id = 1 };
        a.Compositions.Add(new TemplateComposition("b-in-a") { TemplateId = 1, ComposedTemplateId = 2 });
        a.Compositions.Add(new TemplateComposition("c-in-a") { TemplateId = 1, ComposedTemplateId = 3 });

        StubTemplate(a);
        StubTemplate(b);
        StubTemplate(c);
        StubTemplate(d);
        StubAllSharedScripts();
        StubAllExternalSystems();
        StubAllFolders();

        var result = await Sut().ResolveAsync(SelectTemplates(1), CancellationToken.None);

        Assert.Equal(4, result.Templates.Count);
        Assert.Single(result.Templates, t => t.Id == 4);
    }

    [Fact]
    public async Task Resolve_includes_template_folder_for_each_selected_template()
    {
        var root = new TemplateFolder("Root") { Id = 1, ParentFolderId = null };
        var child = new TemplateFolder("Child") { Id = 2, ParentFolderId = 1 };
        var grand = new TemplateFolder("Grand") { Id = 3, ParentFolderId = 2 };
        var t = new Template("T") { Id = 99, FolderId = 3 };

        StubTemplate(t);
        StubAllSharedScripts();
        StubAllExternalSystems();
        StubAllFolders(root, child, grand,
            new TemplateFolder("Unrelated") { Id = 4, ParentFolderId = null });

        var result = await Sut().ResolveAsync(SelectTemplates(99), CancellationToken.None);

        Assert.Equal(3, result.TemplateFolders.Count);
        // Root-first ordering: depth 0, 1, 2.
        Assert.Equal("Root", result.TemplateFolders[0].Name);
        Assert.Equal("Child", result.TemplateFolders[1].Name);
        Assert.Equal("Grand", result.TemplateFolders[2].Name);
    }

    [Fact]
    public async Task Resolve_returns_topological_order_base_before_derived()
    {
        // Top composes Middle, Middle composes Leaf. Order must be Leaf, Middle, Top.
        var leaf = new Template("Leaf") { Id = 30 };
        var middle = new Template("Middle") { Id = 20 };
        middle.Compositions.Add(new TemplateComposition("l") { TemplateId = 20, ComposedTemplateId = 30 });
        var top = new Template("Top") { Id = 10 };
        top.Compositions.Add(new TemplateComposition("m") { TemplateId = 10, ComposedTemplateId = 20 });

        StubTemplate(top);
        StubTemplate(middle);
        StubTemplate(leaf);
        StubAllSharedScripts();
        StubAllExternalSystems();
        StubAllFolders();

        var result = await Sut().ResolveAsync(SelectTemplates(10), CancellationToken.None);

        Assert.Equal(3, result.Templates.Count);
        Assert.Equal("Leaf", result.Templates[0].Name);
        Assert.Equal("Middle", result.Templates[1].Name);
        Assert.Equal("Top", result.Templates[2].Name);
    }
}
