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 SmsConfigurationPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Notifications.SmsConfiguration;

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

/// <summary>
/// bUnit rendering tests for the SMS Configuration page — verifies the read-only list,
/// that the stored Auth Token value is never rendered (only a presence indicator),
/// repository-direct save on create/edit, and the preserve-if-blank secret handling.
/// </summary>
public class SmsConfigurationPageTests : 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 const string SecretToken = "super-secret-auth-token";

    private static SmsConfiguration Sample() =>
        new("ACcccccccccccccccccccccccccccccccc", "+15551234567")
        {
            Id = 1,
            MessagingServiceSid = "MGtest_messaging_service",
            ApiBaseUrl = "https://api.example.com",
            AuthToken = SecretToken,
            ConnectionTimeoutSeconds = 30,
            MaxRetries = 10,
            RetryDelay = TimeSpan.FromMinutes(1),
        };

    private static INotificationRepository RepoWith(params SmsConfiguration[] configs)
    {
        var repo = Substitute.For<INotificationRepository>();
        repo.GetAllSmsConfigurationsAsync()
            .Returns(Task.FromResult<IReadOnlyList<SmsConfiguration>>(configs.ToList()));
        return repo;
    }

    [Fact]
    public void ReadOnlyView_ShowsConfigRow_ButNeverRendersAuthTokenValue()
    {
        var repo = RepoWith(Sample());
        Services.AddSingleton(repo);
        WireAuth();

        var cut = Render<SmsConfigurationPage>();

        cut.WaitForAssertion(() =>
        {
            // Config row fields render.
            Assert.Contains("ACcccccccccccccccccccccccccccccccc", cut.Markup);
            Assert.Contains("+15551234567", cut.Markup);
            Assert.Contains("MGtest_messaging_service", cut.Markup);
            // Auth Token shows a presence indicator only — never the value.
            Assert.Contains("Auth Token", cut.Markup);
            Assert.Contains("(stored)", cut.Markup);
            Assert.DoesNotContain(SecretToken, cut.Markup);
        });
    }

    [Fact]
    public void EditForm_DoesNotPrefillAuthToken_ButPrefillsMessagingServiceSid()
    {
        var repo = RepoWith(Sample());
        Services.AddSingleton(repo);
        WireAuth();

        var cut = Render<SmsConfigurationPage>();
        cut.WaitForState(() => cut.Markup.Contains("ACcccccccccccccccccccccccccccccccc"));

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

        cut.WaitForAssertion(() =>
        {
            // The secret is never placed into the form markup (no input pre-fill).
            Assert.DoesNotContain(SecretToken, cut.Markup);
            // Non-secret fields, including MessagingServiceSid, are pre-filled.
            var inputs = cut.FindAll("input");
            Assert.Contains(inputs, i => i.GetAttribute("value") == "MGtest_messaging_service");
        });
    }

    [Fact]
    public void SavingNewConfig_CallsAddAndSaveChanges()
    {
        var repo = RepoWith();
        Services.AddSingleton(repo);
        WireAuth();

        var cut = Render<SmsConfigurationPage>();
        cut.WaitForState(() => cut.Markup.Contains("No SMS configuration set."));

        cut.FindAll("button").First(b => b.TextContent.Contains("Add SMS configuration")).Click();

        // Re-query between each Change(): two-way binding re-renders the form and
        // invalidates previously found element references.
        cut.FindAll("input[type=text]")[0].Change("ACdddddddddddddddddddddddddddddddd");   // Account SID
        cut.FindAll("input[type=text]")[1].Change("+15559876543");    // From Number
        cut.FindAll("input[type=password]").First().Change("new-token");

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

        cut.WaitForAssertion(() =>
        {
            repo.Received().AddSmsConfigurationAsync(
                Arg.Is<SmsConfiguration>(c =>
                    c.AccountSid == "ACdddddddddddddddddddddddddddddddd" &&
                    c.FromNumber == "+15559876543" &&
                    c.AuthToken == "new-token"));
            repo.Received().SaveChangesAsync();
        });
    }

    [Fact]
    public void SavingNewConfig_MessagingServiceSidOnly_NoFromNumber_Saves()
    {
        // UI-Med-2: a Twilio Messaging-Service-only config is valid with no From number.
        // The either-or validation must accept it and persist a null FromNumber.
        var repo = RepoWith();
        Services.AddSingleton(repo);
        WireAuth();

        var cut = Render<SmsConfigurationPage>();
        cut.WaitForState(() => cut.Markup.Contains("No SMS configuration set."));

        cut.FindAll("button").First(b => b.TextContent.Contains("Add SMS configuration")).Click();

        // Account SID + Messaging Service SID, leaving From Number (index 1) blank.
        cut.FindAll("input[type=text]")[0].Change("ACeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");        // Account SID
        cut.FindAll("input[type=text]")[2].Change("MGmessaging_service");  // Messaging Service SID
        cut.FindAll("input[type=password]").First().Change("new-token");

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

        cut.WaitForAssertion(() =>
        {
            repo.Received().AddSmsConfigurationAsync(
                Arg.Is<SmsConfiguration>(c =>
                    c.AccountSid == "ACeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" &&
                    c.FromNumber == null &&
                    c.MessagingServiceSid == "MGmessaging_service" &&
                    c.AuthToken == "new-token"));
            repo.Received().SaveChangesAsync();
        });
    }

    [Fact]
    public void SavingEdit_WithBlankAuthToken_PreservesExistingToken()
    {
        var config = Sample();
        var repo = RepoWith(config);
        Services.AddSingleton(repo);
        WireAuth();

        var cut = Render<SmsConfigurationPage>();
        cut.WaitForState(() => cut.Markup.Contains("ACcccccccccccccccccccccccccccccccc"));

        cut.FindAll("button").First(b => b.TextContent.Contains("Edit")).Click();
        // Leave the (blank) Auth Token input untouched, then save.
        cut.FindAll("button").First(b => b.TextContent.Contains("Save")).Click();

        cut.WaitForAssertion(() =>
        {
            repo.Received().UpdateSmsConfigurationAsync(
                Arg.Is<SmsConfiguration>(c =>
                    c.AuthToken == SecretToken &&
                    c.MessagingServiceSid == "MGtest_messaging_service"));
            repo.Received().SaveChangesAsync();
        });
    }

    [Fact]
    public void SavingEdit_WithNewAuthToken_OverwritesToken()
    {
        var config = Sample();
        var repo = RepoWith(config);
        Services.AddSingleton(repo);
        WireAuth();

        var cut = Render<SmsConfigurationPage>();
        cut.WaitForState(() => cut.Markup.Contains("ACcccccccccccccccccccccccccccccccc"));

        cut.FindAll("button").First(b => b.TextContent.Contains("Edit")).Click();
        cut.FindAll("input[type=password]").First().Change("rotated-token");
        cut.FindAll("button").First(b => b.TextContent.Contains("Save")).Click();

        cut.WaitForAssertion(() =>
        {
            repo.Received().UpdateSmsConfigurationAsync(
                Arg.Is<SmsConfiguration>(c => c.AuthToken == "rotated-token"));
            repo.Received().SaveChangesAsync();
        });
    }
}
