using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Newtonsoft.Json;
using UnityEditor.Callbacks;

namespace Jovian.PackageSync {
    public sealed class PackageSyncWindow : EditorWindow {
        // private static PackageMapping[] defaultPackages = new PackageMapping[] {
        //     new(
        //         "Save System",
        //         "Packages/com.jovian.savesystem",
        //         @"D:\repos\unity-save-system"),
        //     new(
        //         "Zone System",
        //         "Packages/com.jovian.zonesystem",
        //         @"D:\repos\unity-zone-system")
        // };

        private static string FilePath => Path.Combine(Directory.GetParent(Application.dataPath).FullName, "ProjectSettings/PackageSyncSettings.json");
        private Vector2 scrollPosition;
        private bool hasChanges;
        private bool syncEnabled;
        private PackageMapping[] currentPackages = Array.Empty<PackageMapping>();

        [MenuItem("Jovian/Package Sync")]
        private static void ShowWindow() {
            var window = GetWindow<PackageSyncWindow>("Package Sync");
            window.minSize = new Vector2(400, 300);
        }

        [DidReloadScripts]
        private static void OnScriptsReloaded() {
            if (EditorPrefs.GetBool("PackageSync.AutoSync", false)) {
                PushToRepos();
            }
        }

        private static void PushToRepos() {
            var packages = LoadSettings();
            foreach(var package in packages) {
                SyncFiles(package.packagePath, package.repoPath, package.displayName);
            }
        }


        private static void SaveSettings(PackageMapping[] data) {
            var json = JsonConvert.SerializeObject(data);
            File.WriteAllText(FilePath, json);
        }

        private static PackageMapping[] LoadSettings() {
            if(!File.Exists(FilePath)) {
                File.Create(FilePath).Close();
                File.WriteAllText(FilePath, string.Empty);
                return Array.Empty<PackageMapping>();
            }

            var json = File.ReadAllText(FilePath);
            return JsonConvert.DeserializeObject<PackageMapping[]>(json);
        }

        private void OnEnable() {
            currentPackages = LoadSettings();
        }

        private void OnGUI() {
            scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

            EditorGUILayout.Space(8);
            EditorGUI.BeginChangeCheck();
            syncEnabled = EditorGUILayout.ToggleLeft("Enable Package Sync", syncEnabled);
            if(EditorGUI.EndChangeCheck()) {
                EditorPrefs.SetBool("PackageSync.AutoSync", syncEnabled);
                if(syncEnabled) {
                    PushToRepos();
                }
            }

            EditorGUILayout.Space(4);
            EditorGUILayout.HelpBox(
                "Sync files between Unity packages and their standalone git repos.",
                MessageType.Info);
            EditorGUILayout.Space(8);

            EditorGUILayout.LabelField("Add Packages", EditorStyles.boldLabel);

            foreach(var package in currentPackages) {
                var set = new List<string> {
                    package.displayName,
                    package.packagePath,
                    package.repoPath
                };
                EditorGUILayout.BeginHorizontal();
                EditorGUI.BeginChangeCheck();
                set[0] = EditorGUILayout.DelayedTextField(set[0]);
                set[1] = EditorGUILayout.TextField(set[1]);
                set[2] = EditorGUILayout.TextField(set[2]);
                package.selected = EditorGUILayout.Toggle(package.selected, GUILayout.Width(20));
                if(EditorGUI.EndChangeCheck()) {
                    package.displayName = set[0];
                    package.packagePath = set[1];
                    package.repoPath = set[2];
                    hasChanges = true;
                }
                EditorGUILayout.EndHorizontal();
            }
            EditorGUILayout.Space(4);
            EditorGUILayout.BeginHorizontal();
            if(GUILayout.Button("Add Package")) {
                var package = new PackageMapping(string.Empty, string.Empty, string.Empty);
                currentPackages = currentPackages.Append(package).ToArray();
                SaveSettings(currentPackages);
            }
            if(GUILayout.Button("Remove Selected")) {
                currentPackages = currentPackages.Where(p => !p.selected).ToArray();
                SaveSettings(currentPackages);
            }
            EditorGUILayout.EndHorizontal();

            if(hasChanges) {
                SaveSettings(currentPackages);
            }

            EditorGUILayout.Space(8);
            foreach(var package in currentPackages) {
                DrawPackageSection(package);
                EditorGUILayout.Space(12);
            }

            EditorGUILayout.Space(8);
            EditorGUILayout.LabelField("Bulk Actions", EditorStyles.boldLabel);

            EditorGUILayout.BeginHorizontal();
            if(GUILayout.Button("Push All to Repos", GUILayout.Height(28))) {
                foreach(var package in currentPackages) {
                    SyncFiles(package.packagePath, package.repoPath, package.displayName);
                }
            }
            if(GUILayout.Button("Pull All from Repos", GUILayout.Height(28))) {
                foreach(var package in currentPackages) {
                    SyncFiles(package.repoPath, package.packagePath, package.displayName);
                }
            }
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.EndScrollView();
        }

