using System.IO;
using System.Text.Json;
using Moq;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Services;
using AppGraphClientFactory = SharepointToolbox.Infrastructure.Auth.GraphClientFactory;
using AppMsalClientFactory = SharepointToolbox.Infrastructure.Auth.MsalClientFactory;

namespace SharepointToolbox.Tests.Services;

/// <summary>
/// Unit tests for AppRegistrationResult, TenantProfile.AppId, and AppRegistrationService.
/// Graph API calls require live Entra connectivity and are marked as Integration tests.
/// Pure logic (model behaviour, BuildRequiredResourceAccess structure) is covered here.
/// </summary>
[Trait("Category", "Unit")]
public class AppRegistrationServiceTests
{
    // ────────────────────────────────────────────────────────────────────────
    // AppRegistrationResult — factory method tests
    // ────────────────────────────────────────────────────────────────────────

    [Fact]
    public void Success_CarriesAppId()
    {
        var result = AppRegistrationResult.Success("appId123");

        Assert.True(result.IsSuccess);
        Assert.False(result.IsFallback);
        Assert.Equal("appId123", result.AppId);
        Assert.Null(result.ErrorMessage);
    }

    [Fact]
    public void Failure_CarriesMessage()
    {
        var result = AppRegistrationResult.Failure("Something went wrong");

        Assert.False(result.IsSuccess);
        Assert.False(result.IsFallback);
        Assert.Null(result.AppId);
        Assert.Equal("Something went wrong", result.ErrorMessage);
    }

    [Fact]
    public void FallbackRequired_SetsFallback()
    {
        var result = AppRegistrationResult.FallbackRequired();

        Assert.False(result.IsSuccess);
        Assert.True(result.IsFallback);
        Assert.Null(result.AppId);
        Assert.Null(result.ErrorMessage);
    }

    // ────────────────────────────────────────────────────────────────────────
    // TenantProfile.AppId — nullable field tests
    // ────────────────────────────────────────────────────────────────────────

    [Fact]
    public void AppId_DefaultsToNull()
    {
        var profile = new TenantProfile();
        Assert.Null(profile.AppId);
    }

    [Fact]
    public void AppId_RoundTrips_ViaJson()
    {
        var profile = new TenantProfile
        {
            Name = "Test Tenant",
            TenantUrl = "https://example.sharepoint.com",
            ClientId = "client-id-abc",
            AppId = "registered-app-id-xyz"
        };

        var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
        var json = JsonSerializer.Serialize(profile, options);
        var loaded = JsonSerializer.Deserialize<TenantProfile>(json, options);

        Assert.NotNull(loaded);
        Assert.Equal("registered-app-id-xyz", loaded!.AppId);
        Assert.Equal("Test Tenant", loaded.Name);
    }

    [Fact]
    public void AppId_Null_RoundTrips_ViaJson()
    {
        var profile = new TenantProfile
        {
            Name = "Test Tenant",
            TenantUrl = "https://example.sharepoint.com",
            ClientId = "client-id-abc",
            AppId = null
        };

        var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
        var json = JsonSerializer.Serialize(profile, options);
        var loaded = JsonSerializer.Deserialize<TenantProfile>(json, options);

        Assert.NotNull(loaded);
        Assert.Null(loaded!.AppId);
    }

    // ────────────────────────────────────────────────────────────────────────
    // AppRegistrationService — constructor / dependency wiring
    // ────────────────────────────────────────────────────────────────────────

    [Fact]
    public void AppRegistrationService_ImplementsInterface()
    {
        // Verify that the concrete class satisfies the interface contract.
        // We instantiate with a real MsalClientFactory (no-IO path) and mocked session manager / logger.
        var msalFactory = new AppMsalClientFactory(Path.GetTempPath());
        var graphFactory = new AppGraphClientFactory(msalFactory);
        var sessionManagerMock = new Mock<ISessionManager>();
        var loggerMock = new Microsoft.Extensions.Logging.Abstractions.NullLogger<AppRegistrationService>();

        var service = new AppRegistrationService(graphFactory, msalFactory, sessionManagerMock.Object, loggerMock);

        Assert.IsAssignableFrom<IAppRegistrationService>(service);
    }

    // ────────────────────────────────────────────────────────────────────────
    // BuildRequiredResourceAccess — structure verification (no live calls)
    // ────────────────────────────────────────────────────────────────────────

    [Fact]
    public void BuildRequiredResourceAccess_ContainsTwoResources()
    {
        var result = AppRegistrationService.BuildRequiredResourceAccess();

        Assert.Equal(2, result.Count);
    }

    [Fact]
    public void BuildRequiredResourceAccess_GraphResource_HasFourScopes()
    {
        const string graphAppId = "00000003-0000-0000-c000-000000000000";
        var result = AppRegistrationService.BuildRequiredResourceAccess();

        var graphEntry = result.Single(r => r.ResourceAppId == graphAppId);
        Assert.NotNull(graphEntry.ResourceAccess);
        Assert.Equal(4, graphEntry.ResourceAccess!.Count);
    }

    [Fact]
    public void BuildRequiredResourceAccess_SharePointResource_HasOneScope()
    {
        const string spoAppId = "00000003-0000-0ff1-ce00-000000000000";
        var result = AppRegistrationService.BuildRequiredResourceAccess();

        var spoEntry = result.Single(r => r.ResourceAppId == spoAppId);
        Assert.NotNull(spoEntry.ResourceAccess);
        Assert.Single(spoEntry.ResourceAccess!);
    }

    [Fact]
    public void BuildRequiredResourceAccess_AllScopes_HaveScopeType()
    {
        var result = AppRegistrationService.BuildRequiredResourceAccess();

        var allAccess = result.SelectMany(r => r.ResourceAccess!);
        Assert.All(allAccess, ra => Assert.Equal("Scope", ra.Type));
    }

    [Fact]
    public void BuildRequiredResourceAccess_GraphResource_ContainsUserReadScope()
    {
        const string graphAppId = "00000003-0000-0000-c000-000000000000";
        var userReadGuid = Guid.Parse("e1fe6dd8-ba31-4d61-89e7-88639da4683d"); // User.Read
        var result = AppRegistrationService.BuildRequiredResourceAccess();

        var graphEntry = result.Single(r => r.ResourceAppId == graphAppId);
        Assert.Contains(graphEntry.ResourceAccess!, ra => ra.Id == userReadGuid);
    }
}
