using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;

namespace SharepointToolbox.Infrastructure.Auth;

/// <summary>
/// Creates and caches one IPublicClientApplication per ClientId with a persistent MsalCacheHelper.
/// Never shares instances across different ClientIds (per-tenant isolation).
/// </summary>
public class MsalClientFactory
{
    private readonly Dictionary<string, IPublicClientApplication> _clients = new();
    private readonly Dictionary<string, MsalCacheHelper> _helpers = new();
    private readonly SemaphoreSlim _lock = new(1, 1);

    /// <summary>Cache directory for MSAL token files.</summary>
    public string CacheDirectory { get; }

    /// <summary>Default constructor — uses %AppData%\SharepointToolbox\auth.</summary>
    public MsalClientFactory()
        : this(Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
            "SharepointToolbox", "auth"))
    { }

    /// <summary>Constructor allowing a custom cache directory (used in tests).</summary>
    public MsalClientFactory(string cacheDirectory)
    {
        CacheDirectory = cacheDirectory;
    }

    /// <summary>
    /// Returns the cached IPublicClientApplication for the given clientId,
    /// or creates a new one (with MsalCacheHelper) on first call.
    /// Thread-safe: concurrent callers with the same clientId receive the same instance.
    /// </summary>
    public async Task<IPublicClientApplication> GetOrCreateAsync(string clientId)
    {
        await _lock.WaitAsync();
        try
        {
            if (_clients.TryGetValue(clientId, out var existing))
                return existing;

            Directory.CreateDirectory(CacheDirectory);

            var storageProps = new StorageCreationPropertiesBuilder(
                    $"msal_{clientId}.cache", CacheDirectory)
                .Build();

            var pca = PublicClientApplicationBuilder
                .Create(clientId)
                .WithDefaultRedirectUri()
                .WithLegacyCacheCompatibility(false)
                .Build();

            var helper = await MsalCacheHelper.CreateAsync(storageProps);
            helper.RegisterCache(pca.UserTokenCache);

            _clients[clientId] = pca;
            _helpers[clientId] = helper;
            return pca;
        }
        finally { _lock.Release(); }
    }

    /// <summary>
    /// Returns the MsalCacheHelper for the given clientId.
    /// GetOrCreateAsync must be called first — throws InvalidOperationException otherwise.
    /// Used by SessionManager to wire PnP's internal token cache to the same persistent cache file.
    /// </summary>
    public MsalCacheHelper GetCacheHelper(string clientId)
    {
        if (!_helpers.TryGetValue(clientId, out var helper))
            throw new InvalidOperationException(
                $"No cache helper found for clientId '{clientId}'. Call GetOrCreateAsync first.");
        return helper;
    }
}
