using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Microsoft.Win32;
using Newtonsoft.Json.Linq;

namespace MosswartMassacre
{
    internal enum MetaFileSyncStatus
    {
        Unknown,
        NotDownloaded,
        UpToDate,
        UpdateAvailable,
        Error
    }

    internal sealed class MetaFileEntry
    {
        public string RelativePath { get; set; }
        public string FileName { get; set; }
        public string Extension { get; set; }
        public string RemoteBlobSha { get; set; }
        public string LocalPath { get; set; }
        public bool LocalExists { get; set; }
        public string LocalSha256 { get; set; }
        public string RemoteSha256 { get; set; }
        public MetaFileSyncStatus Status { get; set; }
        public string StatusText { get; set; }
    }

    internal sealed class MetaDownloadResult
    {
        public string RelativePath { get; set; }
        public bool Downloaded { get; set; }
        public bool SkippedAsCurrent { get; set; }
        public bool BackupCreated { get; set; }
        public string Message { get; set; }
    }

    internal static class MetaSyncManager
    {
        private const string RepoOwner = "SawatoMosswartsEnjoyersClub";
        private const string RepoName = "metas";
        private const string BranchName = "main";
        private const string ApiBaseUrl = "https://git.snakedesert.se/api/v1";
        private const string RawBaseUrl = "https://git.snakedesert.se/SawatoMosswartsEnjoyersClub/metas/raw/branch/main";

        private static readonly HttpClient HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
        private static readonly object Sync = new object();
        private static List<MetaFileEntry> _cachedFiles = new List<MetaFileEntry>();

        public static DateTime LastCatalogRefreshUtc { get; private set; } = DateTime.MinValue;
        public static DateTime LastStatusCheckUtc { get; private set; } = DateTime.MinValue;

        public static IReadOnlyList<MetaFileEntry> GetCachedFiles()
        {
            lock (Sync)
            {
                return _cachedFiles
                    .Select(CloneEntry)
                    .ToList()
                    .AsReadOnly();
            }
        }

        public static string GetResolvedVtankPath()
        {
            try
            {
                if (!string.IsNullOrWhiteSpace(PluginSettings.Instance?.VTankProfilesPath) &&
                    Directory.Exists(PluginSettings.Instance.VTankProfilesPath))
                {
                    return PluginSettings.Instance.VTankProfilesPath;
                }

                var defaultPath = @"C:\Games\VirindiPlugins\VirindiTank\";

                try
                {
                    var regKey = Registry.LocalMachine.OpenSubKey("Software\\Decal\\Plugins\\{642F1F48-16BE-48BF-B1D4-286652C4533E}");
                    if (regKey != null)
                    {
                        var profilePath = regKey.GetValue("ProfilePath")?.ToString();
                        if (!string.IsNullOrWhiteSpace(profilePath) && Directory.Exists(profilePath))
                        {
                            return profilePath;
                        }
                    }
                }
                catch
                {
                }

                return defaultPath;
            }
            catch
            {
                return string.Empty;
            }
        }

        public static async Task<int> RefreshCatalogAsync()
        {
            var remoteEntries = await FetchRemoteTreeEntriesAsync();
            var vtankPath = GetResolvedVtankPath();

            var result = remoteEntries
                .Where(path => path.EndsWith(".met", StringComparison.OrdinalIgnoreCase) ||
                               path.EndsWith(".nav", StringComparison.OrdinalIgnoreCase))
                .OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
                .Select(path =>
                {
                    var localPath = string.IsNullOrWhiteSpace(vtankPath)
                        ? string.Empty
                        : Path.Combine(vtankPath, Path.GetFileName(path));
                    var exists = !string.IsNullOrWhiteSpace(localPath) && File.Exists(localPath);
                    return new MetaFileEntry
                    {
                        RelativePath = path,
                        FileName = Path.GetFileName(path),
                        Extension = Path.GetExtension(path)?.TrimStart('.').ToLowerInvariant() ?? string.Empty,
                        RemoteBlobSha = string.Empty,
                        LocalPath = localPath,
                        LocalExists = exists,
                        Status = exists ? MetaFileSyncStatus.Unknown : MetaFileSyncStatus.NotDownloaded,
                        StatusText = exists ? "Unknown" : "Not downloaded"
                    };
                })
                .ToList();

            lock (Sync)
            {
                _cachedFiles = result;
                LastCatalogRefreshUtc = DateTime.UtcNow;
            }

            return result.Count;
        }

