using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SharePoint.Client;
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>
/// Tests for auto-elevation logic in PermissionsViewModel scan loop.
/// OWN-02: catch access-denied, call ElevateAsync, retry scan, tag entries.
/// </summary>
public class PermissionsViewModelOwnershipTests
{
    /// <summary>
    /// Creates a ServerUnauthorizedAccessException via Activator (the reference assembly
    /// exposes a different ctor signature than the runtime DLL — use runtime reflection).
    /// </summary>
    private static ServerUnauthorizedAccessException MakeAccessDeniedException()
    {
        var t = typeof(ServerUnauthorizedAccessException);
        var ctor = t.GetConstructors(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[0];
        return (ServerUnauthorizedAccessException)ctor.Invoke(new object?[] { "Access Denied", "", 0, "", "ServerUnauthorizedAccessException", null, "" });
    }

    private static readonly string SiteUrl = "https://tenant.sharepoint.com/sites/test";
    private static readonly string TenantUrl = "https://tenant.sharepoint.com";

    private static PermissionsViewModel CreateVm(
        Mock<IPermissionsService> permissionsSvc,
        Mock<ISessionManager> sessionManager,
        SettingsService? settingsService = null,
        IOwnershipElevationService? ownershipService = null)
    {
        var siteListSvc = new Mock<ISiteListService>();
        var logger = NullLogger<FeatureViewModelBase>.Instance;

        var vm = new PermissionsViewModel(
            permissionsSvc.Object,
            siteListSvc.Object,
            sessionManager.Object,
            logger,
            settingsService: settingsService,
            ownershipService: ownershipService);

        WeakReferenceMessenger.Default.Send(new GlobalSitesChangedMessage(
            new List<SiteInfo> { new(SiteUrl, "Test Site") }.AsReadOnly()));
        vm.SetCurrentProfile(new TenantProfile
        {
            Name = "Test",
            TenantUrl = TenantUrl,
            ClientId = "client-id"
        });

        return vm;
    }

    /// <summary>
    /// When AutoTakeOwnership=false and ScanSiteAsync throws ServerUnauthorizedAccessException,
    /// the exception propagates.
    /// </summary>
    [Fact]
    public async Task ScanLoop_ToggleOff_AccessDenied_ExceptionPropagates()
    {
        var permSvc = new Mock<IPermissionsService>();
        permSvc
            .Setup(s => s.ScanSiteAsync(It.IsAny<ClientContext>(), It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(), It.IsAny<CancellationToken>()))
            .ThrowsAsync(MakeAccessDeniedException());

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

        // No settingsService => toggle OFF (null treated as false)
        var vm = CreateVm(permSvc, sessionMgr, settingsService: null, ownershipService: null);

        await Assert.ThrowsAsync<ServerUnauthorizedAccessException>(
            () => vm.TestRunOperationAsync(CancellationToken.None, new Progress<OperationProgress>()));
    }

    /// <summary>
    /// When AutoTakeOwnership=true and scan throws access denied,
    /// ElevateAsync is called once then ScanSiteAsync is retried.
    /// </summary>
    [Fact]
    public async Task ScanLoop_ToggleOn_AccessDenied_ElevatesAndRetries()
    {
        var permSvc = new Mock<IPermissionsService>();
        var callCount = 0;
        permSvc
            .Setup(s => s.ScanSiteAsync(It.IsAny<ClientContext>(), It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(() =>
            {
                callCount++;
                if (callCount == 1)
                    throw MakeAccessDeniedException();
                return new List<PermissionEntry>
                {
                    new("Site", "Test", SiteUrl, true, "User1", "u1@t.com", "Full Control", "Direct", "User")
                };
            });

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

        var elevationSvc = new Mock<IOwnershipElevationService>();
        elevationSvc
            .Setup(e => e.ElevateAsync(It.IsAny<ClientContext>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
            .Returns(Task.CompletedTask);

        var settingsSvc = FakeSettingsServiceWithAutoOwnership(true);

        var vm = CreateVm(permSvc, sessionMgr, settingsService: settingsSvc, ownershipService: elevationSvc.Object);

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

        // ElevateAsync called once
        elevationSvc.Verify(
            e => e.ElevateAsync(It.IsAny<ClientContext>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()),
            Times.Once);

        // ScanSiteAsync called twice (first fails, retry succeeds)
        permSvc.Verify(
            s => s.ScanSiteAsync(It.IsAny<ClientContext>(), It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(), It.IsAny<CancellationToken>()),
            Times.Exactly(2));
    }

    /// <summary>
    /// When ScanSiteAsync succeeds on first try, ElevateAsync is never called.
    /// </summary>
    [Fact]
    public async Task ScanLoop_ScanSucceeds_ElevateNeverCalled()
    {
        var permSvc = new Mock<IPermissionsService>();
        permSvc
            .Setup(s => s.ScanSiteAsync(It.IsAny<ClientContext>(), It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<PermissionEntry>
            {
                new("Site", "Test", SiteUrl, true, "User1", "u1@t.com", "Full Control", "Direct", "User")
            });

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

        var elevationSvc = new Mock<IOwnershipElevationService>();
        var settingsSvc = FakeSettingsServiceWithAutoOwnership(true);

        var vm = CreateVm(permSvc, sessionMgr, settingsService: settingsSvc, ownershipService: elevationSvc.Object);

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

        elevationSvc.Verify(
            e => e.ElevateAsync(It.IsAny<ClientContext>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()),
            Times.Never);
    }

    /// <summary>
    /// After successful elevation+retry, returned entries have WasAutoElevated=true.
    /// </summary>
    [Fact]
    public async Task ScanLoop_AfterElevation_EntriesTaggedWasAutoElevated()
    {
        var permSvc = new Mock<IPermissionsService>();
        var callCount = 0;
        permSvc
            .Setup(s => s.ScanSiteAsync(It.IsAny<ClientContext>(), It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(() =>
            {
                callCount++;
                if (callCount == 1)
                    throw MakeAccessDeniedException();
                return new List<PermissionEntry>
                {
                    new("Site", "Test", SiteUrl, true, "User1", "u1@t.com", "Full Control", "Direct", "User")
                };
            });

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

        var elevationSvc = new Mock<IOwnershipElevationService>();
        elevationSvc
            .Setup(e => e.ElevateAsync(It.IsAny<ClientContext>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
            .Returns(Task.CompletedTask);

        var settingsSvc = FakeSettingsServiceWithAutoOwnership(true);

        var vm = CreateVm(permSvc, sessionMgr, settingsService: settingsSvc, ownershipService: elevationSvc.Object);

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

        Assert.NotEmpty(vm.Results);
        Assert.All(vm.Results, e => Assert.True(e.WasAutoElevated));
    }

    /// <summary>
    /// If ElevateAsync itself throws, the exception propagates (no infinite retry).
    /// </summary>
    [Fact]
    public async Task ScanLoop_ElevationThrows_ExceptionPropagates()
    {
        var permSvc = new Mock<IPermissionsService>();
        permSvc
            .Setup(s => s.ScanSiteAsync(It.IsAny<ClientContext>(), It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(), It.IsAny<CancellationToken>()))
            .ThrowsAsync(MakeAccessDeniedException());

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

        var elevationSvc = new Mock<IOwnershipElevationService>();
        elevationSvc
            .Setup(e => e.ElevateAsync(It.IsAny<ClientContext>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
            .ThrowsAsync(new InvalidOperationException("Elevation failed"));
        // ScanSiteAsync always throws access denied (does NOT succeed after elevation throws)


        var settingsSvc = FakeSettingsServiceWithAutoOwnership(true);

        var vm = CreateVm(permSvc, sessionMgr, settingsService: settingsSvc, ownershipService: elevationSvc.Object);

        await Assert.ThrowsAsync<InvalidOperationException>(
            () => vm.TestRunOperationAsync(CancellationToken.None, new Progress<OperationProgress>()));

        // ScanSiteAsync was called exactly once (no retry after elevation failure)
        permSvc.Verify(
            s => s.ScanSiteAsync(It.IsAny<ClientContext>(), It.IsAny<ScanOptions>(),
                It.IsAny<IProgress<OperationProgress>>(), It.IsAny<CancellationToken>()),
            Times.Once);
    }

    /// <summary>
    /// Validates DeriveAdminUrl logic for standard tenant URL.
    /// </summary>
    [Theory]
    [InlineData("https://tenant.sharepoint.com", "https://tenant-admin.sharepoint.com")]
    [InlineData("https://tenant.sharepoint.com/", "https://tenant-admin.sharepoint.com")]
    [InlineData("https://tenant-admin.sharepoint.com", "https://tenant-admin.sharepoint.com")]
    public void DeriveAdminUrl_ReturnsCorrectAdminUrl(string input, string expected)
    {
        var result = PermissionsViewModel.DeriveAdminUrl(input);
        Assert.Equal(expected, result);
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private static SettingsService FakeSettingsServiceWithAutoOwnership(bool enabled)
    {
        // Use in-memory temp file
        var tempFile = System.IO.Path.GetTempFileName();
        System.IO.File.Delete(tempFile);
        var repo = new SharepointToolbox.Infrastructure.Persistence.SettingsRepository(tempFile);
        var svc = new SettingsService(repo);
        // Seed via SetAutoTakeOwnershipAsync synchronously
        svc.SetAutoTakeOwnershipAsync(enabled).GetAwaiter().GetResult();
        return svc;
    }
}
