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

namespace SharepointToolbox.Tests.ViewModels;

/// <summary>
/// Unit tests for UserAccessAuditViewModel (Phase 7 Plan 08).
/// Verifies: AuditUsersAsync invocation, results population, summary properties,
/// tenant switch reset, global sites message, override guard, CanExport state.
/// </summary>
public class UserAccessAuditViewModelTests
{
    // ── Reset messenger between tests ─────────────────────────────────────────

    public UserAccessAuditViewModelTests()
    {
        WeakReferenceMessenger.Default.Reset();
    }

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

    private static UserAccessEntry MakeEntry(
        string userLogin = "alice@contoso.com",
        string siteUrl = "https://contoso.sharepoint.com",
        bool isHighPrivilege = false) =>
        new("Alice", userLogin, siteUrl, "Contoso", "List", "Docs",
            siteUrl + "/Docs", "Read", AccessType.Direct, "Direct Permissions",
            isHighPrivilege, false);

    private static GraphUserResult MakeUser(
        string display = "Alice Smith",
        string upn = "alice@contoso.com") =>
        new(display, upn, upn);

    /// <summary>Creates a ViewModel wired with mock services.</summary>
    private static (UserAccessAuditViewModel vm, Mock<IUserAccessAuditService> auditMock, Mock<IGraphUserSearchService> graphMock)
        CreateViewModel(IReadOnlyList<UserAccessEntry>? auditResult = null)
    {
        var mockAudit = new Mock<IUserAccessAuditService>();
        mockAudit
            .Setup(s => s.AuditUsersAsync(
                It.IsAny<ISessionManager>(),
                It.IsAny<TenantProfile>(),
                It.IsAny<IReadOnlyList<string>>(),
                It.IsAny<IReadOnlyList<SiteInfo>>(),
                It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(auditResult ?? Array.Empty<UserAccessEntry>());

        var mockGraph = new Mock<IGraphUserSearchService>();
        var mockSession = new Mock<ISessionManager>();

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

        // Set a default profile so RunOperationAsync doesn't early-return
        vm._currentProfile = new TenantProfile
        {
            Name = "Test",
            TenantUrl = "https://contoso.sharepoint.com",
            ClientId = "test-client-id"
        };

        return (vm, mockAudit, mockGraph);
    }

    // ── Test 1: RunOperation calls AuditUsersAsync ────────────────────────────

    [Fact]
    public async Task RunOperation_calls_AuditUsersAsync()
    {
        var (vm, auditMock, _) = CreateViewModel();

        vm.SelectedUsers.Add(MakeUser());
        WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage(
            new List<SiteInfo> { new("https://contoso.sharepoint.com", "Contoso") }.AsReadOnly()));

        await vm.TestRunOperationAsync(CancellationToken.None, new Progress<OperationProgress>());

        auditMock.Verify(
            s => s.AuditUsersAsync(
                It.IsAny<ISessionManager>(),
                It.IsAny<TenantProfile>(),
                It.IsAny<IReadOnlyList<string>>(),
                It.IsAny<IReadOnlyList<SiteInfo>>(),
                It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(),
                It.IsAny<CancellationToken>()),
            Times.Once);
    }

    // ── Test 2: RunOperation populates Results ────────────────────────────────

    [Fact]
    public async Task RunOperation_populates_results()
    {
        var entries = new List<UserAccessEntry>
        {
            MakeEntry(),
            MakeEntry(userLogin: "bob@contoso.com")
        };

        var (vm, _, _) = CreateViewModel(entries);

        vm.SelectedUsers.Add(MakeUser());
        WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage(
            new List<SiteInfo> { new("https://contoso.sharepoint.com", "Contoso") }.AsReadOnly()));

        await vm.TestRunOperationAsync(CancellationToken.None, new Progress<OperationProgress>());

        Assert.Equal(2, vm.Results.Count);
    }

    // ── Test 3: RunOperation updates summary properties ───────────────────────

    [Fact]
    public async Task RunOperation_updates_summary_properties()
    {
        var entries = new List<UserAccessEntry>
        {
            MakeEntry(userLogin: "alice@contoso.com", siteUrl: "https://contoso.sharepoint.com/sites/s1", isHighPrivilege: true),
            MakeEntry(userLogin: "alice@contoso.com", siteUrl: "https://contoso.sharepoint.com/sites/s2", isHighPrivilege: false),
            MakeEntry(userLogin: "bob@contoso.com",   siteUrl: "https://contoso.sharepoint.com/sites/s1", isHighPrivilege: true)
        };

        var (vm, _, _) = CreateViewModel(entries);

        vm.SelectedUsers.Add(MakeUser());
        WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage(
            new List<SiteInfo> { new("https://contoso.sharepoint.com", "Contoso") }.AsReadOnly()));

