using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Formats;
using Berufsschule_HAM.Models;
using SixLabors.ImageSharp.Processing;

namespace Berufsschule_HAM.Helpers;

public static class ImageHelper
{
    public static Dictionary<int, Dictionary<string, byte[]>> ImageCache = [];
    public static byte[] ResizeAndConvertToWebp(string imageString, int size = 32)
    {
        if (imageString.Length == 0) return [];
        if (ImageCache.TryGetValue(size, out Dictionary<string, byte[]>? sizeCache)
        && sizeCache.TryGetValue(imageString, out byte[]? result) && result.Length > 0)
        {
            return result;
        }
        if (sizeCache is null) ImageCache[size] = [];
        lock (ImageCache[size])
        {
            byte[] imageBytes = Convert.FromBase64String(imageString);
            using var inputStream = new MemoryStream(imageBytes);

            var decoderOptions = new DecoderOptions
            {
                TargetSize = new Size(size, size),
                SkipMetadata = true
            };

            using var image = Image.Load(decoderOptions, inputStream);

            int minDimension = Math.Min(image.Width, image.Height);
            var cropRectangle = new Rectangle(
                (image.Width - minDimension) / 2,
                (image.Height - minDimension) / 2,
                minDimension,
                minDimension);

            image.Mutate(x =>
            {
                x.Crop(cropRectangle);
                x.Resize(new ResizeOptions
                {
                    Size = new Size(size, size),
                    Mode = ResizeMode.Crop
                });
            });

            using var outputStream = new MemoryStream();
            image.Save(outputStream, new WebpEncoder());
            result = outputStream.ToArray();
            ImageCache[size][imageString] = result;
        }
        return result;
    }

    public static byte[] GetDefaultUserImage(int size = 48)
    {
        return ResizeAndConvertToWebp(GetDefaultUserImageAsBase64(), size);
    }

    public static string GetDefaultUserImageAsBase64()
    {
        return Convert.ToBase64String(File.ReadAllBytes("wwwroot/user_default.webp"));
    }

    public static async Task PreloadUsers(IEnumerable<UserModel> userModels, UserImagePreloadType? preloadType)
    {
        if (preloadType is null || preloadType == UserImagePreloadType.None) return;

        foreach (UserModel user in userModels)
        {
            if (user.JpegPhoto is null) continue;
            ResizeAndConvertToWebp(user.JpegPhoto, 48);
            if (preloadType == UserImagePreloadType.FullSized)
            {
                ResizeAndConvertToWebp(user.JpegPhoto, 256);
            }
            Thread.Sleep(10);
        }
    }

    public static long GetImageCacheSize()
    {
        long size = 0;
        int stringOverhead = GetStringOverhead();
        int arrayOverhead = GetArrayOverhead();
        foreach (var kvOuter in ImageCache)
        {
            foreach (var kvInner in kvOuter.Value)
            {
                size += System.Text.ASCIIEncoding.Unicode.GetByteCount(kvInner.Key);
                size += stringOverhead;

                // byte[] size
                if (kvInner.Value != null)
                    size += kvInner.Value.Length + arrayOverhead;
            }
        }
        return size;
    }

    public static string ToHumanReadableSize(long bytes)
    {
        string[] sizes = ["B", "KB", "MB", "GB", "TB", "PB"];
        double len = bytes;
        int order = 0;

        while (len >= 1024 && order < sizes.Length - 1)
        {
            order++;
            len /= 1024;
        }

        return $"{len:0.##} {sizes[order]}";
    }

    private static int GetStringOverhead()
    {
        // Object header size
        int header = IntPtr.Size == 8 ? 16 : 8; // 16 bytes on x64, 8 on x86

        int size =
            header +
            sizeof(int) +                // string length field
            sizeof(char);                // null terminator

        return size;
    }

    private static int GetArrayOverhead()
    {
        // On x64 object header = 16 bytes; on x86 = 8 bytes
        int header = IntPtr.Size == 8 ? 16 : 8;

        // Array length field = 4 bytes
        int lengthField = sizeof(int);

        // Padding on x64 to align the elements (so the first element is aligned)
        int padding = IntPtr.Size == 8 ? 4 : 0;

        return header + lengthField + padding;
    }
}