using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SharePoint.Client;
using PnP.Framework;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Infrastructure.Auth;

namespace SharepointToolbox.Services;

/// <summary>
/// Singleton that owns all live ClientContext instances.
/// Every SharePoint operation goes through this class — callers MUST NOT store
/// ClientContext references; request it fresh each time via GetOrCreateContextAsync.
/// </summary>
public class SessionManager : ISessionManager
{
    private readonly MsalClientFactory _msalFactory;
    private readonly Dictionary<string, ClientContext> _contexts = new();
    private readonly SemaphoreSlim _lock = new(1, 1);

    public SessionManager(MsalClientFactory msalFactory)
    {
        _msalFactory = msalFactory;
    }

    /// <summary>Returns true if an authenticated ClientContext exists for this tenantUrl.</summary>
    public bool IsAuthenticated(string tenantUrl) =>
        _contexts.ContainsKey(NormalizeUrl(tenantUrl));

    /// <summary>
    /// Returns an existing ClientContext or creates a new one via interactive MSAL login.
    /// Uses MsalClientFactory so the token cache is registered per-clientId before PnP acquires tokens.
    /// Only SessionManager holds ClientContext instances — never return to callers for storage.
    /// </summary>
    public async Task<ClientContext> GetOrCreateContextAsync(
        TenantProfile profile,
        CancellationToken ct = default)
    {
        ArgumentException.ThrowIfNullOrEmpty(profile.TenantUrl);
        ArgumentException.ThrowIfNullOrEmpty(profile.ClientId);

        var key = NormalizeUrl(profile.TenantUrl);

        await _lock.WaitAsync(ct);
        try
        {
            if (_contexts.TryGetValue(key, out var existing))
                return existing;

            // Ensure the per-clientId PCA + MsalCacheHelper is created BEFORE PnP acquires tokens.
            // MsalClientFactory.GetOrCreateAsync creates the PCA and registers MsalCacheHelper on it.
            // We then wire the same helper to PnP's internal token cache via tokenCacheCallback,
            // so both PCA and PnP share the same persistent cache file per clientId.
            await _msalFactory.GetOrCreateAsync(profile.ClientId);
            var cacheHelper = _msalFactory.GetCacheHelper(profile.ClientId);

            var authManager = AuthenticationManager.CreateWithInteractiveLogin(
                clientId: profile.ClientId,
                openBrowserCallback: (url, port) =>
                {
                    System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
                    {
                        FileName = url,
                        UseShellExecute = true
                    });
                },
                tokenCacheCallback: tokenCache =>
                {
                    // Wire the same MsalCacheHelper to PnP's internal token cache so both
                    // PnP and our PCA (from MsalClientFactory) share the same persistent
                    // cache file at %AppData%\SharepointToolbox\auth\msal_{clientId}.cache
                    cacheHelper.RegisterCache(tokenCache);
                });

            var ctx = await authManager.GetContextAsync(profile.TenantUrl, ct);
            _contexts[key] = ctx;
            return ctx;
        }
        finally { _lock.Release(); }
    }

    /// <summary>
    /// Disposes the ClientContext and clears MSAL state for the given tenant.
    /// Idempotent — safe to call for tenants that were never authenticated.
    /// Called by "Clear Session" button and on tenant profile deletion.
    /// </summary>
    public async Task ClearSessionAsync(string tenantUrl)
    {
        var key = NormalizeUrl(tenantUrl);
        await _lock.WaitAsync();
        try
        {
            if (_contexts.TryGetValue(key, out var ctx))
            {
                ctx.Dispose();
                _contexts.Remove(key);
            }
        }
        finally { _lock.Release(); }
    }

    private static string NormalizeUrl(string url) =>
        url.TrimEnd('/').ToLowerInvariant();
}
