using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Services;
using SharepointToolbox.ViewModels;
using SharepointToolbox.ViewModels.Tabs;

namespace SharepointToolbox.Tests.ViewModels;

/// <summary>
/// Unit tests for directory browse mode in UserAccessAuditViewModel (Phase 13 Plan 02).
/// Verifies: directory load, progress, cancellation, member/guest filter, text filter,
/// sorting, tenant switch reset, and no regression on search mode.
/// </summary>
[Trait("Category", "Unit")]
public class UserAccessAuditViewModelDirectoryTests
{
    public UserAccessAuditViewModelDirectoryTests()
    {
        WeakReferenceMessenger.Default.Reset();
    }

    // ── Helper factories ──────────────────────────────────────────────────────

    private static GraphDirectoryUser MakeMember(string name = "Alice", string dept = "IT", string jobTitle = "Engineer") =>
        new(name, $"{name.ToLower().Replace(" ", "")}@contoso.com", null, dept, jobTitle, "Member");

    private static GraphDirectoryUser MakeGuest(string name = "Bob External") =>
        new(name, $"{name.ToLower().Replace(" ", "")}@external.com", null, null, null, "Guest");

    private static (UserAccessAuditViewModel vm, Mock<IGraphUserDirectoryService> dirMock, Mock<IUserAccessAuditService> auditMock)
        CreateViewModel(IReadOnlyList<GraphDirectoryUser>? directoryResult = null)
    {
        var mockAudit = new Mock<IUserAccessAuditService>();
        var mockGraph = new Mock<IGraphUserSearchService>();
        var mockSession = new Mock<ISessionManager>();
        var mockDir = new Mock<IGraphUserDirectoryService>();
        mockDir.Setup(s => s.GetUsersAsync(
                It.IsAny<string>(),
                It.IsAny<bool>(),
                It.IsAny<IProgress<int>>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(directoryResult ?? Array.Empty<GraphDirectoryUser>());

        var vm = new UserAccessAuditViewModel(
            mockAudit.Object,
            mockGraph.Object,
            mockSession.Object,
            NullLogger<FeatureViewModelBase>.Instance,
            graphUserDirectoryService: mockDir.Object);

        vm._currentProfile = new TenantProfile
        {
            Name = "Test",
            TenantUrl = "https://contoso.sharepoint.com",
            ClientId = "test-client-id"
        };

        return (vm, mockDir, mockAudit);
    }

    // ── Test 1: IsBrowseMode defaults to false ───────────────────────────────

    [Fact]
    public void IsBrowseMode_defaults_to_false()
    {
        var (vm, _, _) = CreateViewModel();
        Assert.False(vm.IsBrowseMode);
    }

    // ── Test 2: DirectoryUsers is empty by default ───────────────────────────

    [Fact]
    public void DirectoryUsers_empty_by_default()
    {
        var (vm, _, _) = CreateViewModel();
        Assert.Empty(vm.DirectoryUsers);
    }

    // ── Test 3: Commands are not null ─────────────────────────────────────────

    [Fact]
    public void LoadDirectoryCommand_and_CancelDirectoryLoadCommand_not_null()
    {
        var (vm, _, _) = CreateViewModel();
        Assert.NotNull(vm.LoadDirectoryCommand);
        Assert.NotNull(vm.CancelDirectoryLoadCommand);
    }

    // ── Test 4: LoadDirectoryAsync populates DirectoryUsers ──────────────────

    [Fact]
    public async Task LoadDirectoryAsync_populates_DirectoryUsers()
    {
        var users = new List<GraphDirectoryUser> { MakeMember("Alice"), MakeMember("Charlie") };
        var (vm, _, _) = CreateViewModel(users);

        await vm.TestLoadDirectoryAsync();

        Assert.Equal(2, vm.DirectoryUsers.Count);
        Assert.Contains(vm.DirectoryUsers, u => u.DisplayName == "Alice");
        Assert.Contains(vm.DirectoryUsers, u => u.DisplayName == "Charlie");
    }

    // ── Test 5: LoadDirectoryAsync reports progress via DirectoryLoadStatus ──

    [Fact]
    public async Task LoadDirectoryAsync_sets_DirectoryLoadStatus_on_completion()
    {
        var users = new List<GraphDirectoryUser> { MakeMember("Alice") };
        var (vm, _, _) = CreateViewModel(users);

        await vm.TestLoadDirectoryAsync();

        Assert.Equal("1 users loaded", vm.DirectoryLoadStatus);
    }

    // ── Test 6: LoadDirectoryAsync with no profile sets StatusMessage ─────────

    [Fact]
    public async Task LoadDirectoryAsync_with_no_profile_sets_StatusMessage()
    {
        var (vm, _, _) = CreateViewModel();
        vm._currentProfile = null;

        await vm.TestLoadDirectoryAsync();

        Assert.Equal("No tenant profile selected. Please connect first.", vm.StatusMessage);
        Assert.Empty(vm.DirectoryUsers);
    }

    // ── Test 7: CancelDirectoryLoadCommand cancels in-flight load ────────────

    [Fact]
    public async Task CancelDirectoryLoad_cancels_inflight_load()
    {
        var tcs = new TaskCompletionSource<IReadOnlyList<GraphDirectoryUser>>();
        var mockDir = new Mock<IGraphUserDirectoryService>();
        mockDir.Setup(s => s.GetUsersAsync(
                It.IsAny<string>(),
                It.IsAny<bool>(),
                It.IsAny<IProgress<int>>(),
                It.IsAny<CancellationToken>()))
            .Returns<string, bool, IProgress<int>?, CancellationToken>((_, _, _, ct) =>
            {
                var localTcs = new TaskCompletionSource<IReadOnlyList<GraphDirectoryUser>>();
                ct.Register(() => localTcs.TrySetCanceled(ct));
                return localTcs.Task;
            });

        var vm = new UserAccessAuditViewModel(
            new Mock<IUserAccessAuditService>().Object,
            new Mock<IGraphUserSearchService>().Object,
            new Mock<ISessionManager>().Object,
            NullLogger<FeatureViewModelBase>.Instance,
            graphUserDirectoryService: mockDir.Object);
        vm._currentProfile = new TenantProfile
        {
            Name = "Test",
            TenantUrl = "https://contoso.sharepoint.com",
            ClientId = "test-client-id"
        };

        // Start load (will block on the mock)
        var loadTask = vm.TestLoadDirectoryAsync();

        // Cancel
        vm.CancelDirectoryLoadCommand.Execute(null);

        await loadTask;

        Assert.Equal("Load cancelled.", vm.DirectoryLoadStatus);
        Assert.False(vm.IsLoadingDirectory);
    }

    // ── Test 8: IncludeGuests=false filters out Guest users ──────────────────

    [Fact]
    public void IncludeGuests_false_filters_out_guest_users()
    {
        var (vm, _, _) = CreateViewModel();
        vm.DirectoryUsers.Add(MakeMember("Alice"));
        vm.DirectoryUsers.Add(MakeGuest("Bob External"));
        vm.DirectoryUsers.Add(MakeMember("Charlie"));

        vm.IncludeGuests = false;

        var visible = vm.DirectoryUsersView.Cast<GraphDirectoryUser>().ToList();
        Assert.Equal(2, visible.Count);
        Assert.All(visible, u => Assert.Equal("Member", u.UserType));
    }

    // ── Test 9: IncludeGuests=true shows all users ───────────────────────────

    [Fact]
    public void IncludeGuests_true_shows_all_users()
    {
        var (vm, _, _) = CreateViewModel();
        vm.DirectoryUsers.Add(MakeMember("Alice"));
        vm.DirectoryUsers.Add(MakeGuest("Bob External"));

        vm.IncludeGuests = true;

        var visible = vm.DirectoryUsersView.Cast<GraphDirectoryUser>().ToList();
        Assert.Equal(2, visible.Count);
    }

    // ── Test 10: DirectoryFilterText filters by DisplayName ──────────────────

    [Fact]
    public void DirectoryFilterText_filters_by_DisplayName()
    {
        var (vm, _, _) = CreateViewModel();
        vm.DirectoryUsers.Add(MakeMember("Alice"));
        vm.DirectoryUsers.Add(MakeMember("Charlie"));
        vm.IncludeGuests = true;

        vm.DirectoryFilterText = "Ali";

        var visible = vm.DirectoryUsersView.Cast<GraphDirectoryUser>().ToList();
        Assert.Single(visible);
        Assert.Equal("Alice", visible[0].DisplayName);
    }

    // ── Test 11: DirectoryFilterText filters by Department ───────────────────

    [Fact]
    public void DirectoryFilterText_filters_by_Department()
    {
        var (vm, _, _) = CreateViewModel();
        vm.DirectoryUsers.Add(MakeMember("Alice", dept: "Engineering"));
        vm.DirectoryUsers.Add(MakeMember("Charlie", dept: "Marketing"));
        vm.IncludeGuests = true;

        vm.DirectoryFilterText = "Market";

        var visible = vm.DirectoryUsersView.Cast<GraphDirectoryUser>().ToList();
        Assert.Single(visible);
        Assert.Equal("Charlie", visible[0].DisplayName);
    }

    // ── Test 12: DirectoryUsersView default sort is DisplayName ascending ────

    [Fact]
    public void DirectoryUsersView_sorted_by_DisplayName_ascending()
    {
        var (vm, _, _) = CreateViewModel();
        vm.DirectoryUsers.Add(MakeMember("Charlie"));
        vm.DirectoryUsers.Add(MakeMember("Alice"));
        vm.DirectoryUsers.Add(MakeMember("Bob"));
        vm.IncludeGuests = true;

        var visible = vm.DirectoryUsersView.Cast<GraphDirectoryUser>().ToList();
        Assert.Equal("Alice", visible[0].DisplayName);
        Assert.Equal("Bob", visible[1].DisplayName);
        Assert.Equal("Charlie", visible[2].DisplayName);
    }

    // ── Test 13: OnTenantSwitched clears directory state ─────────────────────

    [Fact]
    public async Task OnTenantSwitched_clears_directory_state()
    {
        var users = new List<GraphDirectoryUser> { MakeMember("Alice") };
        var (vm, _, _) = CreateViewModel(users);

        // Load directory
        await vm.TestLoadDirectoryAsync();
        Assert.NotEmpty(vm.DirectoryUsers);
        vm.IsBrowseMode = true;
        vm.DirectoryFilterText = "test";
        vm.IncludeGuests = true;

        // Act: switch tenant
        var newProfile = new TenantProfile
        {
            Name = "NewTenant",
            TenantUrl = "https://newtenant.sharepoint.com",
            ClientId = "new-client-id"
        };
        WeakReferenceMessenger.Default.Send(new Core.Messages.TenantSwitchedMessage(newProfile));

        // Assert
        Assert.Empty(vm.DirectoryUsers);
        Assert.False(vm.IsBrowseMode);
        Assert.Empty(vm.DirectoryFilterText);
        Assert.Empty(vm.DirectoryLoadStatus);
        Assert.False(vm.IsLoadingDirectory);
        Assert.False(vm.IncludeGuests);
    }

    // ── Test 14: DirectoryUserCount reflects filtered count ───────────────────

    [Fact]
    public void DirectoryUserCount_reflects_filtered_count()
    {
        var (vm, _, _) = CreateViewModel();
        vm.DirectoryUsers.Add(MakeMember("Alice"));
        vm.DirectoryUsers.Add(MakeGuest("Bob External"));
        vm.DirectoryUsers.Add(MakeMember("Charlie"));

        // With guests hidden (default IncludeGuests=false)
        vm.IncludeGuests = false;
        Assert.Equal(2, vm.DirectoryUserCount);

        // With guests shown
        vm.IncludeGuests = true;
        Assert.Equal(3, vm.DirectoryUserCount);

        // With text filter
        vm.DirectoryFilterText = "Ali";
        Assert.Equal(1, vm.DirectoryUserCount);
    }

    // ── Test 15: Search mode still works (no regression) ─────────────────────

    [Fact]
    public void Search_mode_SelectedUsers_still_works()
    {
        var (vm, _, _) = CreateViewModel();

        // Search mode properties should be functional
        Assert.Empty(vm.SelectedUsers);
        vm.SelectedUsers.Add(new GraphUserResult("Alice Smith", "alice@contoso.com", "alice@contoso.com"));
        Assert.Single(vm.SelectedUsers);
        Assert.Equal("1 user(s) selected", vm.SelectedUsersLabel);
    }

    // ── Test 16: DirectoryFilterText filters by JobTitle ─────────────────────

    [Fact]
    public void DirectoryFilterText_filters_by_JobTitle()
    {
        var (vm, _, _) = CreateViewModel();
        vm.DirectoryUsers.Add(MakeMember("Alice", jobTitle: "Senior Developer"));
        vm.DirectoryUsers.Add(MakeMember("Charlie", jobTitle: "Product Manager"));
        vm.IncludeGuests = true;

        vm.DirectoryFilterText = "Developer";

        var visible = vm.DirectoryUsersView.Cast<GraphDirectoryUser>().ToList();
        Assert.Single(visible);
        Assert.Equal("Alice", visible[0].DisplayName);
    }

    // ── Test 17: SelectDirectoryUserCommand adds user to SelectedUsers ──────

    [Fact]
    public void SelectDirectoryUserCommand_adds_user_to_SelectedUsers()
    {
        var (vm, _, _) = CreateViewModel();
        var dirUser = MakeMember("Alice");

        vm.SelectDirectoryUserCommand.Execute(dirUser);

        Assert.Single(vm.SelectedUsers);
        Assert.Equal("Alice", vm.SelectedUsers[0].DisplayName);
        Assert.Equal("alice@contoso.com", vm.SelectedUsers[0].UserPrincipalName);
    }

    // ── Test 18: SelectDirectoryUserCommand skips duplicates ─────────────────

    [Fact]
    public void SelectDirectoryUserCommand_skips_duplicates()
    {
        var (vm, _, _) = CreateViewModel();
        var dirUser = MakeMember("Alice");

        vm.SelectDirectoryUserCommand.Execute(dirUser);
        vm.SelectDirectoryUserCommand.Execute(dirUser);

        Assert.Single(vm.SelectedUsers);
    }

    // ── Test 19: SelectDirectoryUserCommand with null does nothing ───────────

    [Fact]
    public void SelectDirectoryUserCommand_with_null_does_nothing()
    {
        var (vm, _, _) = CreateViewModel();

        vm.SelectDirectoryUserCommand.Execute(null);

        Assert.Empty(vm.SelectedUsers);
    }

    // ── Test 20: After SelectDirectoryUser, user can be audited ──────────────

    [Fact]
    public void SelectDirectoryUser_adds_auditable_user_to_SelectedUsers()
    {
        var (vm, _, _) = CreateViewModel();
        var dirUser = MakeMember("Alice");

        vm.SelectDirectoryUserCommand.Execute(dirUser);

        Assert.True(vm.SelectedUsers.Count > 0);
        Assert.Equal("alice@contoso.com", vm.SelectedUsers[0].UserPrincipalName);
    }
}
