using System.Security.Claims;
using ZB.MOM.WW.ScadaBridge.Security;
using Bunit;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using SmtpConfigurationPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Notifications.SmtpConfiguration;

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

/// <summary>
/// bUnit rendering tests for the SMTP Configuration page — specifically the TlsMode
/// field added so the UI exposes all five user-relevant SmtpConfiguration fields.
/// </summary>
public class SmtpConfigurationPageTests : BunitContext
{
    private void WireAuth()
    {
        var claims = new[]
        {
            new Claim(JwtTokenService.UsernameClaimType, "tester"),
            new Claim(JwtTokenService.RoleClaimType, "Administrator"),
        };
        var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
        Services.AddSingleton<AuthenticationStateProvider>(new TestAuthStateProvider(user));
        Services.AddAuthorizationCore();
    }

    private static SmtpConfiguration Sample() =>
        new("smtp.example.com", "Basic", "noreply@example.com")
        {
            Id = 1,
            Port = 587,
            TlsMode = "StartTLS",
            Credentials = "user:pass",
        };

    private static SmtpConfiguration OAuth2Sample() =>
        new("smtp.example.com", "OAuth2", "noreply@example.com")
        {
            Id = 1,
            Port = 587,
            TlsMode = "StartTLS",
            Credentials = "client-secret",
        };

    private static SmtpConfiguration EwsSample() =>
        new("https://mail.example.com/ews/exchange.asmx", "Basic", "noreply@example.com")
        {
            Id = 2,
            Port = 587,
            TlsMode = "StartTLS",
            Transport = "Ews",
            Credentials = @"domain\user:pass",
        };

    private IRenderedComponent<SmtpConfigurationPage> RenderWith(
        INotificationRepository repo, params SmtpConfiguration[] configs)
    {
        repo.GetAllSmtpConfigurationsAsync()
            .Returns(Task.FromResult<IReadOnlyList<SmtpConfiguration>>(configs.ToList()));
        Services.AddSingleton(repo);
        WireAuth();

        var cut = Render<SmtpConfigurationPage>();
        cut.WaitForState(() => cut.Markup.Contains(configs[0].Host));
        return cut;
    }

    private static void ClickEdit(IRenderedComponent<SmtpConfigurationPage> cut)
        => cut.FindAll("button").First(b => b.TextContent.Contains("Edit")).Click();

    private static void ClickSave(IRenderedComponent<SmtpConfigurationPage> cut)
        => cut.FindAll("button").First(b => b.TextContent.Contains("Save")).Click();

    /// <summary>Locates a select by an option it offers — the page has no element ids.</summary>
    private static AngleSharp.Dom.IElement SelectWithOption(
        IRenderedComponent<SmtpConfigurationPage> cut, string optionText)
        => cut.FindAll("select")
            .Single(s => s.QuerySelectorAll("option").Any(o => o.TextContent == optionText));

    private static bool HasSelectWithOption(
        IRenderedComponent<SmtpConfigurationPage> cut, string optionText)
        => cut.FindAll("select")
            .Any(s => s.QuerySelectorAll("option").Any(o => o.TextContent == optionText));

    [Fact]
    public void EditForm_RendersTlsModeSelectWithAllThreeModes()
    {
        var repo = Substitute.For<INotificationRepository>();
        repo.GetAllSmtpConfigurationsAsync()
            .Returns(Task.FromResult<IReadOnlyList<SmtpConfiguration>>(
                new List<SmtpConfiguration> { Sample() }));
        Services.AddSingleton(repo);
        WireAuth();

        var cut = Render<SmtpConfigurationPage>();
        cut.WaitForState(() => cut.Markup.Contains("smtp.example.com"));

        cut.FindAll("button").First(b => b.TextContent.Contains("Edit")).Click();

        cut.WaitForAssertion(() =>
        {
            var selects = cut.FindAll("select");
            var tlsSelect = selects.Single(s => s.QuerySelectorAll("option")
                .Any(o => o.TextContent == "StartTLS"));
            var modes = tlsSelect.QuerySelectorAll("option").Select(o => o.TextContent).ToList();
            Assert.Equal(new[] { "None", "StartTLS", "SSL" }, modes);
        });
    }

