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

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

/// <summary>
/// bUnit tests for the adapter-gated Type selector + per-type recipient input on the
/// Notification List create/edit form (SMS Notifications, S7).
/// </summary>
public class NotificationListFormTests : BunitContext
{
    private readonly INotificationRepository _repo = Substitute.For<INotificationRepository>();

    public NotificationListFormTests()
    {
        Services.AddSingleton(_repo);
        // The Type selector derives its options from INotificationChannelCatalog
        // (S7). Register a substitute exposing both channels so the selector renders
        // Email + SMS without pulling the real adapter graph into the test.
        var catalog = Substitute.For<INotificationChannelCatalog>();
        catalog.SupportedChannels.Returns(
            new[] { NotificationType.Email, NotificationType.Sms });
        Services.AddSingleton(catalog);
        AddTestAuth();
    }

    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();
    }

    [Fact]
    public void TypeSelector_RendersRegisteredChannels()
    {
        var cut = Render<NotificationListForm>();

        cut.WaitForAssertion(() =>
        {
            var optionTexts = cut.FindAll("select.form-select option")
                .Select(o => o.TextContent.Trim())
                .ToList();
            Assert.Contains("Email", optionTexts);
            Assert.Contains("SMS", optionTexts);
        });
    }

    [Fact]
    public void TypeSelector_IsEnabled_OnCreate()
    {
        var cut = Render<NotificationListForm>();

        cut.WaitForAssertion(() =>
        {
            var select = cut.Find("select.form-select");
            Assert.False(select.HasAttribute("disabled"));
        });
    }

    [Fact]
    public void SelectingEmail_ShowsEmailInput_NotPhone()
    {
        // Edit mode renders the recipient form (recipients only show once a list exists).
        _repo.GetNotificationListByIdAsync(1)
            .Returns(Task.FromResult<NotificationList?>(new NotificationList("Ops") { Id = 1, Type = NotificationType.Email }));
        _repo.GetRecipientsByListIdAsync(1)
            .Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(new List<NotificationRecipient>()));

        var cut = Render<NotificationListForm>(p => p.Add(c => c.Id, 1));

        cut.WaitForAssertion(() =>
        {
            Assert.NotNull(cut.Find("input[type=email]"));
            Assert.Empty(cut.FindAll("input[type=tel]"));
            // The recipients table column header reflects the list type.
            Assert.Contains("Email", cut.FindAll("th").Select(t => t.TextContent.Trim()));
        });
    }

    [Fact]
    public void SelectingSms_ShowsPhoneInput_NotEmail()
    {
        _repo.GetNotificationListByIdAsync(2)
            .Returns(Task.FromResult<NotificationList?>(new NotificationList("On-Call") { Id = 2, Type = NotificationType.Sms }));
        _repo.GetRecipientsByListIdAsync(2)
            .Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(new List<NotificationRecipient>()));

        var cut = Render<NotificationListForm>(p => p.Add(c => c.Id, 2));

        cut.WaitForAssertion(() =>
        {
            Assert.NotNull(cut.Find("input[type=tel]"));
            Assert.Empty(cut.FindAll("input[type=email]"));
            Assert.Contains("Phone", cut.FindAll("th").Select(t => t.TextContent.Trim()));
        });
    }

    [Fact]
    public void TypeSelector_IsDisabled_OnEdit()
    {
        _repo.GetNotificationListByIdAsync(3)
            .Returns(Task.FromResult<NotificationList?>(new NotificationList("Ops") { Id = 3, Type = NotificationType.Email }));
        _repo.GetRecipientsByListIdAsync(3)
            .Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(new List<NotificationRecipient>()));

        var cut = Render<NotificationListForm>(p => p.Add(c => c.Id, 3));

        cut.WaitForAssertion(() =>
        {
            var select = cut.Find("select.form-select");
            Assert.True(select.HasAttribute("disabled"));
        });
    }

    [Fact]
    public void CreatingSmsList_PersistsTypeSms()
    {
        NotificationList? captured = null;
        _repo.AddNotificationListAsync(Arg.Do<NotificationList>(l => captured = l))
            .Returns(Task.CompletedTask);

        var cut = Render<NotificationListForm>();

        cut.Find("input[type=text]").Change("On-Call");
        // Switch the Type selector to SMS, then save.
        cut.Find("select.form-select").Change(NotificationType.Sms.ToString());
        cut.FindAll("button").First(b => b.TextContent.Trim() == "Save").Click();

        cut.WaitForAssertion(() =>
        {
            Assert.NotNull(captured);
            Assert.Equal(NotificationType.Sms, captured!.Type);
            Assert.Equal("On-Call", captured.Name);
        });
    }

    [Fact]
    public void AddingSmsRecipient_CreatesForSmsRecipient_WithPhoneSet()
    {
        NotificationRecipient? captured = null;
        _repo.GetNotificationListByIdAsync(4)
            .Returns(Task.FromResult<NotificationList?>(new NotificationList("On-Call") { Id = 4, Type = NotificationType.Sms }));
        _repo.GetRecipientsByListIdAsync(4)
            .Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(new List<NotificationRecipient>()));
        _repo.AddRecipientAsync(Arg.Do<NotificationRecipient>(r => captured = r))
            .Returns(Task.CompletedTask);

        var cut = Render<NotificationListForm>(p => p.Add(c => c.Id, 4));

        cut.WaitForState(() => cut.FindAll("input[type=tel]").Count > 0);

        // The first text input is the list Name; the recipient Name input is the last.
        cut.FindAll("input[type=text]").Last().Change("Jane");
        cut.Find("input[type=tel]").Change("+15551234567");
        cut.FindAll("button").First(b => b.TextContent.Trim() == "Add").Click();

        cut.WaitForAssertion(() =>
        {
            Assert.NotNull(captured);
            Assert.Equal("+15551234567", captured!.PhoneNumber);
            Assert.Null(captured.EmailAddress);
            Assert.Equal(4, captured.NotificationListId);
        });
    }

    [Fact]
    public void AddingSmsRecipient_RejectsInvalidPhone()
    {
        _repo.GetNotificationListByIdAsync(5)
            .Returns(Task.FromResult<NotificationList?>(new NotificationList("On-Call") { Id = 5, Type = NotificationType.Sms }));
        _repo.GetRecipientsByListIdAsync(5)
            .Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(new List<NotificationRecipient>()));

        var cut = Render<NotificationListForm>(p => p.Add(c => c.Id, 5));

        cut.WaitForState(() => cut.FindAll("input[type=tel]").Count > 0);

        // The first text input is the list Name; the recipient Name input is the last.
        cut.FindAll("input[type=text]").Last().Change("Jane");
        cut.Find("input[type=tel]").Change("not-a-number");
        cut.FindAll("button").First(b => b.TextContent.Trim() == "Add").Click();

        cut.WaitForAssertion(() =>
        {
            Assert.Contains("valid phone number", cut.Markup);
            _repo.DidNotReceive().AddRecipientAsync(Arg.Any<NotificationRecipient>());
        });
    }
}
