using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Olive.Klipy;

namespace Olive.Services;

internal sealed partial class GifCache
{
    private readonly string _cacheDirectory;
    private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new(StringComparer.OrdinalIgnoreCase);

    public GifCache()
    {
        _cacheDirectory = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "Olive",
            "Cache");
    }

    public async Task<string> GetOrDownloadAsync(KlipyGif gif, CancellationToken cancellationToken)
    {
        Directory.CreateDirectory(_cacheDirectory);

        var fileName = SafeFileName(gif.Id) + ".gif";
        var finalPath = Path.Combine(_cacheDirectory, fileName);
        if (File.Exists(finalPath))
        {
            return finalPath;
        }

        var gate = _locks.GetOrAdd(fileName, _ => new SemaphoreSlim(1, 1));
        await gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            if (File.Exists(finalPath))
            {
                return finalPath;
            }

            var tempPath = Path.Combine(_cacheDirectory, fileName + "." + Guid.NewGuid().ToString("N") + ".tmp");
            try
            {
                await KlipyClient.DownloadGifAsync(gif.GifUrl, tempPath, cancellationToken).ConfigureAwait(false);
                File.Move(tempPath, finalPath, overwrite: true);
            }
            finally
            {
                if (File.Exists(tempPath))
                {
                    File.Delete(tempPath);
                }
            }

            return finalPath;
        }
        finally
        {
            gate.Release();
        }
    }

    private static string SafeFileName(string id)
    {
        var safe = UnsafeFileNameCharacters().Replace(id, "_");
        return string.IsNullOrWhiteSpace(safe) ? Guid.NewGuid().ToString("N") : safe;
    }

    [GeneratedRegex("[^a-zA-Z0-9_.-]+")]
    private static partial Regex UnsafeFileNameCharacters();
}
