using System.Diagnostics;
using System.Globalization;
using System.IO;
using NexusShell.Models;

namespace NexusShell.Services;

public sealed class NexusFileSystemService
{
    private static readonly CultureInfo DaDk = CultureInfo.GetCultureInfo("da-DK");

    private readonly string _nexusRoot;
    private readonly string _projectsPath;
    private readonly string _workspacesPath;

    public NexusFileSystemService()
    {
        _nexusRoot = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
            "NinjaNexus");
        _projectsPath = Path.Combine(_nexusRoot, "Projects");
        _workspacesPath = Path.Combine(_nexusRoot, "Workspaces");
        Directory.CreateDirectory(_projectsPath);
        Directory.CreateDirectory(_workspacesPath);
    }

    public string NexusRoot => _nexusRoot;
    public string ProjectsPath => _projectsPath;
    public string WorkspacesPath => _workspacesPath;

    public string GetDocumentsPath() => GetKnownFolder(Environment.SpecialFolder.MyDocuments);

    public string ResolveLocationId(string id) => id switch
    {
        NexusPaths.DesktopId => GetKnownFolder(Environment.SpecialFolder.Desktop),
        NexusPaths.DocumentsId => GetDocumentsPath(),
        NexusPaths.DownloadsId => Path.Combine(GetKnownFolder(Environment.SpecialFolder.UserProfile), "Downloads"),
        NexusPaths.PicturesId => GetKnownFolder(Environment.SpecialFolder.MyPictures),
        NexusPaths.VideosId => GetKnownFolder(Environment.SpecialFolder.MyVideos),
        NexusPaths.MusicId => GetKnownFolder(Environment.SpecialFolder.MyMusic),
        NexusPaths.ThisPc => NexusPaths.ThisPc,
        NexusPaths.Projects => _projectsPath,
        NexusPaths.Workspaces => _workspacesPath,
        NexusPaths.Recent => NexusPaths.Recent,
        _ => id,
    };

    public IReadOnlyList<(string Id, string Label)> GetDefaultFavorites() =>
    [
        (NexusPaths.DesktopId, "Desktop"),
        (NexusPaths.DocumentsId, "Dokumenter"),
        (NexusPaths.DownloadsId, "Downloads"),
        (NexusPaths.PicturesId, "Billeder"),
        (NexusPaths.VideosId, "Videoer"),
        (NexusPaths.MusicId, "Musik"),
    ];

    public IReadOnlyDictionary<string, (string Label, string PhysicalPath)> GetKnownRoots()
    {
        var roots = new Dictionary<string, (string, string)>(StringComparer.OrdinalIgnoreCase);
        foreach (var (id, label) in GetDefaultFavorites())
        {
            roots[ResolveLocationId(id)] = (label, ResolveLocationId(id));
        }

        roots[_projectsPath] = ("Projekter", _projectsPath);
        roots[_workspacesPath] = ("Arbejdsområder", _workspacesPath);
        return roots;
    }

    public IReadOnlyList<FileSystemItem> ListLocation(string locationKey, IReadOnlyList<string>? recentPaths = null)
    {
        if (string.Equals(locationKey, NexusPaths.ThisPc, StringComparison.OrdinalIgnoreCase))
        {
            return ListDrives();
        }

        if (string.Equals(locationKey, NexusPaths.Recent, StringComparison.OrdinalIgnoreCase))
        {
            return ListRecent(recentPaths ?? []);
        }

        if (!Directory.Exists(locationKey))
        {
            return [];
        }

        return ListDirectory(locationKey);
    }

    public IReadOnlyList<FileSystemItem> ListDirectory(string directoryPath)
    {
        var items = new List<FileSystemItem>();
        try
        {
            foreach (var dir in Directory.EnumerateDirectories(directoryPath))
            {
                if (ShouldSkipEntry(dir))
                {
                    continue;
                }

                items.Add(CreateDirectoryItem(new DirectoryInfo(dir)));
            }

            foreach (var file in Directory.EnumerateFiles(directoryPath))
            {
                if (ShouldSkipEntry(file))
                {
                    continue;
                }

                items.Add(CreateFileItem(new FileInfo(file)));
            }
        }
        catch (UnauthorizedAccessException)
        {
            return items;
        }
        catch (IOException)
        {
            return items;
        }

        return items
            .OrderByDescending(static item => item.IsDirectory)
            .ThenBy(static item => item.Name, StringComparer.OrdinalIgnoreCase)
            .ToList();
    }

    public string? GetParentPath(string currentPath)
    {
        if (string.Equals(currentPath, NexusPaths.ThisPc, StringComparison.OrdinalIgnoreCase) ||
            string.Equals(currentPath, NexusPaths.Recent, StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        if (IsKnownVirtualRoot(currentPath))
        {
            return null;
        }

        if (Directory.Exists(currentPath))
        {
            var parent = Directory.GetParent(currentPath);
            return parent?.FullName;
        }

        return null;
    }

    public bool IsKnownVirtualRoot(string path) =>
        string.Equals(path, NexusPaths.ThisPc, StringComparison.OrdinalIgnoreCase) ||
        string.Equals(path, NexusPaths.Recent, StringComparison.OrdinalIgnoreCase);

    public bool TryOpenWithDefaultApp(string path, out string? error)
    {
        error = null;
        try
        {
            Process.Start(new ProcessStartInfo
            {
                FileName = path,
                UseShellExecute = true,
            });
            return true;
        }
        catch (Exception ex)
        {
            error = ex.Message;
            return false;
        }
    }

    private IReadOnlyList<FileSystemItem> ListRecent(IReadOnlyList<string> recentPaths)
    {
        var items = new List<FileSystemItem>();
        foreach (var path in recentPaths)
        {
            try
            {
                if (Directory.Exists(path))
                {
                    items.Add(CreateDirectoryItem(new DirectoryInfo(path)));
                    continue;
                }

                if (File.Exists(path))
                {
                    items.Add(CreateFileItem(new FileInfo(path)));
                }
            }
            catch
            {
                // Skip inaccessible recent entries.
            }
        }

        return items;
    }

    private IReadOnlyList<FileSystemItem> ListDrives()
    {
        return DriveInfo.GetDrives()
            .Where(static drive => drive.IsReady)
            .Select(drive =>
            {
                var letter = drive.Name.TrimEnd('\\');
                var label = string.IsNullOrWhiteSpace(drive.VolumeLabel)
                    ? $"Drev ({letter.TrimEnd(':')})"
                    : $"{drive.VolumeLabel} ({letter.TrimEnd(':')})";

                return new FileSystemItem
                {
                    Name = label,
                    FullPath = drive.Name,
                    IsDirectory = true,
                    IsDrive = true,
                    KindLabel = "Drev",
                    IconPath = NexusLucideIcons.HardDrive,
                    SizeLabel = FormatBytes(drive.TotalSize - drive.AvailableFreeSpace) + " brugt",
                    ModifiedLabel = "—",
                };
            })
            .OrderBy(static item => item.Name, StringComparer.OrdinalIgnoreCase)
            .ToList();
    }

    private static FileSystemItem CreateDirectoryItem(DirectoryInfo info) =>
        new()
        {
            Name = info.Name,
            FullPath = info.FullName,
            IsDirectory = true,
            KindLabel = "Mappe",
            IconPath = NexusLucideIcons.Folder,
            ModifiedAt = info.LastWriteTime,
            ModifiedLabel = FormatDate(info.LastWriteTime),
            SizeLabel = "—",
        };

    private static FileSystemItem CreateFileItem(FileInfo info) =>
        new()
        {
            Name = info.Name,
            FullPath = info.FullName,
            IsDirectory = false,
            KindLabel = GetFileKind(info.Extension),
            IconPath = NexusLucideIcons.ForExtension(info.Extension),
            ModifiedAt = info.LastWriteTime,
            ModifiedLabel = FormatDate(info.LastWriteTime),
            SizeBytes = info.Length,
            SizeLabel = FormatBytes(info.Length),
        };

    private static bool ShouldSkipEntry(string path)
    {
        try
        {
            var attributes = File.GetAttributes(path);
            return attributes.HasFlag(FileAttributes.System);
        }
        catch
        {
            return true;
        }
    }

    private static string GetKnownFolder(Environment.SpecialFolder folder)
    {
        try
        {
            return Environment.GetFolderPath(folder);
        }
        catch
        {
            return Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
        }
    }

    private static string GetFileKind(string extension) => extension.ToLowerInvariant() switch
    {
        ".pdf" => "PDF",
        ".doc" or ".docx" => "Word-dokument",
        ".xls" or ".xlsx" => "Regneark",
        ".ppt" or ".pptx" => "Præsentation",
        ".png" or ".jpg" or ".jpeg" or ".webp" or ".gif" => "Billede",
        ".mp4" or ".mkv" or ".avi" => "Video",
        ".mp3" or ".wav" or ".flac" => "Musik",
        ".zip" or ".rar" or ".7z" => "Arkiv",
        ".txt" or ".md" => "Tekst",
        _ => "Fil",
    };

    internal static string FormatBytes(long bytes)
    {
        if (bytes < 1024)
        {
            return $"{bytes} B";
        }

        var kb = bytes / 1024d;
        if (kb < 1024)
        {
            return $"{kb.ToString("0.#", DaDk)} KB";
        }

        var mb = kb / 1024d;
        if (mb < 1024)
        {
            return $"{mb.ToString("0.#", DaDk)} MB";
        }

        var gb = mb / 1024d;
        return $"{gb.ToString("0.#", DaDk)} GB";
    }

    internal static string FormatDate(DateTime value) =>
        value.Date == DateTime.Today
            ? value.ToString("HH:mm", DaDk)
            : value.ToString("d. MMM yyyy", DaDk);
}