        public static async Task<(int upToDate, int needsUpdate, int notDownloaded, int errors)> CheckStatusesAsync()
        {
            List<MetaFileEntry> workingSet;
            lock (Sync)
            {
                workingSet = _cachedFiles.Select(CloneEntry).ToList();
            }

            if (workingSet.Count == 0)
            {
                await RefreshCatalogAsync();
                lock (Sync)
                {
                    workingSet = _cachedFiles.Select(CloneEntry).ToList();
                }
            }

            foreach (var entry in workingSet)
            {
                try
                {
                    if (string.IsNullOrWhiteSpace(entry.LocalPath) || !File.Exists(entry.LocalPath))
                    {
                        entry.LocalExists = false;
                        entry.LocalSha256 = string.Empty;
                        entry.Status = MetaFileSyncStatus.NotDownloaded;
                        entry.StatusText = "Not downloaded";
                        continue;
                    }

                    entry.LocalExists = true;
                    entry.LocalSha256 = CalculateFileSha256(entry.LocalPath);

                    var remoteBytes = await DownloadRemoteFileBytesAsync(entry.RelativePath);
                    entry.RemoteSha256 = CalculateSha256(remoteBytes);

                    if (string.Equals(entry.LocalSha256, entry.RemoteSha256, StringComparison.OrdinalIgnoreCase))
                    {
                        entry.Status = MetaFileSyncStatus.UpToDate;
                        entry.StatusText = "Up to date";
                    }
                    else
                    {
                        entry.Status = MetaFileSyncStatus.UpdateAvailable;
                        entry.StatusText = "Update available";
                    }
                }
                catch (Exception ex)
                {
                    entry.Status = MetaFileSyncStatus.Error;
                    entry.StatusText = "Error";
                    PluginCore.WriteToChat($"[Metas] Status check failed for {entry.RelativePath}: {ex.Message}");
                }
            }

            lock (Sync)
            {
                _cachedFiles = workingSet;
                LastStatusCheckUtc = DateTime.UtcNow;
            }

            var upToDate = workingSet.Count(x => x.Status == MetaFileSyncStatus.UpToDate);
            var needsUpdate = workingSet.Count(x => x.Status == MetaFileSyncStatus.UpdateAvailable);
            var notDownloaded = workingSet.Count(x => x.Status == MetaFileSyncStatus.NotDownloaded);
            var errors = workingSet.Count(x => x.Status == MetaFileSyncStatus.Error);

            return (upToDate, needsUpdate, notDownloaded, errors);
        }

        public static async Task<MetaDownloadResult> DownloadFileAsync(string relativePath)
        {
            var result = new MetaDownloadResult
            {
                RelativePath = relativePath,
                Downloaded = false,
                SkippedAsCurrent = false,
                BackupCreated = false,
                Message = string.Empty
            };

            if (string.IsNullOrWhiteSpace(relativePath))
            {
                result.Message = "No file selected.";
                return result;
            }

            var vtankPath = GetResolvedVtankPath();
            if (string.IsNullOrWhiteSpace(vtankPath))
            {
                result.Message = "VTank path could not be resolved.";
                return result;
            }

            if (!Directory.Exists(vtankPath))
            {
                Directory.CreateDirectory(vtankPath);
            }

            var localPath = Path.Combine(vtankPath, Path.GetFileName(relativePath));
            var remoteBytes = await DownloadRemoteFileBytesAsync(relativePath);
            var remoteHash = CalculateSha256(remoteBytes);

            if (File.Exists(localPath))
            {
                var localHash = CalculateFileSha256(localPath);
                if (string.Equals(localHash, remoteHash, StringComparison.OrdinalIgnoreCase))
                {
                    result.SkippedAsCurrent = true;
                    result.Message = "Already up to date.";
                    await UpdateSingleCachedEntryAsync(relativePath, localPath, localHash, remoteHash, MetaFileSyncStatus.UpToDate, "Up to date");
                    return result;
                }

                var backupPath = localPath + ".bak";
                File.Copy(localPath, backupPath, true);
                result.BackupCreated = true;
            }

            var tempPath = localPath + ".tmp";
            File.WriteAllBytes(tempPath, remoteBytes);

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

            File.Move(tempPath, localPath);

            result.Downloaded = true;
            result.Message = result.BackupCreated ? "Downloaded and updated (.bak created)." : "Downloaded.";

            await UpdateSingleCachedEntryAsync(relativePath, localPath, CalculateFileSha256(localPath), remoteHash, MetaFileSyncStatus.UpToDate, "Up to date");

            return result;
        }

