using System.Security.Claims;
using Bunit;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.CentralUI.ScriptAnalysis;
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.Security;
using ZB.MOM.WW.ScadaBridge.TemplateEngine;
using TemplateEditPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Design.TemplateEdit;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Design;

/// <summary>
/// bUnit tests for the template editor's READ-ONLY inheritance preview (M9-T26b):
/// rendering the FULL transitively-resolved inherited member set (not just the
/// immediate base) annotated with origin, plus a read-only base-changed staleness
/// banner. The resolve query is dispatched through
/// <see cref="ITemplateInheritanceQueryService"/>, which is substituted here so the
/// tests feed a crafted <see cref="ResolvedTemplateMembers"/> without a real
/// ManagementActor. No mutation / deploy-path behavior is exercised.
/// </summary>
public class TemplateEditInheritedSetTests : BunitContext
{
    private readonly ITemplateEngineRepository _repo = Substitute.For<ITemplateEngineRepository>();
    private readonly ICentralUiRepository _centralUi = Substitute.For<ICentralUiRepository>();
    private readonly IAuditService _audit = Substitute.For<IAuditService>();
    private readonly ISharedScriptCatalog _sharedScripts = Substitute.For<ISharedScriptCatalog>();
    private readonly ITemplateInheritanceQueryService _resolve =
        Substitute.For<ITemplateInheritanceQueryService>();

    // The derived template under edit (id 30) and its immediate base (id 20).
    private const int DerivedId = 30;
    private const int BaseId = 20;

    public TemplateEditInheritedSetTests()
    {
        Services.AddSingleton(_repo);
        Services.AddSingleton(_centralUi);
        Services.AddSingleton(_audit);
        Services.AddSingleton(_resolve);
        Services.AddScoped<TemplateService>();
        Services.AddScoped<IDialogService, DialogService>();

        // ScriptAnalysisService is injected but never exercised on the initial
        // render — supply its collaborators so DI can construct it.
        Services.AddSingleton(_sharedScripts);
        Services.AddSingleton<IMemoryCache>(new MemoryCache(new MemoryCacheOptions()));
        Services.AddScoped<ScriptAnalysisService>();

        AddTestAuth();
        SeedDerivedTemplateRows();
    }

    private void AddTestAuth()
    {
        var claims = new[]
        {
            new Claim(JwtTokenService.UsernameClaimType, "tester"),
            new Claim(JwtTokenService.RoleClaimType, "Designer"),
        };
        var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
        Services.AddSingleton<AuthenticationStateProvider>(new TestAuthStateProvider(user));
        Services.AddAuthorizationCore();
    }

    /// <summary>
    /// Seeds the stored-row repository view of a derived template. The stored rows
    /// deliberately omit the transitively-inherited members — those only appear via
    /// the resolved set the query service returns.
    /// </summary>
    private void SeedDerivedTemplateRows()
    {
        var grandparent = new Template("Root") { Id = 10 };
        var baseTemplate = new Template("Base") { Id = BaseId, ParentTemplateId = 10 };
        var derived = new Template("Derived")
        {
            Id = DerivedId,
            ParentTemplateId = BaseId,
            IsDerived = true,
        };

        _repo.GetAllTemplatesAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<Template>>(
                new List<Template> { grandparent, baseTemplate, derived }));
        _repo.GetTemplateWithChildrenAsync(DerivedId, Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<Template?>(derived));
        _repo.GetTemplateByIdAsync(BaseId, Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<Template?>(baseTemplate));

        // Stored child rows: empty (the inherited set comes from the resolver).
        _repo.GetAttributesByTemplateIdAsync(DerivedId, Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<TemplateAttribute>>(new List<TemplateAttribute>()));
        _repo.GetAlarmsByTemplateIdAsync(DerivedId, Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<TemplateAlarm>>(new List<TemplateAlarm>()));
        _repo.GetScriptsByTemplateIdAsync(DerivedId, Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<TemplateScript>>(new List<TemplateScript>()));
        _repo.GetCompositionsByTemplateIdAsync(DerivedId, Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<TemplateComposition>>(new List<TemplateComposition>()));
        _repo.GetNativeAlarmSourcesByTemplateIdAsync(DerivedId, Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<TemplateNativeAlarmSource>>(
                new List<TemplateNativeAlarmSource>()));

