using System.Net.Http;
using System.Threading.Tasks;
using Serilog;
using SPTInstaller.Models;

namespace SPTInstaller.Helpers;

public static class DownloadCacheHelper
{
    private static HttpClient _httpClient = new() { Timeout = TimeSpan.FromHours(1) };

    public static string CachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer/cache");
    public static string ReleaseMirrorUrl = "https://spt-releases.modd.in/release.json";
    public static string PatchMirrorUrl = "https://slugma.waffle-lord.net/mirrors.json";

    public static string GetCacheSizeText()
    {
        if (!Directory.Exists(CachePath))
        {
            var message = "No cache folder";
            Log.Information(message);
            return message;
        }

        var cacheDir = new DirectoryInfo(CachePath);

        var cacheSize = DirectorySizeHelper.GetSizeOfDirectory(cacheDir);

        if (cacheSize == -1)
        {
            var message = "An error occurred while getting the cache size :(";
            Log.Error(message);
            return message;
        }

        if (cacheSize == 0)
            return "Empty";

        return DirectorySizeHelper.SizeSuffix(cacheSize);
    }

    /// <summary>
    /// Check if a file in the cache already exists
    /// </summary>
    /// <param name="fileName">The name of the file to check for</param>
    /// <param name="expectedHash">The expected hash of the file in the cache</param>
    /// <param name="cachedFile">The file found in the cache; null if no file is found</param>
    /// <returns>True if the file is in the cache and its hash matches the expected hash, otherwise false</returns>
    public static bool CheckCache(string fileName, string expectedHash, out FileInfo cachedFile) 
        => CheckCache(new FileInfo(Path.Join(CachePath, fileName)), expectedHash, out cachedFile);

    private static bool CheckCache(FileInfo cacheFile, string expectedHash, out FileInfo fileInCache)
    {
        fileInCache = cacheFile;

        try
        {
            cacheFile.Refresh();
            Directory.CreateDirectory(CachePath);

            if (!cacheFile.Exists || expectedHash == null)
            {
                Log.Information($"{cacheFile.Name} {(cacheFile.Exists ? "is in cache" : "NOT in cache")}");
                Log.Information($"Expected hash: {(expectedHash == null ? "not provided" : expectedHash)}");
                return false;
            }

            if (FileHashHelper.CheckHash(cacheFile, expectedHash))
            {
                fileInCache = cacheFile;
                Log.Information("Hashes MATCH");
                return true;
            }

            Log.Warning("Hashes DO NOT MATCH");
            return false;
        }
        catch(Exception ex)
        {
            Log.Error(ex, "Something went wrong during hashing");
            return false;
        }
    }

    /// <summary>
    /// Download a file to the cache folder
    /// </summary>
    /// <param name="outputFileName">The file name to save the file as</param>
    /// <param name="targetLink">The url to download the file from</param>
    /// <param name="progress">A provider for progress updates</param>
    /// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
    /// <remarks>If the file exists, it is deleted before downloading</remarks>
    public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, string targetLink, IProgress<double> progress)
    {
        Directory.CreateDirectory(CachePath);
        var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));

        try
        {
            if (outputFile.Exists)
                outputFile.Delete();

            // Use the provided extension method
            using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
                await _httpClient.DownloadDataAsync(targetLink, file, progress);

            outputFile.Refresh();

            if (!outputFile.Exists)
            {
                Log.Error("Failed to download file from url: {name} :: {url}", outputFileName, targetLink);
                return null;
            }

            return outputFile;
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to download file from url: {name} :: {url}", outputFileName, targetLink);
            return null;
        }
    }

    /// <summary>
    /// Download a file to the cache folder
    /// </summary>
    /// <param name="outputFileName">The file name to save the file as</param>
    /// <param name="downloadStream">The stream the download the file from</param>
    /// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
    /// <remarks>If the file exists, it is deleted before downloading</remarks>
    public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, Stream downloadStream)
    {
        Directory.CreateDirectory(CachePath);
        var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));

        try
        {
            if (outputFile.Exists)
                outputFile.Delete();

            using var patcherFileStream = outputFile.Open(FileMode.Create);
            {
                await downloadStream.CopyToAsync(patcherFileStream);
            }

            patcherFileStream.Close();
            
            outputFile.Refresh();

            if (!outputFile.Exists)
            {
                Log.Error("Failed to download file from stream: {name}", outputFileName);
                return null;
            }

            return outputFile;
        }
        catch(Exception ex)
        {
            Log.Error(ex, "Failed to download file from stream: {fileName}", outputFileName);
            return null;
        }
    }

    /// <summary>
    /// Get the file from cache or download it
    /// </summary>
    /// <param name="fileName">The name of the file to check for in the cache</param>
    /// <param name="targetLink">The url to download from if the file doesn't exist in the cache</param>
    /// <param name="progress">A provider for progress updates</param>
    /// <param name="expectedHash">The expected hash of the cached file</param>
    /// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
    /// <remarks>Use <see cref="DownloadFileAsync(string, string, IProgress{double})"/> if you don't have an expected cache file hash</remarks>
    public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> progress, string expectedHash)
    {
        try
        {
            if (CheckCache(fileName, expectedHash, out var cacheFile))
                return cacheFile;

            return await DownloadFileAsync(fileName, targetLink, progress);
        }
        catch (Exception ex)
        {
            Log.Error(ex, $"Error while getting file: {fileName}");
            return null;
        }
    }

    /// <summary>
    /// Get the file from cache or download it
    /// </summary>
    /// <param name="fileName">The name of the file to check for in the cache</param>
    /// <param name="fileDownloadStream">The stream to download from if the file doesn't exist in the cache</param>
    /// <param name="expectedHash">The expected hash of the cached file</param>
    /// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
    /// <remarks>Use <see cref="DownloadFileAsync(string, Stream)"/> if you don't have an expected cache file hash</remarks>
    public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash)
    {
        try
        {
            if (CheckCache(fileName, expectedHash, out var cacheFile))
                return cacheFile;

            return await DownloadFileAsync(fileName, fileDownloadStream);
        }
        catch (Exception ex)
        {
            Log.Error(ex, $"Error while getting file: {fileName}");
            return null;
        }
    }
}