using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;

namespace LightlessSync.Utils
{
    public static class FileSystemHelper
    {
        public enum FilesystemType
        {
            Unknown = 0,
            NTFS, // Compressable on file level
            Btrfs, // Compressable on file level
            Ext4, // Uncompressable
            Xfs, // Uncompressable
            Apfs, // Compressable on OS
            HfsPlus, // Compressable on OS
            Fat, // Uncompressable
            Exfat, // Uncompressable
            Zfs // Compressable, not on file level
        }

        private const string _mountPath = "/proc/mounts";
        private const int _defaultBlockSize = 4096;
        private static readonly Dictionary<string, int> _blockSizeCache = new(StringComparer.OrdinalIgnoreCase);
        private static readonly ConcurrentDictionary<string, FilesystemType> _filesystemTypeCache = new(StringComparer.OrdinalIgnoreCase);

        public static FilesystemType GetFilesystemType(string filePath, bool isWine = false)
        {
            try
            {
                string rootPath;

                if (OperatingSystem.IsWindows() && (!IsProbablyWine() || !isWine))
                {
                    var info = new FileInfo(filePath);
                    var dir = info.Directory ?? new DirectoryInfo(filePath);
                    rootPath = dir.Root.FullName;
                }
                else
                {
                    rootPath = GetMountPoint(filePath);
                    if (string.IsNullOrEmpty(rootPath))
                        rootPath = "/";
                }

                if (_filesystemTypeCache.TryGetValue(rootPath, out var cachedType))
                    return cachedType;

                FilesystemType detected;

                if (OperatingSystem.IsWindows() && (!IsProbablyWine() || !isWine))
                {
                    var root = new DriveInfo(rootPath);
                    var format = root.DriveFormat?.ToUpperInvariant() ?? string.Empty;

                    detected = format switch
                    {
                        "NTFS" => FilesystemType.NTFS,
                        "FAT32" => FilesystemType.Fat,
                        "EXFAT" => FilesystemType.Exfat,
                        _ => FilesystemType.Unknown
                    };
                }
                else
                {
                    detected = GetLinuxFilesystemType(filePath);
                }

                if (isWine || IsProbablyWine())
                {
                    switch (detected)
                    {
                        case FilesystemType.NTFS:
                        case FilesystemType.Unknown:
                            {
                                var linuxDetected = GetLinuxFilesystemType(filePath);
                                if (linuxDetected != FilesystemType.Unknown)
                                {
                                    detected = linuxDetected;
                                }

                                break;
                            }
                    }
                }

                _filesystemTypeCache[rootPath] = detected;
                return detected;
            }
            catch
            {
                return FilesystemType.Unknown;
            }
        }

        private static string GetMountPoint(string filePath)
        {
            try
            {
                var path = Path.GetFullPath(filePath);
                if (!File.Exists(_mountPath)) return "/";
                var mounts = File.ReadAllLines(_mountPath);

                string bestMount = "/";
                foreach (var line in mounts)
                {
                    var parts = line.Split(' ');
                    if (parts.Length < 3) continue;
                    var mountPoint = parts[1].Replace("\\040", " ", StringComparison.Ordinal);

                    string normalizedMount;
                    try { normalizedMount = Path.GetFullPath(mountPoint); }
                    catch { normalizedMount = mountPoint; }

                    if (path.StartsWith(normalizedMount, StringComparison.Ordinal) &&
                        normalizedMount.Length > bestMount.Length)
                    {
                        bestMount = normalizedMount;
                    }
                }

                return bestMount;
            }
            catch
            {
                return "/";
            }
        }

        public static string GetMountOptionsForPath(string path)
        {
            try
            {
                var fullPath = Path.GetFullPath(path);
                var mounts = File.ReadAllLines("/proc/mounts");
                string bestMount = string.Empty;
                string mountOptions = string.Empty;

                foreach (var line in mounts)
                {
                    var parts = line.Split(' ');
                    if (parts.Length < 4) continue;
                    var mountPoint = parts[1].Replace("\\040", " ", StringComparison.Ordinal);
                    string normalized;
                    try { normalized = Path.GetFullPath(mountPoint); }
                    catch { normalized = mountPoint; }

                    if (fullPath.StartsWith(normalized, StringComparison.Ordinal) &&
                        normalized.Length > bestMount.Length)
                    {
                        bestMount = normalized;
                        mountOptions = parts[3];
                    }
                }

                return mountOptions;
            }
            catch (Exception)
            {
                return string.Empty;
            }
        }

        private static FilesystemType GetLinuxFilesystemType(string filePath)
        {
            try
            {
                var mountPoint = GetMountPoint(filePath);
                var mounts = File.ReadAllLines(_mountPath);

                foreach (var line in mounts)
                {
                    var parts = line.Split(' ');
                    if (parts.Length < 3) continue;
                    var mount = parts[1].Replace("\\040", " ", StringComparison.Ordinal);
                    if (string.Equals(mount, mountPoint, StringComparison.Ordinal))
                    {
                        var fstype = parts[2].ToLowerInvariant();
                        return fstype switch
                        {
                            "btrfs" => FilesystemType.Btrfs,
                            "ext4" => FilesystemType.Ext4,
                            "xfs" => FilesystemType.Xfs,
                            "zfs" => FilesystemType.Zfs,
                            "apfs" => FilesystemType.Apfs,
                            "hfsplus" => FilesystemType.HfsPlus,
                            _ => FilesystemType.Unknown
                        };
                    }
                }

                return FilesystemType.Unknown;
            }
            catch
            {
                return FilesystemType.Unknown;
            }
        }

        public static int GetBlockSizeForPath(string path, ILogger? logger = null, bool isWine = false)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(path))
                    return _defaultBlockSize;

                var fi = new FileInfo(path);
                if (!fi.Exists)
                    return _defaultBlockSize;

                var root = fi.Directory?.Root.FullName.ToLowerInvariant() ?? "/";
                if (_blockSizeCache.TryGetValue(root, out int cached))
                    return cached;

                if (OperatingSystem.IsWindows() && !isWine)
                {
                    int result = GetDiskFreeSpaceW(root,
                        out uint sectorsPerCluster,
                        out uint bytesPerSector,
                        out _,
                        out _);

                    if (result == 0)
                    {
                        logger?.LogWarning("Failed to determine block size for {root}", root);
                        return _defaultBlockSize;
                    }

                    int clusterSize = (int)(sectorsPerCluster * bytesPerSector);
                    _blockSizeCache[root] = clusterSize;
                    logger?.LogTrace("NTFS cluster size for {root}: {cluster}", root, clusterSize);
                    return clusterSize;
                }

                return _defaultBlockSize;
            }
            catch (Exception ex)
            {
                logger?.LogTrace(ex, "Error determining block size for {path}", path);
                return _defaultBlockSize;
            }
        }

        [DllImport("kernel32.dll", SetLastError = true, PreserveSig = true)]
        private static extern int GetDiskFreeSpaceW([In, MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName, out uint lpSectorsPerCluster, out uint lpBytesPerSector, out uint lpNumberOfFreeClusters, out uint lpTotalNumberOfClusters);

        //Extra check on 
        public static bool IsProbablyWine() => Environment.GetEnvironmentVariable("WINELOADERNOEXEC") != null || Environment.GetEnvironmentVariable("WINEDLLPATH") != null || Directory.Exists("/proc/self") && File.Exists("/proc/mounts");
    }
}