    [Fact]
    public void ReadOnlyView_ShowsTlsMode()
    {
        var repo = Substitute.For<INotificationRepository>();
        repo.GetAllSmtpConfigurationsAsync()
            .Returns(Task.FromResult<IReadOnlyList<SmtpConfiguration>>(
                new List<SmtpConfiguration> { Sample() }));
        Services.AddSingleton(repo);
        WireAuth();

        var cut = Render<SmtpConfigurationPage>();

        cut.WaitForAssertion(() =>
        {
            Assert.Contains("TLS Mode", cut.Markup);
            Assert.Contains("StartTLS", cut.Markup);
        });
    }

    [Fact]
    public void SavingEdit_PersistsChosenTlsMode()
    {
        var config = Sample();
        var repo = Substitute.For<INotificationRepository>();
        repo.GetAllSmtpConfigurationsAsync()
            .Returns(Task.FromResult<IReadOnlyList<SmtpConfiguration>>(
                new List<SmtpConfiguration> { config }));
        Services.AddSingleton(repo);
        WireAuth();

        var cut = Render<SmtpConfigurationPage>();
        cut.WaitForState(() => cut.Markup.Contains("smtp.example.com"));

        cut.FindAll("button").First(b => b.TextContent.Contains("Edit")).Click();

        var tlsSelect = cut.FindAll("select")
            .Single(s => s.QuerySelectorAll("option").Any(o => o.TextContent == "StartTLS"));
        tlsSelect.Change("SSL");

        cut.FindAll("button").First(b => b.TextContent.Contains("Save")).Click();

        cut.WaitForAssertion(() =>
        {
            repo.Received().UpdateSmtpConfigurationAsync(
                Arg.Is<SmtpConfiguration>(c => c.TlsMode == "SSL"));
            repo.Received().SaveChangesAsync();
        });
    }

    // ========================================================================
    // Transport selector (EWS) — form state machine
    // ========================================================================

    [Fact]
    public void EditForm_WithEwsTransport_HidesPortTlsAndAuthTypeAndForcesBasicAuth()
    {
        var repo = Substitute.For<INotificationRepository>();
        var cut = RenderWith(repo, OAuth2Sample());

        ClickEdit(cut);
        SelectWithOption(cut, "Ews").Change("Ews");

        // Port input, TLS select and Auth Type select are all gone under EWS.
        Assert.Empty(cut.FindAll("input[type=number]"));
        Assert.False(HasSelectWithOption(cut, "StartTLS"));
        Assert.False(HasSelectWithOption(cut, "OAuth2"));
        Assert.Contains("EWS URL", cut.Markup);

        // The forced auth type is only observable through what gets persisted: the row
        // loaded as OAuth2 must save as Basic because EWS supports Basic only.
        cut.Find("input[type=text]").Change("https://mail.example.com/ews/exchange.asmx");
        cut.Find("input[type=password]").Change(@"dom\user:pw");
        ClickSave(cut);

        cut.WaitForAssertion(() =>
            repo.Received().UpdateSmtpConfigurationAsync(Arg.Is<SmtpConfiguration>(
                c => c.Transport == "Ews"
                     && c.AuthType == "Basic"
                     && c.Host == "https://mail.example.com/ews/exchange.asmx")));
    }

    [Fact]
    public void EditForm_SwitchingEwsBackToSmtp_RestoresAuthTypeAndReshowsPortAndTls()
    {
        var repo = Substitute.For<INotificationRepository>();
        var cut = RenderWith(repo, OAuth2Sample());

        ClickEdit(cut);
        SelectWithOption(cut, "Ews").Change("Ews");
        SelectWithOption(cut, "Ews").Change("Smtp");

        // Port and TLS controls are back...
        Assert.Single(cut.FindAll("input[type=number]"));
        Assert.True(HasSelectWithOption(cut, "StartTLS"));
        Assert.True(HasSelectWithOption(cut, "OAuth2"));

        // ...and the round trip left the pre-switch values (OAuth2 / 587 / StartTLS) intact.
        ClickSave(cut);

        cut.WaitForAssertion(() =>
            repo.Received().UpdateSmtpConfigurationAsync(Arg.Is<SmtpConfiguration>(
                c => c.Transport == "Smtp"
                     && c.AuthType == "OAuth2"
                     && c.Port == 587
                     && c.TlsMode == "StartTLS")));
    }