        private void DrawPackageSection(PackageMapping package) {
            EditorGUILayout.LabelField(package.displayName, EditorStyles.boldLabel);

            var packageExists = Directory.Exists(package.packagePath);
            var repoExists = Directory.Exists(package.repoPath);

            EditorGUILayout.LabelField("Package", packageExists ? package.packagePath : $"{package.packagePath} (missing)");
            EditorGUILayout.LabelField("Repo", repoExists ? package.repoPath : $"{package.repoPath} (missing)");

            if(!packageExists || !repoExists) {
                EditorGUILayout.HelpBox(
                    $"Cannot sync: {(!packageExists ? "package" : "repo")} directory not found.",
                    MessageType.Warning);
                return;
            }

            EditorGUILayout.BeginHorizontal();

            if(GUILayout.Button("Push to Repo →")) {
                SyncFiles(package.packagePath, package.repoPath, package.displayName);
            }

            if(GUILayout.Button("← Pull from Repo")) {
                SyncFiles(package.repoPath, package.packagePath, package.displayName);
            }

            EditorGUILayout.EndHorizontal();

            if(GUILayout.Button("Show Differences")) {
                ShowDifferences(package);
            }
        }

        private static void SyncFiles(string sourcePath, string destPath, string displayName) {
            var fullSource = Path.GetFullPath(sourcePath);
            var fullDest = Path.GetFullPath(destPath);

            var sourceFiles = Directory.GetFiles(fullSource, "*", SearchOption.AllDirectories)
                .Where(f => !IsGitPath(f) && !f.EndsWith(".meta"))
                .ToArray();

            var copied = 0;
            var skipped = 0;

            foreach(var sourceFile in sourceFiles) {
                var relativePath = sourceFile.Substring(fullSource.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
                var destFile = Path.Combine(fullDest, relativePath);

                if(IsFileIdentical(sourceFile, destFile)) {
                    skipped++;
                    continue;
                }

                var destDir = Path.GetDirectoryName(destFile);
                if(!Directory.Exists(destDir)) {
                    if(destDir != null) {
                        Directory.CreateDirectory(destDir);
                    }
                }

                File.Copy(sourceFile, destFile, true);
                copied++;
            }

            // Remove files in dest that no longer exist in source
            var destFiles = Directory.GetFiles(fullDest, "*", SearchOption.AllDirectories)
                .Where(f => !IsGitPath(f) && !f.EndsWith(".meta"))
                .ToArray();

            var deleted = 0;
            foreach(var destFile in destFiles) {
                var relativePath = destFile.Substring(fullDest.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
                var sourceFile = Path.Combine(fullSource, relativePath);

                if(!File.Exists(sourceFile)) {
                    File.Delete(destFile);
                    deleted++;
                }
            }

            var direction = fullSource == Path.GetFullPath(destPath) ? "Pulled" : "Pushed";
            Debug.Log($"[PackageSync] {displayName}: {direction} {copied} files, {skipped} unchanged, {deleted} removed.");
            AssetDatabase.Refresh();
        }

        private static void ShowDifferences(PackageMapping package) {
            var fullPackage = Path.GetFullPath(package.packagePath);
            var fullRepo = Path.GetFullPath(package.repoPath);

            var packageFiles = Directory.GetFiles(fullPackage, "*", SearchOption.AllDirectories)
                .Where(f => !IsGitPath(f) && !f.EndsWith(".meta"))
                .Select(f => f.Substring(fullPackage.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
                .ToArray();

            var repoFiles = Directory.GetFiles(fullRepo, "*", SearchOption.AllDirectories)
                .Where(f => !IsGitPath(f) && !f.EndsWith(".meta"))
                .Select(f => f.Substring(fullRepo.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
                .ToArray();

            var onlyInPackage = packageFiles.Except(repoFiles).ToArray();
            var onlyInRepo = repoFiles.Except(packageFiles).ToArray();
            var common = packageFiles.Intersect(repoFiles).ToArray();

            var modified = 0;
            foreach(var file in common) {
                var packageFile = Path.Combine(fullPackage, file);
                var repoFile = Path.Combine(fullRepo, file);

                if(!IsFileIdentical(packageFile, repoFile)) {
                    Debug.Log($"[PackageSync] Modified: {file}");
                    modified++;
                }
            }

            foreach(var file in onlyInPackage) {
                Debug.Log($"[PackageSync] Only in package: {file}");
            }

            foreach(var file in onlyInRepo) {
                Debug.Log($"[PackageSync] Only in repo: {file}");
            }

            var total = modified + onlyInPackage.Length + onlyInRepo.Length;
            if(total == 0) {
                Debug.Log($"[PackageSync] {package.displayName}: In sync — no differences found.");
            }
            else {
                Debug.Log($"[PackageSync] {package.displayName}: {modified} modified, {onlyInPackage.Length} only in package, {onlyInRepo.Length} only in repo.");
            }
        }

        private static bool IsGitPath(string path) {
            return path.Replace('\\', '/').Contains("/.git/") || path.Replace('\\', '/').Contains("/.git");
        }

        private static bool IsFileIdentical(string fileA, string fileB) {
            if(!File.Exists(fileA) || !File.Exists(fileB)) {
                return false;
            }

            var infoA = new FileInfo(fileA);
            var infoB = new FileInfo(fileB);

            if(infoA.Length != infoB.Length) {
                return false;
            }

            var bytesA = File.ReadAllBytes(fileA);
            var bytesB = File.ReadAllBytes(fileB);

            return bytesA.SequenceEqual(bytesB);
        }

        private sealed class PackageMapping {
            public string displayName;
            public string packagePath;
            public string repoPath;
            public bool selected;

            public PackageMapping(string displayName, string packagePath, string repoPath) {
                this.displayName = displayName;
                this.packagePath = packagePath;
                this.repoPath = repoPath;
            }
        }
    }
}
