using System.IO;
using NexusShell.Models;

namespace NexusShell.Services;

public sealed class NexusMediaLibraryService
{
    public static readonly IReadOnlySet<string> VideoExtensions =
        new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            ".mp4", ".mkv", ".mov", ".avi", ".webm",
        };

    public static readonly IReadOnlySet<string> AudioExtensions =
        new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            ".mp3", ".wav", ".flac", ".aac", ".ogg",
        };

    public static readonly IReadOnlySet<string> SupportedExtensions =
        VideoExtensions.Concat(AudioExtensions).ToHashSet(StringComparer.OrdinalIgnoreCase);

    private readonly string _videosPath;
    private readonly string _musicPath;
    private readonly string _downloadsPath;
    private readonly NexusMediaMetadataService _metadata;
    private IReadOnlyList<NexusMediaItem> _cachedItems = [];
    private DateTime _lastScanUtc = DateTime.MinValue;

    public NexusMediaLibraryService(NexusMediaMetadataService metadata)
    {
        _metadata = metadata;
        _videosPath = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos);
        _musicPath = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);
        var profile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
        _downloadsPath = Path.Combine(profile, "Downloads");
    }

    public string VideosPath => _videosPath;
    public string MusicPath => _musicPath;
    public string DownloadsPath => _downloadsPath;

    public bool IsSupportedExtension(string path) =>
        SupportedExtensions.Contains(Path.GetExtension(path));

    public NexusMediaKind GetKind(string path) =>
        VideoExtensions.Contains(Path.GetExtension(path)) ? NexusMediaKind.Video : NexusMediaKind.Audio;

    public IReadOnlyList<(NexusMediaSection Section, string Path, string Label)> GetScanRoots() =>
    [
        (NexusMediaSection.Videos, _videosPath, "Videoer"),
        (NexusMediaSection.Music, _musicPath, "Musik"),
        (NexusMediaSection.Folders, _downloadsPath, "Downloads"),
    ];

    public IReadOnlyList<NexusMediaItem> ScanLibrary(bool forceRefresh = false)
    {
        if (!forceRefresh && _cachedItems.Count > 0 &&
            DateTime.UtcNow - _lastScanUtc < TimeSpan.FromMinutes(2))
        {
            return _cachedItems;
        }

        var items = new List<NexusMediaItem>();
        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        foreach (var (_, rootPath, label) in GetScanRoots())
        {
            if (!Directory.Exists(rootPath))
            {
                continue;
            }

            ScanDirectory(rootPath, label, items, seen, recursive: true);
        }

        _cachedItems = items
            .OrderByDescending(static i => i.ModifiedUtc)
            .ToList();
        _lastScanUtc = DateTime.UtcNow;
        return _cachedItems;
    }

    public IReadOnlyList<NexusMediaItem> GetItemsForSection(NexusMediaSection section, IReadOnlyList<NexusMediaItem>? source = null)
    {
        var all = source ?? ScanLibrary();
        return section switch
        {
            NexusMediaSection.Videos => all.Where(static i => i.Kind == NexusMediaKind.Video).ToList(),
            NexusMediaSection.Music => all.Where(static i => i.Kind == NexusMediaKind.Audio).ToList(),
            NexusMediaSection.Favorites => all.Where(static i => i.IsFavorite).ToList(),
            NexusMediaSection.Recent => all.OrderByDescending(static i => i.ModifiedUtc).Take(200).ToList(),
            NexusMediaSection.Folders => all,
            _ => all,
        };
    }

    public IReadOnlyList<NexusMediaItem> GetItemsInFolder(string folderPath, IReadOnlyList<NexusMediaItem>? source = null)
    {
        var normalized = Path.GetFullPath(folderPath).TrimEnd('\\');
        return (source ?? ScanLibrary())
            .Where(i => Path.GetFullPath(i.FolderPath).TrimEnd('\\')
                .StartsWith(normalized, StringComparison.OrdinalIgnoreCase))
            .ToList();
    }

    public IReadOnlyList<NexusMediaFolderSummary> GetFolderSummaries(IReadOnlyList<NexusMediaItem>? source = null)
    {
        return (source ?? ScanLibrary())
            .GroupBy(static i => i.FolderPath, StringComparer.OrdinalIgnoreCase)
            .Select(g =>
            {
                var latest = g.OrderByDescending(static i => i.ModifiedUtc).First();
                return new NexusMediaFolderSummary
                {
                    FolderPath = g.Key,
                    Label = latest.FolderLabel,
                    ItemCount = g.Count(),
                    TotalBytes = g.Sum(static i => i.FileSizeBytes),
                    LatestModifiedUtc = g.Max(static i => i.ModifiedUtc),
                    DominantKind = g.GroupBy(static i => i.Kind).OrderByDescending(gg => gg.Count()).First().Key,
                };
            })
            .OrderByDescending(static f => f.ItemCount)
            .ToList();
    }

    public IReadOnlyList<NexusMediaItem> GetRecentItems(int count, IReadOnlyList<NexusMediaItem>? source = null) =>
        (source ?? ScanLibrary()).OrderByDescending(static i => i.ModifiedUtc).Take(count).ToList();

    public IReadOnlyList<NexusMediaItem> GetNewItems(int count, IReadOnlyList<NexusMediaItem>? source = null) =>
        (source ?? ScanLibrary()).OrderByDescending(static i => i.CreatedUtc).Take(count).ToList();

    public IReadOnlyList<NexusMediaItem> SearchLocal(string query, IReadOnlyList<NexusMediaItem> source)
    {
        if (string.IsNullOrWhiteSpace(query))
        {
            return source;
        }

        var term = query.Trim();
        return source
            .Where(i => i.FileName.Contains(term, StringComparison.OrdinalIgnoreCase) ||
                        i.Title.Contains(term, StringComparison.OrdinalIgnoreCase) ||
                        i.Artist.Contains(term, StringComparison.OrdinalIgnoreCase) ||
                        i.Album.Contains(term, StringComparison.OrdinalIgnoreCase) ||
                        i.FolderLabel.Contains(term, StringComparison.OrdinalIgnoreCase))
            .OrderBy(static i => i.Title, StringComparer.OrdinalIgnoreCase)
            .ToList();
    }

    public string GetUnsupportedFormatMessage(string path)
    {
        var ext = Path.GetExtension(path);
        var video = string.Join(", ", VideoExtensions.Select(static e => e.TrimStart('.')).OrderBy(static e => e));
        var audio = string.Join(", ", AudioExtensions.Select(static e => e.TrimStart('.')).OrderBy(static e => e));
        return $"Formatet \"{ext}\" understøttes ikke i Nexus Media.\n\nVideo: {video.ToUpperInvariant()}\nLyd: {audio.ToUpperInvariant()}";
    }

    public NexusMediaItem? FindByPath(string path, IReadOnlyList<NexusMediaItem>? source = null)
    {
        var full = Path.GetFullPath(path);
        return (source ?? ScanLibrary())
            .FirstOrDefault(i => string.Equals(Path.GetFullPath(i.FullPath), full, StringComparison.OrdinalIgnoreCase));
    }

    private void ScanDirectory(
        string directoryPath,
        string rootLabel,
        List<NexusMediaItem> items,
        HashSet<string> seen,
        bool recursive)
    {
        IEnumerable<string> files;
        try
        {
            files = Directory.EnumerateFiles(directoryPath);
        }
        catch
        {
            return;
        }

        foreach (var filePath in files)
        {
            if (!IsSupportedExtension(filePath) || !seen.Add(filePath))
            {
                continue;
            }

            var item = TryBuildItem(filePath, rootLabel);
            if (item is not null)
            {
                items.Add(item);
            }
        }

        if (!recursive)
        {
            return;
        }

        IEnumerable<string> subdirs;
        try
        {
            subdirs = Directory.EnumerateDirectories(directoryPath);
        }
        catch
        {
            return;
        }

        foreach (var subdir in subdirs)
        {
            ScanDirectory(subdir, rootLabel, items, seen, recursive: true);
        }
    }

    public bool TryBuildItemFromPath(string filePath, out NexusMediaItem? item)
    {
        item = TryBuildItem(filePath, "Fil");
        return item is not null;
    }

    private NexusMediaItem? TryBuildItem(string filePath, string rootLabel)
    {
        try
        {
            var info = new FileInfo(filePath);
            if (!info.Exists)
            {
                return null;
            }

            var folderPath = info.DirectoryName ?? string.Empty;
            var folderLabel = BuildFolderLabel(folderPath, rootLabel);
            var kind = GetKind(filePath);
            var meta = _metadata.ProbeFile(filePath, kind);

            return new NexusMediaItem
            {
                FullPath = info.FullName,
                FileName = info.Name,
                Title = meta.Title,
                FolderPath = folderPath,
                FolderLabel = folderLabel,
                FileSizeBytes = info.Length,
                CreatedUtc = info.CreationTimeUtc,
                ModifiedUtc = info.LastWriteTimeUtc,
                Format = info.Extension.TrimStart('.').ToUpperInvariant(),
                Extension = info.Extension,
                Kind = kind,
                Artist = meta.Artist,
                Album = meta.Album,
                Duration = meta.Duration,
                VideoWidth = meta.VideoWidth,
                VideoHeight = meta.VideoHeight,
                Codec = meta.Codec,
                BitrateKbps = meta.BitrateKbps,
                IsFavorite = false,
            };
        }
        catch
        {
            return null;
        }
    }

    private static string BuildFolderLabel(string folderPath, string rootLabel)
    {
        if (string.IsNullOrWhiteSpace(folderPath))
        {
            return rootLabel;
        }

        return $"{rootLabel} › {Path.GetFileName(folderPath.TrimEnd('\\'))}";
    }
}