    [Fact]
    public void Save_WithEwsTransportAndNonHttpsHost_ShowsFormErrorAndDoesNotPersist()
    {
        var repo = Substitute.For<INotificationRepository>();
        var cut = RenderWith(repo, OAuth2Sample());

        ClickEdit(cut);
        SelectWithOption(cut, "Ews").Change("Ews");
        cut.Find("input[type=text]").Change("http://mail.example.com/ews/exchange.asmx");
        ClickSave(cut);

        cut.WaitForAssertion(() => Assert.Contains("absolute https://", cut.Markup));
        repo.DidNotReceive().UpdateSmtpConfigurationAsync(Arg.Any<SmtpConfiguration>());
        repo.DidNotReceive().SaveChangesAsync();
    }

    [Fact]
    public void Save_WithEwsTransportAndCredentialsWithoutColon_ShowsFormErrorAndDoesNotPersist()
    {
        var repo = Substitute.For<INotificationRepository>();
        // The realistic mistake: an existing SMTP row whose credential is a bare password
        // is flipped to Ews. Host is valid, so only the credentials rule can reject it.
        var cut = RenderWith(repo, OAuth2Sample());

        ClickEdit(cut);
        SelectWithOption(cut, "Ews").Change("Ews");
        cut.Find("input[type=text]").Change("https://mail.example.com/ews/exchange.asmx");
        cut.Find("input[type=password]").Change("bare-password");
        ClickSave(cut);

        cut.WaitForAssertion(() => Assert.Contains("username:password", cut.Markup));
        repo.DidNotReceive().UpdateSmtpConfigurationAsync(Arg.Any<SmtpConfiguration>());
        repo.DidNotReceive().SaveChangesAsync();
    }

    [Fact]
    public void Save_WithEwsTransportAndDomainQualifiedCredentials_Persists()
    {
        var repo = Substitute.For<INotificationRepository>();
        var cut = RenderWith(repo, OAuth2Sample());

        ClickEdit(cut);
        SelectWithOption(cut, "Ews").Change("Ews");
        cut.Find("input[type=text]").Change("https://mail.example.com/ews/exchange.asmx");
        // A domain-qualified user name carries no colon of its own; the split takes the
        // FIRST colon only, so a password containing colons stays intact.
        cut.Find("input[type=password]").Change(@"dom\user:pw:with:colons");
        ClickSave(cut);

        cut.WaitForAssertion(() =>
        {
            repo.Received().UpdateSmtpConfigurationAsync(Arg.Is<SmtpConfiguration>(
                c => c.Transport == "Ews" && c.Credentials == @"dom\user:pw:with:colons"));
            repo.Received().SaveChangesAsync();
        });
    }

    [Fact]
    public void ReadOnlyView_ShowsTransportBadgeAndHidesPortAndTlsForEwsRows()
    {
        var ewsCut = RenderWith(Substitute.For<INotificationRepository>(), EwsSample());

        ewsCut.WaitForAssertion(() =>
        {
            Assert.Contains("<span class=\"badge bg-secondary\">Ews</span>", ewsCut.Markup);
            Assert.Contains("EWS URL", ewsCut.Markup);
            Assert.DoesNotContain("TLS Mode", ewsCut.Markup);
            // The EWS URL is rendered bare — no ":port" suffix as the SMTP host row has.
            Assert.DoesNotContain("exchange.asmx:587", ewsCut.Markup);
        });
    }

    [Fact]
    public void ReadOnlyView_LegacyRowWithNullTransport_ShowsSmtpBadgeAndKeepsPortAndTls()
    {
        var cut = RenderWith(Substitute.For<INotificationRepository>(), Sample());

        cut.WaitForAssertion(() =>
        {
            Assert.Contains("<span class=\"badge bg-secondary\">Smtp</span>", cut.Markup);
            Assert.Contains("smtp.example.com:587", cut.Markup);
            Assert.Contains("TLS Mode", cut.Markup);
        });
    }
}
