using SharepointToolbox.Services;

namespace SharepointToolbox.Tests.Services;

/// <summary>
/// Unit tests for <see cref="SharePointGroupResolver"/> (Phase 17 Plan 01).
///
/// Testing strategy:
///   SharePointGroupResolver wraps CSOM (ClientContext) and Microsoft Graph SDK.
///   Both require live infrastructure that cannot be mocked without heavy ceremony.
///
///   We test what IS unit-testable without live infrastructure:
///     1. IsAadGroup      — static helper: login prefix pattern detection
///     2. ExtractAadGroupId — static helper: GUID extraction from AAD group login
///     3. StripClaims     — static helper: UPN extraction after last pipe
///     4. ResolveGroupsAsync with empty list — returns empty dict (no CSOM calls made)
///
///   Integration tests requiring live tenant / CSOM context are skip-marked.
/// </summary>
[Trait("Category", "Unit")]
public class SharePointGroupResolverTests
{
    // ── IsAadGroup ─────────────────────────────────────────────────────────────

    [Fact]
    public void IsAadGroup_AadGroupLogin_ReturnsTrue()
    {
        var login = "c:0t.c|tenant|aaaabbbb-cccc-dddd-eeee-ffffgggghhhh";
        Assert.True(SharePointGroupResolver.IsAadGroup(login));
    }

    [Fact]
    public void IsAadGroup_RegularUserLogin_ReturnsFalse()
    {
        var login = "i:0#.f|membership|user@contoso.com";
        Assert.False(SharePointGroupResolver.IsAadGroup(login));
    }

    [Fact]
    public void IsAadGroup_SecurityGroupLogin_ReturnsFalse()
    {
        var login = "c:0(.s|true";
        Assert.False(SharePointGroupResolver.IsAadGroup(login));
    }

    [Fact]
    public void IsAadGroup_EmptyString_ReturnsFalse()
    {
        Assert.False(SharePointGroupResolver.IsAadGroup(string.Empty));
    }

    [Fact]
    public void IsAadGroup_CaseInsensitive_ReturnsTrue()
    {
        // Prefix check should be case-insensitive per OrdinalIgnoreCase
        var login = "C:0T.C|TENANT|aaaabbbb-cccc-dddd-eeee-ffffgggghhhh";
        Assert.True(SharePointGroupResolver.IsAadGroup(login));
    }

    // ── ExtractAadGroupId ──────────────────────────────────────────────────────

    [Fact]
    public void ExtractAadGroupId_ValidAadLogin_ExtractsGuid()
    {
        var login = "c:0t.c|tenant|aaaabbbb-cccc-dddd-eeee-ffffgggghhhh";
        var result = SharePointGroupResolver.ExtractAadGroupId(login);
        Assert.Equal("aaaabbbb-cccc-dddd-eeee-ffffgggghhhh", result);
    }

    [Fact]
    public void ExtractAadGroupId_SingleSegment_ReturnsFullString()
    {
        // Edge: no pipe — LastIndexOf returns -1 so [(-1+1)..] = [0..] = whole string
        var login = "nopipe";
        var result = SharePointGroupResolver.ExtractAadGroupId(login);
        Assert.Equal("nopipe", result);
    }

    // ── StripClaims ────────────────────────────────────────────────────────────

    [Fact]
    public void StripClaims_MembershipLogin_ReturnsUpn()
    {
        var login = "i:0#.f|membership|user@contoso.com";
        var result = SharePointGroupResolver.StripClaims(login);
        Assert.Equal("user@contoso.com", result);
    }

    [Fact]
    public void StripClaims_NoClaimsPrefix_ReturnsFullString()
    {
        var login = "user@contoso.com";
        var result = SharePointGroupResolver.StripClaims(login);
        Assert.Equal("user@contoso.com", result);
    }

    [Fact]
    public void StripClaims_MultiPipeLogin_ReturnsAfterLastPipe()
    {
        var login = "c:0t.c|tenant|some-guid-here";
        var result = SharePointGroupResolver.StripClaims(login);
        Assert.Equal("some-guid-here", result);
    }

    // ── ResolveGroupsAsync — empty input ──────────────────────────────────────

    [Fact]
    public async Task ResolveGroupsAsync_EmptyGroupNames_ReturnsEmptyDict()
    {
        // Arrange: create resolver without real dependencies — empty list triggers early return
        // No CSOM ClientContext or GraphClientFactory is called for empty input
        // We pass null! for the factory since it must not be invoked for an empty list
        var resolver = new SharePointGroupResolver(null!);

        // Act
        var result = await resolver.ResolveGroupsAsync(
            ctx: null!,
            clientId: "ignored",
            groupNames: Array.Empty<string>(),
            ct: CancellationToken.None);

        // Assert
        Assert.NotNull(result);
        Assert.Empty(result);
    }

    [Fact]
    public async Task ResolveGroupsAsync_EmptyGroupNames_DictUsesOrdinalIgnoreCase()
    {
        // Arrange
        var resolver = new SharePointGroupResolver(null!);

        // Act
        var result = await resolver.ResolveGroupsAsync(
            ctx: null!,
            clientId: "ignored",
            groupNames: Array.Empty<string>(),
            ct: CancellationToken.None);

        // Assert: verify the returned dict is OrdinalIgnoreCase by casting to Dictionary
        // and checking its comparer, or by testing that the underlying type supports it.
        // Since ResolveGroupsAsync returns a Dictionary<string,…> wrapped as IReadOnlyDictionary,
        // we cast back and insert a test entry with mixed casing.
        var mutable = (Dictionary<string, IReadOnlyList<SharepointToolbox.Core.Models.ResolvedMember>>)result;
        mutable["Site Members"] = Array.Empty<SharepointToolbox.Core.Models.ResolvedMember>();
        Assert.True(mutable.ContainsKey("site members"),
            "Result dictionary comparer must be OrdinalIgnoreCase");
    }

    // ── Integration tests (live SP tenant required) ────────────────────────────

    [Fact(Skip = "Requires live SP tenant — run manually against a real ClientContext")]
    public async Task ResolveGroupsAsync_KnownGroup_ReturnsMembers()
    {
        // Integration test: create a real ClientContext, call with a known group name,
        // verify the returned list contains at least one ResolvedMember.
        await Task.CompletedTask;
    }

    [Fact(Skip = "Requires live SP tenant — verify case-insensitive lookup with real data")]
    public async Task ResolveGroupsAsync_LookupDifferentCasing_FindsGroup()
    {
        // Integration test: resolver stores "Site Members" — lookup "site members" should succeed.
        await Task.CompletedTask;
    }
}
