using InABox.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InABox.Avalonia;

public static class CacheManager
{
    private static Dictionary<string, DateTime> _expiries = new();

    private static Timer? _timer;

    private static void StartTimer()
    {
        if (_timer is not null) return;

        // _timer = new Timer(ProcessCache, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
    }

    private static void ProcessCache(object? state)
    {
        var now = DateTime.Now;
        var toDelete = _expiries.Where(x => x.Value < now).Select(x => x.Key).ToArray();
        foreach (var item in toDelete)
        {
            try
            {
                File.Delete(CacheFileName(item));
            }
            catch(Exception e)
            {
                MobileLogging.Log(e);
            }
        }
        lock (_expiries)
        {
            foreach (var item in toDelete)
            {
                _expiries.Remove(item);
            }
        }
    }
    private static void AddExpiry(string key, DateTime expiry)
    {
        // lock (_expiries)
        // {
        //     _expiries[key] = expiry;
        // }
        // SaveExpiries();
    }

    private static Guid CacheID { get; set; }

    private static bool IsCached(string key) => File.Exists(CacheFileName(key));
    
    private static string CacheFileName(string key) =>
        Path.Combine(CacheFolder(), key);

    private static string CacheFolder()
    {
        var result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
        if (OperatingSystem.IsWindows())
        {
            var assembly = Path.GetFileNameWithoutExtension(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName);
            result = Path.Combine(result, assembly);
        }

        if (CacheID != Guid.Empty)
            result = Path.Combine(result, CacheID.ToString());
        if (!Directory.Exists(result))
            Directory.CreateDirectory(result);
        return result;
    }

    private const string EXPIRY_FILE = "cache_expiry.json";

    private static void LoadExpiries()
    {
        var filename = CacheFileName(EXPIRY_FILE);
        if (File.Exists(filename))
        {
            var json = File.ReadAllText(filename);
            lock (_expiries)
            {
                _expiries = Serialization.Deserialize<Dictionary<string, DateTime>>(json) ?? [];
            }
        }
        else
        {
            lock (_expiries)
            {
                _expiries = [];
            }
        }
    }
    private static void SaveExpiries()
    {
        try
        {
            lock (_expiries)
            {
                var file = CacheFileName(EXPIRY_FILE);
                var json = Serialization.Serialize(_expiries);
                File.WriteAllText(file, json);
            }
        }
        catch (Exception e)
        {
            MobileLogging.Log(e);
        }
    }

    #region Public Interface

    public static void Initialise(Guid cacheID)
    {
        CacheID = cacheID;
        // LoadExpiries();

        StartTimer();
    }

    public static bool TryLoadBinary<T>(string key, [NotNullWhen(true)] out T? value, out DateTime lastUpdated)
        where T : class, ISerializeBinary, new()
    {
        var filename = CacheFileName(key);
        if (File.Exists(filename))
        {
            lastUpdated = File.GetLastWriteTime(filename);
            using var stream = new FileStream(filename, FileMode.Open);
            value = Serialization.ReadBinary<T>(stream, BinarySerializationSettings.Latest);
            return true;
        }
        else
        {
            lastUpdated = default;
            value = null;
            return false;
        }
    }
    public static void SaveBinary<T>(string key, T value, DateTime expiry)
        where T : class, ISerializeBinary, new()
    {
        var data = value.WriteBinary(BinarySerializationSettings.Latest);
        try
        {
            var file = CacheFileName(key);
            File.WriteAllBytes(file, data);
            AddExpiry(key, expiry);
        }
        catch (Exception e)
        {
            MobileLogging.Log(e);
        }
    }

    public static bool TryLoadJSON<T>(string key, [NotNullWhen(true)] out T? value, out DateTime lastUpdated)
        where T : class, new()
    {
        var filename = CacheFileName(key);
        if (File.Exists(filename))
        {
            lastUpdated = File.GetLastWriteTime(filename);
            var json = File.ReadAllText(filename);
            value = Serialization.Deserialize<T>(json);
            return value is not null;
        }
        else
        {
            lastUpdated = default;
            value = null;
            return false;
        }
    }
    public static void SaveJSON<T>(string key, T value, DateTime expiry)
        where T : class, new()
    {
        try
        {
            var file = CacheFileName(key);
            var json = Serialization.Serialize(value);
            File.WriteAllText(file, json);
            AddExpiry(key, expiry);
        }
        catch (Exception e)
        {
            MobileLogging.Log(e);
        }
    }

    #endregion
}
