using Microsoft.Graph.Models;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Services;

namespace SharepointToolbox.Tests.Services;

/// <summary>
/// Unit tests for <see cref="GraphUserDirectoryService"/> (Phase 10 Plan 02).
///
/// Testing strategy: GraphUserDirectoryService wraps Microsoft Graph SDK's PageIterator,
/// whose constructor is internal and cannot be mocked without a real GraphServiceClient.
/// Full pagination/cancellation tests therefore require integration-level setup.
///
/// We test what IS unit-testable:
///   1. MapUser — the static mapping method that converts a Graph User to GraphDirectoryUser.
///      This covers all 5 required fields and the DisplayName fallback logic.
///   2. GetUsersAsync integration paths are documented with Skip tests that explain the
///      constraint and serve as living documentation of intended behaviour.
/// </summary>
[Trait("Category", "Unit")]
public class GraphUserDirectoryServiceTests
{
    // ── MapUser: field mapping ────────────────────────────────────────────────

    [Fact]
    public void MapUser_AllFieldsPresent_MapsCorrectly()
    {
        var user = new User
        {
            DisplayName = "Alice Smith",
            UserPrincipalName = "alice@contoso.com",
            Mail = "alice@contoso.com",
            Department = "Engineering",
            JobTitle = "Senior Developer",
            UserType = "Member"
        };

        var result = GraphUserDirectoryService.MapUser(user);

        Assert.Equal("Alice Smith", result.DisplayName);
        Assert.Equal("alice@contoso.com", result.UserPrincipalName);
        Assert.Equal("alice@contoso.com", result.Mail);
        Assert.Equal("Engineering", result.Department);
        Assert.Equal("Senior Developer", result.JobTitle);
        Assert.Equal("Member", result.UserType);
    }

    [Fact]
    public void MapUser_NullDisplayName_FallsBackToUserPrincipalName()
    {
        var user = new User
        {
            DisplayName = null,
            UserPrincipalName = "bob@contoso.com",
            Mail = null,
            Department = null,
            JobTitle = null,
            UserType = "Guest"
        };

        var result = GraphUserDirectoryService.MapUser(user);

        Assert.Equal("bob@contoso.com", result.DisplayName);
        Assert.Equal("bob@contoso.com", result.UserPrincipalName);
        Assert.Null(result.Mail);
        Assert.Null(result.Department);
        Assert.Null(result.JobTitle);
        Assert.Equal("Guest", result.UserType);
    }

    [Fact]
    public void MapUser_NullDisplayNameAndNullUPN_FallsBackToEmptyString()
    {
        var user = new User
        {
            DisplayName = null,
            UserPrincipalName = null,
            Mail = null,
            Department = null,
            JobTitle = null
        };

        var result = GraphUserDirectoryService.MapUser(user);

        Assert.Equal(string.Empty, result.DisplayName);
        Assert.Equal(string.Empty, result.UserPrincipalName);
    }

    [Fact]
    public void MapUser_NullUPN_ReturnsEmptyStringForUPN()
    {
        var user = new User
        {
            DisplayName = "Carol Jones",
            UserPrincipalName = null,
            Mail = "carol@contoso.com",
            Department = "Marketing",
            JobTitle = "Manager"
        };

        var result = GraphUserDirectoryService.MapUser(user);

        Assert.Equal("Carol Jones", result.DisplayName);
        Assert.Equal(string.Empty, result.UserPrincipalName);
        Assert.Equal("carol@contoso.com", result.Mail);
    }

    [Fact]
    public void MapUser_OptionalFieldsNull_ProducesNullableNullProperties()
    {
        var user = new User
        {
            DisplayName = "Dave Brown",
            UserPrincipalName = "dave@contoso.com",
            Mail = null,
            Department = null,
            JobTitle = null
        };

        var result = GraphUserDirectoryService.MapUser(user);

        Assert.Null(result.Mail);
        Assert.Null(result.Department);
        Assert.Null(result.JobTitle);
    }

    // ── MapUser: UserType mapping ──────────────────────────────────────────────

    [Fact]
    public void MapUser_PopulatesUserType()
    {
        var user = new User
        {
            DisplayName = "Eve Wilson",
            UserPrincipalName = "eve@contoso.com",
            Mail = "eve@contoso.com",
            Department = "Sales",
            JobTitle = "Account Executive",
            UserType = "Member"
        };

        var result = GraphUserDirectoryService.MapUser(user);

        Assert.Equal("Member", result.UserType);
    }

    [Fact]
    public void MapUser_NullUserType_ReturnsNull()
    {
        var user = new User
        {
            DisplayName = "Frank Lee",
            UserPrincipalName = "frank@contoso.com",
            Mail = null,
            Department = null,
            JobTitle = null,
            UserType = null
        };

        var result = GraphUserDirectoryService.MapUser(user);

        Assert.Null(result.UserType);
    }

    // ── GetUsersAsync: integration-level scenarios (skipped without live tenant) ──

    [Fact(Skip = "Requires integration test with real Graph client — PageIterator.CreatePageIterator " +
                 "uses internal GraphServiceClient request execution that cannot be mocked via Moq. " +
                 "Intended behaviour: returns all users matching filter across all pages, " +
                 "correctly mapping all 5 fields per user.")]
    public Task GetUsersAsync_SinglePage_ReturnsMappedUsers()
        => Task.CompletedTask;

    [Fact(Skip = "Requires integration test with real Graph client. " +
                 "Intended behaviour: IProgress<int>.Report is called once per user " +
                 "with an incrementing count (1, 2, 3, ...).")]
    public Task GetUsersAsync_ReportsProgressWithIncrementingCount()
        => Task.CompletedTask;

    [Fact(Skip = "Requires integration test with real Graph client. " +
                 "Intended behaviour: when CancellationToken is cancelled during iteration, " +
                 "the callback returns false and iteration stops, returning partial results " +
                 "(or OperationCanceledException if cancellation fires before first page).")]
    public Task GetUsersAsync_CancelledToken_StopsIteration()
        => Task.CompletedTask;

    [Fact(Skip = "Requires integration test with real Graph client. " +
                 "Intended behaviour: when Graph returns null response, " +
                 "GetUsersAsync returns an empty IReadOnlyList without throwing.")]
    public Task GetUsersAsync_NullResponse_ReturnsEmptyList()
        => Task.CompletedTask;
}
