using System.IO;
using System.IO.Compression;
using System.Text.Json;
using NexusShell.Models;

namespace NexusShell.Services;

public sealed class NexusPackageRepository
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        WriteIndented = true,
    };

    private readonly NexusPackageReader _reader;
    private readonly NexusPackageValidator _validator;
    private readonly string _rootDir;
    private readonly string _repositoryDir;
    private readonly string _installedDir;
    private readonly string _rollbackDir;
    private readonly string _indexPath;
    private readonly string _bundledPackagesDir;
    private readonly string _bundledStorePath;

    private NexusPackageIndex _index = new();
    private Dictionary<string, NexusPackageManifest> _catalog = new(StringComparer.OrdinalIgnoreCase);
    private Dictionary<string, NexusInstalledPackageRecord> _installed = new(StringComparer.OrdinalIgnoreCase);

    public NexusPackageRepository(
        NexusPackageReader? reader = null,
        NexusPackageValidator? validator = null,
        string? rootDir = null)
    {
        _reader = reader ?? new NexusPackageReader();
        _validator = validator ?? new NexusPackageValidator();
        _rootDir = rootDir ?? Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "NinjaNexus",
            "packages");
        _repositoryDir = Path.Combine(_rootDir, "repository");
        _installedDir = Path.Combine(_rootDir, "installed");
        _rollbackDir = Path.Combine(_rootDir, "rollback");
        _indexPath = Path.Combine(_rootDir, "index.json");
        _bundledPackagesDir = Path.Combine(AppContext.BaseDirectory, "Assets", "Packages");
        _bundledStorePath = Path.Combine(AppContext.BaseDirectory, "Assets", "store-repository.json");
    }

    public string RootDirectory => _rootDir;

    public string RepositoryDirectory => _repositoryDir;

    public string InstalledDirectory => _installedDir;

    public string RollbackDirectory => _rollbackDir;

    public string? ResolvePackageFilePath(string packageId)
    {
        var manifest = GetById(packageId);
        if (manifest is null)
        {
            return null;
        }

        var preferred = Path.Combine(_repositoryDir, $"{manifest.Id}-{manifest.Version}.nxp");
        if (File.Exists(preferred))
        {
            return preferred;
        }

        return Directory.EnumerateFiles(_repositoryDir, "*.nxp")
            .Select(path => new { Path = path, Manifest = _reader.ReadManifestFromPackage(path) })
            .Where(x => x.Manifest is not null &&
                        string.Equals(x.Manifest.Id, packageId, StringComparison.OrdinalIgnoreCase))
            .OrderByDescending(x => x.Manifest!.Version, Comparer<string>.Create(NexusPackageVersionComparer.Compare))
            .Select(x => x.Path)
            .FirstOrDefault();
    }

    public void Load()
    {
        EnsureLayout();
        SeedBundledPackages();
        RebuildCatalog();
        LoadInstalledRecords();
    }

    public IReadOnlyList<NexusPackageManifest> GetAll() =>
        _catalog.Values.OrderBy(p => p.Name, StringComparer.OrdinalIgnoreCase).ToList();

    public NexusPackageManifest? GetById(string id) =>
        _catalog.TryGetValue(id, out var manifest) ? manifest : null;

    public NexusPackageIndex GetIndex() => _index;

    public NexusInstalledPackageRecord? GetInstalledRecord(string packageId) =>
        _installed.TryGetValue(packageId, out var record) ? record : null;

    public IReadOnlyList<NexusInstalledPackageRecord> GetInstalledRecords() =>
        _installed.Values.OrderBy(r => r.Id, StringComparer.OrdinalIgnoreCase).ToList();

    public void SaveInstalledRecord(NexusInstalledPackageRecord record)
    {
        _installed[record.Id] = record;
        var path = Path.Combine(_installedDir, $"{record.Id}.json");
        var json = JsonSerializer.Serialize(record, JsonOptions);
        File.WriteAllText(path, json);
    }

    public void RemoveInstalledRecord(string packageId)
    {
        _installed.Remove(packageId);
        var path = Path.Combine(_installedDir, $"{packageId}.json");
        if (File.Exists(path))
        {
            File.Delete(path);
        }
    }

    private void EnsureLayout()
    {
        Directory.CreateDirectory(_rootDir);
        Directory.CreateDirectory(_repositoryDir);
        Directory.CreateDirectory(_installedDir);
        Directory.CreateDirectory(_rollbackDir);
    }

    private void SeedBundledPackages()
    {
        if (Directory.Exists(_bundledPackagesDir))
        {
            foreach (var manifestDir in Directory.GetDirectories(_bundledPackagesDir))
            {
                var manifest = _reader.ReadManifestFromDirectory(manifestDir);
                if (manifest is null || !_validator.Validate(manifest).IsValid)
                {
                    continue;
                }

                var packageFileName = $"{manifest.Id}-{manifest.Version}.nxp";
                var packagePath = Path.Combine(_repositoryDir, packageFileName);
                if (!File.Exists(packagePath))
                {
                    CreatePackageArchive(manifestDir, packagePath);
                }
            }
        }

        MergeBundledStoreRepository();
    }

    private void MergeBundledStoreRepository()
    {
        if (!File.Exists(_bundledStorePath))
        {
            return;
        }

        try
        {
            var json = File.ReadAllText(_bundledStorePath);
            var doc = JsonSerializer.Deserialize<StoreRepositoryDoc>(json, JsonOptions);
            if (doc?.Packages is null)
            {
                return;
            }

            foreach (var manifest in doc.Packages)
            {
                if (!_validator.Validate(manifest).IsValid)
                {
                    continue;
                }

                var packageFileName = $"{manifest.Id}-{manifest.Version}.nxp";
                var packagePath = Path.Combine(_repositoryDir, packageFileName);
                if (!File.Exists(packagePath))
                {
                    CreateManifestOnlyArchive(manifest, packagePath);
                }
            }
        }
        catch
        {
            // Keep repository usable when bundled store file is malformed.
        }
    }

    private void RebuildCatalog()
    {
        _catalog.Clear();
        var indexEntries = new List<NexusPackageIndexEntry>();

        foreach (var packagePath in Directory.EnumerateFiles(_repositoryDir, "*.nxp"))
        {
            var manifest = _reader.ReadManifestFromPackage(packagePath);
            var validation = _validator.Validate(manifest);
            if (!validation.IsValid || manifest is null)
            {
                continue;
            }

            if (!_catalog.TryGetValue(manifest.Id, out var existing) ||
                NexusPackageVersionComparer.IsLessThan(existing.Version, manifest.Version))
            {
                _catalog[manifest.Id] = manifest;
            }

            indexEntries.Add(new NexusPackageIndexEntry
            {
                Id = manifest.Id,
                Version = manifest.Version,
                PackageFile = Path.GetFileName(packagePath),
                CachedAt = File.GetLastWriteTimeUtc(packagePath),
            });
        }

        _index = new NexusPackageIndex
        {
            Version = "1.0",
            UpdatedAt = DateTimeOffset.UtcNow,
            Packages = indexEntries.OrderBy(e => e.Id, StringComparer.OrdinalIgnoreCase).ToList(),
        };

        var indexJson = JsonSerializer.Serialize(_index, JsonOptions);
        File.WriteAllText(_indexPath, indexJson);
    }

    private void LoadInstalledRecords()
    {
        _installed.Clear();

        foreach (var recordPath in Directory.EnumerateFiles(_installedDir, "*.json"))
        {
            try
            {
                var json = File.ReadAllText(recordPath);
                var record = JsonSerializer.Deserialize<NexusInstalledPackageRecord>(json, JsonOptions);
                if (record is null || string.IsNullOrWhiteSpace(record.Id))
                {
                    continue;
                }

                _installed[record.Id] = record;
            }
            catch
            {
                // Skip corrupt installed metadata.
            }
        }

        if (_installed.Count == 0)
        {
            SeedDefaultInstalledRecords();
        }
    }

    private void SeedDefaultInstalledRecords()
    {
        var defaults = new[]
        {
            new NexusInstalledPackageRecord
            {
                Id = "nexus-files",
                InstalledVersion = "1.0.0",
                InstalledAt = DateTimeOffset.UtcNow.AddDays(-14),
                State = NexusPackageState.Installed,
            },
            new NexusInstalledPackageRecord
            {
                Id = "nexus-browser",
                InstalledVersion = "1.0.0",
                InstalledAt = DateTimeOffset.UtcNow.AddDays(-7),
                State = NexusPackageState.Installed,
            },
        };

        foreach (var record in defaults)
        {
            SaveInstalledRecord(record);
        }
    }

    private static void CreatePackageArchive(string sourceDir, string packagePath)
    {
        if (File.Exists(packagePath))
        {
            File.Delete(packagePath);
        }

        ZipFile.CreateFromDirectory(sourceDir, packagePath, CompressionLevel.Optimal, includeBaseDirectory: false);
    }

    private static void CreateManifestOnlyArchive(NexusPackageManifest manifest, string packagePath)
    {
        var tempDir = Path.Combine(Path.GetTempPath(), "nxp-" + Guid.NewGuid().ToString("N"));
        try
        {
            Directory.CreateDirectory(tempDir);
            var manifestPath = Path.Combine(tempDir, "manifest.json");
            var json = JsonSerializer.Serialize(manifest, JsonOptions);
            File.WriteAllText(manifestPath, json);

            if (File.Exists(packagePath))
            {
                File.Delete(packagePath);
            }

            ZipFile.CreateFromDirectory(tempDir, packagePath, CompressionLevel.Optimal, includeBaseDirectory: false);
        }
        finally
        {
            if (Directory.Exists(tempDir))
            {
                Directory.Delete(tempDir, recursive: true);
            }
        }
    }

    private sealed class StoreRepositoryDoc
    {
        public List<NexusPackageManifest> Packages { get; set; } = [];
    }
}