        await vm.TestRunOperationAsync(CancellationToken.None, new Progress<OperationProgress>());

        Assert.Equal(3, vm.TotalAccessCount);
        Assert.Equal(2, vm.SitesCount);
        Assert.Equal(2, vm.HighPrivilegeCount);
    }

    // ── Test 4: OnTenantSwitched resets state ─────────────────────────────────

    [Fact]
    public async Task OnTenantSwitched_resets_state()
    {
        var entries = new List<UserAccessEntry> { MakeEntry() };
        var (vm, _, _) = CreateViewModel(entries);

        // Populate state
        vm.SelectedUsers.Add(MakeUser());
        WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage(
            new List<SiteInfo> { new("https://contoso.sharepoint.com", "Contoso") }.AsReadOnly()));
        await vm.TestRunOperationAsync(CancellationToken.None, new Progress<OperationProgress>());
        Assert.NotEmpty(vm.Results);
        Assert.NotEmpty(vm.SelectedUsers);

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

        // Assert: state cleared
        Assert.Empty(vm.Results);
        Assert.Empty(vm.SelectedUsers);
        Assert.Empty(vm.FilterText);
    }

    // ── Test 5: RunOperation uses GlobalSites directly ─────────────────────

    [Fact]
    public async Task RunOperation_fails_gracefully_without_global_sites()
    {
        var (vm, auditMock, _) = CreateViewModel();
        vm.SelectedUsers.Add(MakeUser());
        // Do NOT send GlobalSitesChangedMessage — no sites selected

        await vm.TestRunOperationAsync(CancellationToken.None, new Progress<OperationProgress>());

        // Should not call audit service — early return with status message
        auditMock.Verify(
            s => s.AuditUsersAsync(
                It.IsAny<ISessionManager>(),
                It.IsAny<TenantProfile>(),
                It.IsAny<IReadOnlyList<string>>(),
                It.IsAny<IReadOnlyList<SiteInfo>>(),
                It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(),
                It.IsAny<CancellationToken>()),
            Times.Never);
    }

    // ── Test 7: CanExport false when no results ───────────────────────────────

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

        // Results is empty by default
        Assert.Empty(vm.Results);
        Assert.False(vm.ExportCsvCommand.CanExecute(null));
        Assert.False(vm.ExportHtmlCommand.CanExecute(null));
    }

    // ── Test 8: CanExport true when has results ───────────────────────────────

    [Fact]
    public async Task CanExport_true_when_has_results()
    {
        var entries = new List<UserAccessEntry> { MakeEntry() };
        var (vm, _, _) = CreateViewModel(entries);

        vm.SelectedUsers.Add(MakeUser());
        WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage(
            new List<SiteInfo> { new("https://contoso.sharepoint.com", "Contoso") }.AsReadOnly()));

        await vm.TestRunOperationAsync(CancellationToken.None, new Progress<OperationProgress>());

        Assert.NotEmpty(vm.Results);
        Assert.True(vm.ExportCsvCommand.CanExecute(null));
        Assert.True(vm.ExportHtmlCommand.CanExecute(null));
    }

    // ── Test 9: Debounced search triggers SearchUsersAsync ───────────────────

    [Fact]
    public async Task SearchQuery_debounced_calls_SearchUsersAsync()
    {
        var graphResults = new List<GraphUserResult>
        {
            new("Alice Smith", "alice@contoso.com", "alice@contoso.com")
        };

        var (vm, _, graphMock) = CreateViewModel();

        graphMock
            .Setup(s => s.SearchUsersAsync(
                It.IsAny<string>(),
                It.Is<string>(q => q == "Ali"),
                It.IsAny<int>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(graphResults);

        // Set a TenantProfile so _currentProfile is non-null
        var profile = new TenantProfile
        {
            Name = "Test",
            TenantUrl = "https://contoso.sharepoint.com",
            ClientId = "test-client-id"
        };
        WeakReferenceMessenger.Default.Send(new TenantSwitchedMessage(profile));

        // Act: set SearchQuery which triggers OnSearchQueryChanged → DebounceSearchAsync
        vm.SearchQuery = "Ali";

        // Wait longer than 300ms debounce to allow async fire-and-forget to complete
        await Task.Delay(600);

        // Assert: SearchUsersAsync was called with the query
        graphMock.Verify(
            s => s.SearchUsersAsync(
                It.IsAny<string>(),
                "Ali",
                It.IsAny<int>(),
                It.IsAny<CancellationToken>()),
            Times.Once);
    }
}
