using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SharePoint.Client;
using Moq;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Services;

namespace SharepointToolbox.Tests.Services;

/// <summary>
/// Unit tests for UserAccessAuditService (Phase 7 Plan 08).
/// Verifies: user filtering, claim format matching, access type classification,
/// high-privilege detection, external user flagging, semicolon splitting, multi-site scanning.
/// </summary>
public class UserAccessAuditServiceTests
{
    // ── Helper factory for PermissionEntry ────────────────────────────────────

    private static PermissionEntry MakeEntry(
        string users = "Alice",
        string logins = "alice@contoso.com",
        string levels = "Read",
        string grantedThrough = "Direct Permissions",
        bool hasUnique = true,
        string objectType = "List",
        string title = "Docs",
        string url = "https://contoso.sharepoint.com/Docs",
        string principalType = "User") =>
        new(objectType, title, url, hasUnique, users, logins, levels, grantedThrough, principalType);

    private static SiteInfo MakeSite(string url = "https://contoso.sharepoint.com", string title = "Contoso") =>
        new(url, title);

    // ── Helper: create a configured service + mocks ───────────────────────────

    private static (UserAccessAuditService svc, Mock<IPermissionsService> permSvc, Mock<ISessionManager> sessionMgr)
        CreateService(IReadOnlyList<PermissionEntry> entries)
    {
        var mockPerm = new Mock<IPermissionsService>();
        mockPerm
            .Setup(p => p.ScanSiteAsync(
                It.IsAny<ClientContext>(),
                It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(entries);

        var mockSession = new Mock<ISessionManager>();
        mockSession
            .Setup(s => s.GetOrCreateContextAsync(It.IsAny<TenantProfile>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync((ClientContext)null!);

        var svc = new UserAccessAuditService(mockPerm.Object);
        return (svc, mockPerm, mockSession);
    }

    private static ScanOptions DefaultOptions => new(
        IncludeInherited: false,
        ScanFolders: false,
        FolderDepth: 1,
        IncludeSubsites: false);

    private static TenantProfile DefaultProfile => new()
    {
        Name = "Test",
        TenantUrl = "https://contoso.sharepoint.com",
        ClientId = "test-client-id"
    };

    // ── Test 1: Filter by target user login ───────────────────────────────────

    [Fact]
    public async Task Filters_by_target_user_login()
    {
        var entries = new List<PermissionEntry>
        {
            MakeEntry(users: "Alice", logins: "alice@contoso.com"),
            MakeEntry(users: "Bob",   logins: "bob@contoso.com"),
            MakeEntry(users: "Carol", logins: "carol@contoso.com")
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            new[] { "alice@contoso.com" },
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        Assert.All(result, r => Assert.Equal("alice@contoso.com", r.UserLogin));
        Assert.DoesNotContain(result, r => r.UserLogin == "bob@contoso.com");
        Assert.DoesNotContain(result, r => r.UserLogin == "carol@contoso.com");
    }

    // ── Test 2: Claim format matching ─────────────────────────────────────────

    [Fact]
    public async Task Matches_user_by_email_in_claim_format()
    {
        var claimLogin = "i:0#.f|membership|alice@contoso.com";
        var entries = new List<PermissionEntry>
        {
            MakeEntry(users: "Alice", logins: claimLogin)
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            new[] { "alice@contoso.com" },
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        Assert.Single(result);
        // Claims prefix is stripped: "i:0#.f|membership|alice@contoso.com" -> "alice@contoso.com"
        Assert.Equal("alice@contoso.com", result[0].UserLogin);
    }

    // ── Test 3: Classifies Direct access ─────────────────────────────────────

    [Fact]
    public async Task Classifies_direct_access()
    {
        var entries = new List<PermissionEntry>
        {
            MakeEntry(hasUnique: true, grantedThrough: "Direct Permissions")
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            new[] { "alice@contoso.com" },
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        Assert.Single(result);
        Assert.Equal(AccessType.Direct, result[0].AccessType);
    }

    // ── Test 4: Classifies Group access ──────────────────────────────────────

    [Fact]
    public async Task Classifies_group_access()
    {
        var entries = new List<PermissionEntry>
        {
            MakeEntry(hasUnique: true, grantedThrough: "SharePoint Group: Members")
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            new[] { "alice@contoso.com" },
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        Assert.Single(result);
        Assert.Equal(AccessType.Group, result[0].AccessType);
    }

    // ── Test 5: Classifies Inherited access ──────────────────────────────────

    [Fact]
    public async Task Classifies_inherited_access()
    {
        var entries = new List<PermissionEntry>
        {
            MakeEntry(hasUnique: false, grantedThrough: "Direct Permissions")
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            new[] { "alice@contoso.com" },
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        Assert.Single(result);
        Assert.Equal(AccessType.Inherited, result[0].AccessType);
    }

    // ── Test 6: Detects high privilege (Full Control) ─────────────────────────

    [Fact]
    public async Task Detects_high_privilege()
    {
        var entries = new List<PermissionEntry>
        {
            MakeEntry(levels: "Full Control")
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            new[] { "alice@contoso.com" },
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        Assert.Single(result);
        Assert.True(result[0].IsHighPrivilege);
    }

    // ── Test 7: Detects high privilege (Site Collection Administrator) ─────────

    [Fact]
    public async Task Detects_high_privilege_site_admin()
    {
        var entries = new List<PermissionEntry>
        {
            MakeEntry(levels: "Site Collection Administrator")
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            new[] { "alice@contoso.com" },
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        Assert.Single(result);
        Assert.True(result[0].IsHighPrivilege);
    }

    // ── Test 8: Flags external user ───────────────────────────────────────────

    [Fact]
    public async Task Flags_external_user()
    {
        var extLogin = "alice_fabrikam.com#EXT#@contoso.onmicrosoft.com";
        var entries = new List<PermissionEntry>
        {
            MakeEntry(users: "Alice (External)", logins: extLogin)
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            new[] { extLogin },
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        Assert.Single(result);
        Assert.True(result[0].IsExternalUser);
    }

    // ── Test 9: Splits semicolon-joined users ─────────────────────────────────

    [Fact]
    public async Task Splits_semicolon_users()
    {
        var entries = new List<PermissionEntry>
        {
            MakeEntry(users: "Alice;Bob", logins: "alice@x.com;bob@x.com", levels: "Read")
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            new[] { "alice@x.com", "bob@x.com" },
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        // 2 users × 1 permission level = 2 rows
        Assert.Equal(2, result.Count);
        Assert.Contains(result, r => r.UserLogin == "alice@x.com");
        Assert.Contains(result, r => r.UserLogin == "bob@x.com");
    }

    // ── Test 10: Splits semicolon permission levels ───────────────────────────

    [Fact]
    public async Task Splits_semicolon_permission_levels()
    {
        var entries = new List<PermissionEntry>
        {
            MakeEntry(users: "Alice", logins: "alice@contoso.com", levels: "Read;Contribute")
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            new[] { "alice@contoso.com" },
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        // 1 user × 2 permission levels = 2 rows
        Assert.Equal(2, result.Count);
        Assert.Contains(result, r => r.PermissionLevel == "Read");
        Assert.Contains(result, r => r.PermissionLevel == "Contribute");
    }

    // ── Test 11: Empty targets returns empty ──────────────────────────────────

    [Fact]
    public async Task Empty_targets_returns_empty()
    {
        var entries = new List<PermissionEntry>
        {
            MakeEntry()
        };

        var (svc, _, session) = CreateService(entries);

        var result = await svc.AuditUsersAsync(
            session.Object,
            DefaultProfile,
            Array.Empty<string>(),
            new[] { MakeSite() },
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        Assert.Empty(result);
    }

    // ── Test 12: Scans multiple sites ─────────────────────────────────────────

    [Fact]
    public async Task Scans_multiple_sites()
    {
        var entries = new List<PermissionEntry>
        {
            MakeEntry(users: "Alice", logins: "alice@contoso.com")
        };

        var mockPerm = new Mock<IPermissionsService>();
        mockPerm
            .Setup(p => p.ScanSiteAsync(
                It.IsAny<ClientContext>(),
                It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(entries);

        var mockSession = new Mock<ISessionManager>();
        mockSession
            .Setup(s => s.GetOrCreateContextAsync(It.IsAny<TenantProfile>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync((ClientContext)null!);

        var svc = new UserAccessAuditService(mockPerm.Object);

        var sites = new List<SiteInfo>
        {
            new("https://contoso.sharepoint.com/sites/site1", "Site 1"),
            new("https://contoso.sharepoint.com/sites/site2", "Site 2")
        };

        var result = await svc.AuditUsersAsync(
            mockSession.Object,
            DefaultProfile,
            new[] { "alice@contoso.com" },
            sites,
            DefaultOptions,
            new Progress<OperationProgress>(),
            CancellationToken.None);

        // Entries from both sites should appear
        Assert.Equal(2, result.Count);
        Assert.Contains(result, r => r.SiteUrl == "https://contoso.sharepoint.com/sites/site1");
        Assert.Contains(result, r => r.SiteUrl == "https://contoso.sharepoint.com/sites/site2");

        // ScanSiteAsync was called exactly twice (once per site)
        mockPerm.Verify(
            p => p.ScanSiteAsync(
                It.IsAny<ClientContext>(),
                It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(),
                It.IsAny<CancellationToken>()),
            Times.Exactly(2));
    }
}