        public static async Task<(int downloaded, int skipped, int failed, int backups)> DownloadOutdatedAsync()
        {
            List<MetaFileEntry> current;
            lock (Sync)
            {
                current = _cachedFiles.Select(CloneEntry).ToList();
            }

            if (current.Count == 0)
            {
                await RefreshCatalogAsync();
                current = GetCachedFiles().Select(CloneEntry).ToList();
            }

            var candidates = current
                .Where(x => x.Status == MetaFileSyncStatus.UpdateAvailable || x.Status == MetaFileSyncStatus.NotDownloaded || x.Status == MetaFileSyncStatus.Unknown)
                .OrderBy(x => x.RelativePath, StringComparer.OrdinalIgnoreCase)
                .ToList();

            var downloaded = 0;
            var skipped = 0;
            var failed = 0;
            var backups = 0;

            foreach (var entry in candidates)
            {
                try
                {
                    var result = await DownloadFileAsync(entry.RelativePath);
                    if (result.Downloaded) downloaded++;
                    if (result.SkippedAsCurrent) skipped++;
                    if (result.BackupCreated) backups++;
                }
                catch (Exception ex)
                {
                    failed++;
                    PluginCore.WriteToChat($"[Metas] Download failed for {entry.RelativePath}: {ex.Message}");
                    await UpdateSingleCachedEntryAsync(entry.RelativePath, entry.LocalPath, entry.LocalSha256, entry.RemoteSha256, MetaFileSyncStatus.Error, "Error");
                }
            }

            return (downloaded, skipped, failed, backups);
        }

        private static async Task<List<string>> FetchRemoteTreeEntriesAsync()
        {
            var treeUrl = $"{ApiBaseUrl}/repos/{RepoOwner}/{RepoName}/git/trees/{BranchName}?recursive=1";
            using (var response = await HttpClient.GetAsync(treeUrl))
            {
                response.EnsureSuccessStatusCode();
                var json = await response.Content.ReadAsStringAsync();
                var parsed = JObject.Parse(json);
                var tree = parsed["tree"] as JArray;
                if (tree == null)
                {
                    return new List<string>();
                }

                return tree
                    .Where(x => string.Equals(x["type"]?.ToString(), "blob", StringComparison.OrdinalIgnoreCase))
                    .Select(x => x["path"]?.ToString())
                    .Where(x => !string.IsNullOrWhiteSpace(x))
                    .ToList();
            }
        }

        private static async Task<byte[]> DownloadRemoteFileBytesAsync(string relativePath)
        {
            var escapedPath = string.Join("/", (relativePath ?? string.Empty)
                .Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(Uri.EscapeDataString));
            var fileUrl = $"{RawBaseUrl}/{escapedPath}";
            return await HttpClient.GetByteArrayAsync(fileUrl);
        }

        private static string CalculateFileSha256(string path)
        {
            using (var stream = File.OpenRead(path))
            {
                using (var sha = SHA256.Create())
                {
                    return ToLowerHex(sha.ComputeHash(stream));
                }
            }
        }

        private static string CalculateSha256(byte[] data)
        {
            using (var sha = SHA256.Create())
            {
                return ToLowerHex(sha.ComputeHash(data));
            }
        }

        private static string ToLowerHex(byte[] hash)
        {
            return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();
        }

        private static async Task UpdateSingleCachedEntryAsync(string relativePath, string localPath, string localHash, string remoteHash, MetaFileSyncStatus status, string statusText)
        {
            await Task.Run(() =>
            {
                lock (Sync)
                {
                    var target = _cachedFiles.FirstOrDefault(x => string.Equals(x.RelativePath, relativePath, StringComparison.OrdinalIgnoreCase));
                    if (target == null)
                    {
                        return;
                    }

                    target.LocalPath = localPath;
                    target.LocalExists = !string.IsNullOrWhiteSpace(localPath) && File.Exists(localPath);
                    target.LocalSha256 = localHash ?? string.Empty;
                    target.RemoteSha256 = remoteHash ?? string.Empty;
                    target.Status = status;
                    target.StatusText = statusText ?? "Unknown";
                }
            });
        }

        private static MetaFileEntry CloneEntry(MetaFileEntry entry)
        {
            return new MetaFileEntry
            {
                RelativePath = entry.RelativePath,
                FileName = entry.FileName,
                Extension = entry.Extension,
                RemoteBlobSha = entry.RemoteBlobSha,
                LocalPath = entry.LocalPath,
                LocalExists = entry.LocalExists,
                LocalSha256 = entry.LocalSha256,
                RemoteSha256 = entry.RemoteSha256,
                Status = entry.Status,
                StatusText = entry.StatusText
            };
        }
    }
}