        _centralUi.GetAllDataConnectionsAsync(Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<DataConnection>>(new List<DataConnection>()));
        _centralUi.GetInstancesFilteredAsync(
                Arg.Any<int?>(), Arg.Any<int?>(), Arg.Any<string?>(), Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<IReadOnlyList<Instance>>(new List<Instance>()));
    }

    private void SeedResolved(ResolvedTemplateMembers resolved) =>
        _resolve.ResolveAsync(DerivedId, Arg.Any<CancellationToken>())
            .Returns(Task.FromResult<ResolvedTemplateMembers?>(resolved));

    [Fact]
    public void Renders_TransitivelyInheritedMembers_WithOriginAnnotation()
    {
        // A grandparent-supplied attribute and a base attribute added AFTER the
        // derived template was created — neither is in the stored rows; both must
        // appear in the resolved inherited set with their origin template name.
        SeedResolved(new ResolvedTemplateMembers
        {
            TemplateId = DerivedId,
            TemplateName = "Derived",
            ParentTemplateId = BaseId,
            Attributes = new List<ResolvedTemplateMemberInfo>
            {
                new()
                {
                    Name = "FromRoot",
                    IsInherited = true,
                    OriginTemplateId = 10,
                    OriginTemplateName = "Root",
                    EffectiveValue = "rootval",
                },
                new()
                {
                    Name = "AddedLater",
                    IsInherited = true,
                    OriginTemplateId = BaseId,
                    OriginTemplateName = "Base",
                    EffectiveValue = "latebase",
                },
            },
            Staleness = new ResolvedTemplateStaleness { IsStale = false, DifferingMemberCount = 0 },
        });

        var cut = Render<TemplateEditPage>(p => p.Add(c => c.Id, DerivedId));

        cut.WaitForAssertion(() =>
        {
            // The transitively-inherited member names render...
            Assert.Contains("FromRoot", cut.Markup);
            Assert.Contains("AddedLater", cut.Markup);
            // ...annotated with their origin template (multi-level + post-creation).
            Assert.Contains("Root", cut.Markup);
            Assert.Contains("Base", cut.Markup);
        });
    }

    [Fact]
    public void Renders_EffectiveAlarmTriggerConfiguration_FromResolvedSet()
    {
        // A HiLo alarm whose effective (merged) trigger config carries both
        // setpoints — the editor must surface the merged EffectiveTriggerConfiguration.
        SeedResolved(new ResolvedTemplateMembers
        {
            TemplateId = DerivedId,
            TemplateName = "Derived",
            ParentTemplateId = BaseId,
            Alarms = new List<ResolvedTemplateMemberInfo>
            {
                new()
                {
                    Name = "TempAlarm",
                    IsInherited = true,
                    OriginTemplateId = BaseId,
                    OriginTemplateName = "Base",
                    EffectiveValue = "500",
                    EffectiveTriggerConfiguration = "{\"hi\":90,\"lo\":10}",
                },
            },
            Staleness = new ResolvedTemplateStaleness { IsStale = false, DifferingMemberCount = 0 },
        });

        var cut = Render<TemplateEditPage>(p => p.Add(c => c.Id, DerivedId));

        cut.WaitForAssertion(() =>
        {
            Assert.Contains("TempAlarm", cut.Markup);
            // The merged effective HiLo config (both setpoints) is shown.
            Assert.Contains("hi", cut.Markup);
            Assert.Contains("lo", cut.Markup);
        });
    }

    [Fact]
    public void Shows_StalenessBanner_WhenResolvedSetIsStale()
    {
        SeedResolved(new ResolvedTemplateMembers
        {
            TemplateId = DerivedId,
            TemplateName = "Derived",
            ParentTemplateId = BaseId,
            Attributes = new List<ResolvedTemplateMemberInfo>
            {
                new()
                {
                    Name = "AddedLater",
                    IsInherited = true,
                    OriginTemplateId = BaseId,
                    OriginTemplateName = "Base",
                    EffectiveValue = "latebase",
                },
            },
            Staleness = new ResolvedTemplateStaleness { IsStale = true, DifferingMemberCount = 3 },
        });

        var cut = Render<TemplateEditPage>(p => p.Add(c => c.Id, DerivedId));

        cut.WaitForAssertion(() =>
        {
            // Informational banner naming the base-changed condition + the count.
            Assert.Contains("Base template changed", cut.Markup);
            Assert.Contains("3", cut.Markup);
        });
    }

    [Fact]
    public void NoStalenessBanner_WhenResolvedSetIsNotStale()
    {
        SeedResolved(new ResolvedTemplateMembers
        {
            TemplateId = DerivedId,
            TemplateName = "Derived",
            ParentTemplateId = BaseId,
            Staleness = new ResolvedTemplateStaleness { IsStale = false, DifferingMemberCount = 0 },
        });

        var cut = Render<TemplateEditPage>(p => p.Add(c => c.Id, DerivedId));

        cut.WaitForAssertion(() => Assert.Contains("Derived", cut.Markup));
        Assert.DoesNotContain("Base template changed", cut.Markup);
    }
}
