using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using AssetBank.Editor.SceneSchema;
using AssetBank.Editor.SchemaConverter;
using AssetBank.Editor.Tools;
using AssetBank.Settings;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Video;

namespace AssetBank.DockableWindow
{
    public class ProjectExporterController
    {
        private static readonly string ProjectRoot = Path.GetDirectoryName(Application.dataPath);
        private readonly ProjectExporterSettings _settings;

        public ProjectExporterController()
        {
            _settings = ProjectExporterSettings.GetOrCreateSettings();
        }

        // Finds assets based on a search filter (e.g., "t:Scene") and builds a hierarchical model.
        public static AssetModel GetAssets(string filter, string category)
        {
            var root = new AssetModel("Assets", "Assets", category);
            var assetGuids = AssetDatabase.FindAssets(filter, new[] { "Assets" });

            foreach (var guid in assetGuids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);

                if (filter.Contains("t:ScriptableObject") && !path.EndsWith(".asset"))
                {
                    continue;
                }

                var parts = path.Split('/');
                var currentNode = root;

                for (int i = 1; i < parts.Length; i++) // Start at 1 to skip "Assets"
                {
                    var part = parts[i];
                    var child = currentNode.Children.FirstOrDefault(c => c.Name == part);

                    if (child == null)
                    {
                        var currentPath = string.Join("/", parts.Take(i + 1));
                        child = new AssetModel(currentPath, part, category);
                        currentNode.Children.Add(child);
                    }
                    currentNode = child;
                }
            }
            return root;
        }

        // Lists all files in the ProjectSettings directory.
        public AssetModel GetProjectSettingsFiles()
        {
            const string category = "Settings";
            var settingsRootPath = Path.Combine(ProjectRoot, "ProjectSettings");
            var root = new AssetModel(settingsRootPath, "ProjectSettings", category);

            if (Directory.Exists(settingsRootPath))
            {
                // Filter to only include *.asset files.
                var files = Directory.GetFiles(settingsRootPath, "*.asset", SearchOption.AllDirectories);
                foreach (var file in files)
                {
                    var relativePath = Path.GetRelativePath(ProjectRoot, file);
                    root.Children.Add(new AssetModel(relativePath, Path.GetFileName(file), category));
                }
            }
            return root;
        }

        // Scans the entire Assets folder for .meta files and builds a hierarchical model.
        public AssetModel GetAllMetaFiles()
        {
            const string category = "Meta Files";
            var assetsRootPath = Application.dataPath; // This is the "Assets" folder
            var root = new AssetModel("Assets", "Assets", category);

            var files = Directory.GetFiles(assetsRootPath, "*.meta", SearchOption.AllDirectories);
            
            foreach (var file in files)
            {
                var relativePath = "Assets/" + Path.GetRelativePath(assetsRootPath, file).Replace('\\', '/');
                var parts = relativePath.Split('/');
                var currentNode = root;

                for (int i = 1; i < parts.Length; i++) // Start at 1 to skip "Assets"
                {
                    var part = parts[i];
                    var child = currentNode.Children.FirstOrDefault(c => c.Name == part);

                    if (child == null)
                    {
                        var currentPath = string.Join("/", parts.Take(i + 1));
                        child = new AssetModel(currentPath, part, category);
                        currentNode.Children.Add(child);
                    }
                    currentNode = child;
                }
            }
            return root;
        }
        
        private string FindPythonExecutable()
        {
            var venvPath = Path.Combine(ProjectRoot, "venv", "bin", "python3");
            if (File.Exists(venvPath))
            {
                return venvPath;
            }
            venvPath = Path.Combine(ProjectRoot, "venv", "bin", "python");
            if (File.Exists(venvPath))
            {
                return venvPath;
            }
            return "python3";
        }

        private string GetConversionScriptPath()
        {
            var guids = AssetDatabase.FindAssets("convert_scene");
            if (guids.Length == 0)
            {
                UnityEngine.Debug.LogError("Conversion script 'convert_scene.py' not found.");
                return null;
            }
            var path = AssetDatabase.GUIDToAssetPath(guids[0]);
            return Path.Combine(ProjectRoot, path);
        }

        // Executes the Python conversion script for the selected assets.
        public void ExportAssets(AssetModel rootNode, string customExportPath = null)
        {
            var assetsToExport = new List<AssetModel>();
            CollectSelectedAssets(rootNode, assetsToExport);
            
            var filteredAssets = FilterAssets(assetsToExport.Select(a => a.Path).ToList())
                .ToHashSet();

            var finalAssetsToExport = assetsToExport.Where(a => filteredAssets.Contains(a.Path)).ToList();

            var pythonExecutable = FindPythonExecutable();
            var conversionScript = GetConversionScriptPath();
            if (string.IsNullOrEmpty(conversionScript)) return;

            var outputDirectory = customExportPath ?? Path.Combine(ProjectRoot, "Library", "ProjectDataExport");
            Directory.CreateDirectory(outputDirectory);
            
            EditorUtility.DisplayProgressBar("Exporting...", "Starting export process...", 0f);

            try
            {
                for (int i = 0; i < finalAssetsToExport.Count; i++)
                {
                    var asset = finalAssetsToExport[i];
                    var relativeAssetPath = asset.Path;
                    var progress = (float)i / finalAssetsToExport.Count;
                    EditorUtility.DisplayProgressBar("Exporting...", $"Processing {Path.GetFileName(relativeAssetPath)}", progress);

                    var absoluteAssetPath = Path.Combine(ProjectRoot, relativeAssetPath);
                    var outputJsonPath = Path.Combine(outputDirectory, relativeAssetPath.Replace('/', Path.DirectorySeparatorChar) + ".json");
                    
                    var fileOutputDir = Path.GetDirectoryName(outputJsonPath);
                    if (!Directory.Exists(fileOutputDir))
                    {
                        Directory.CreateDirectory(fileOutputDir);
                    }

                    var exitCode = asset.Category == "Scenes" ? 
                        ProcessUsingSimpleSceneSerializer(relativeAssetPath, outputJsonPath, out var output, out var error) : 
                        ProcessUsingPythonTool(conversionScript, absoluteAssetPath, outputJsonPath, pythonExecutable, out output, out error);

                    if (exitCode != 0)
                    {
                        UnityEngine.Debug.LogError($"Error exporting {relativeAssetPath}: {error}");
                    }
                    else
                    {
                        if (_settings.OptimiseExport && _settings.CategoriesToOptimise.Contains(asset.Category))
                        {
                            var rawJson = File.ReadAllText(outputJsonPath);
                            var processedJson = JsonDataTrimmer.Process(rawJson, _settings);
                            File.WriteAllText(outputJsonPath, processedJson);
                        }

                        if (_settings.ConvertToNewSchema && asset.Category == "Scenes")
                        {
                            var jsonToConvert = File.ReadAllText(outputJsonPath);
                            var convertedJson = JsonSchemaConverter.Convert(jsonToConvert);
                            File.WriteAllText(outputJsonPath, convertedJson);
                        }
                        
                        UnityEngine.Debug.Log($"Successfully exported {relativeAssetPath}: {output}");
                    }
                }
            }
            finally
            {
                EditorUtility.ClearProgressBar();
            }
        }

        private static int ProcessUsingPythonTool(string conversionScript, string absoluteAssetPath, string outputJsonPath, string pythonExecutable, out string output, out string error)
        {
            var arguments =
                $"\"{conversionScript}\" \"{absoluteAssetPath}\" \"{outputJsonPath}\"";

            var process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = pythonExecutable,
                    Arguments = arguments,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                }
            };

            process.Start();
            output = process.StandardOutput.ReadToEnd();
            error = process.StandardError.ReadToEnd();
            process.WaitForExit();
            return process.ExitCode;
        }

        private static int ProcessUsingSimpleSceneSerializer(string relativeAssetPath, string outputJsonPath, out string output, out string error)
        {
            SceneParser.Process(relativeAssetPath, outputJsonPath, out output, out error);
            return error == null ? 0 : 1;
        }

        private List<string> FilterAssets(List<string> assetPaths)
        {
            if (_settings == null)
            {
                UnityEngine.Debug.LogWarning("ProjectExporterSettings not found. Skipping filtering.");
                return assetPaths;
            }

            var ignoredFolders = _settings.FoldersToIgnore;
            var ignoredExtensions = _settings.FileExtensionsToIgnore.Select(ext => ext.StartsWith(".") ? ext : "." + ext).ToList();
            var ignoredUnityTypes = _settings.UnityTypesToIgnore;

            var filteredList = new List<string>();

            foreach (var path in assetPaths)
            {
                if (ignoredFolders.Any(folder => path.StartsWith(folder, StringComparison.OrdinalIgnoreCase)))
                {
                    continue;
                }

                var extension = Path.GetExtension(path);
                if (ignoredExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
                {
                    continue;
                }

                var assetPathForTypeCheck = path.EndsWith(".meta") ? path[..^5] : path;
                var assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPathForTypeCheck);
                if (assetType != null && IsTypeIgnored(assetType, ignoredUnityTypes))
                {
                    continue;
                }

                filteredList.Add(path);
            }

            return filteredList;
        }

        private bool IsTypeIgnored(Type assetType, List<IgnoredUnityType> ignoredTypes)
        {
            foreach (var ignoredType in ignoredTypes)
            {
                if (ignoredType.assetType == UnityAssetType.Custom)
                {
                    if (!string.IsNullOrEmpty(ignoredType.customType) && assetType.Name.Equals(ignoredType.customType, StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                }
                else if (GetSystemTypeForEnum(ignoredType.assetType) == assetType)
                {
                    return true;
                }
            }
            return false;
        }

        private Type GetSystemTypeForEnum(UnityAssetType unityAssetType)
        {
            switch (unityAssetType)
            {
                case UnityAssetType.Animation: return typeof(AnimationClip);
                case UnityAssetType.AnimatorController: return typeof(AnimatorController);
                case UnityAssetType.AnimatorOverrideController: return typeof(AnimatorOverrideController);
                case UnityAssetType.AudioClip: return typeof(AudioClip);
                case UnityAssetType.AudioMixer: return typeof(AudioMixer);
                case UnityAssetType.ComputeShader: return typeof(ComputeShader);
                case UnityAssetType.Font: return typeof(Font);
                case UnityAssetType.GUISkin: return typeof(GUISkin);
                case UnityAssetType.Material: return typeof(Material);
                case UnityAssetType.Mesh: return typeof(Mesh);
                case UnityAssetType.Model: return typeof(GameObject);
                case UnityAssetType.PhysicMaterial: return typeof(PhysicMaterial);
                case UnityAssetType.Prefab: return typeof(GameObject);
                case UnityAssetType.Scene: return typeof(SceneAsset);
                case UnityAssetType.Script: return typeof(MonoScript);
                case UnityAssetType.Shader: return typeof(Shader);
                case UnityAssetType.Sprite: return typeof(Sprite);
                case UnityAssetType.Texture: return typeof(Texture2D);
                case UnityAssetType.VideoClip: return typeof(VideoClip);
                case UnityAssetType.RenderTexture: return typeof(RenderTexture);
                case UnityAssetType.LightmapParameters: return typeof(LightmapParameters);
                default: return null;
            }
        }

        // Recursively collects the paths of all selected assets.
        private void CollectSelectedAssets(AssetModel node, List<AssetModel> selectedAssets)
        {
            // If the node itself is a file and is selected, add it.
            if (node.Children.Count == 0 && node.IsSelected)
            {
                selectedAssets.Add(node);
            }

            // Recurse into children
            foreach (var child in node.Children)
            {
                CollectSelectedAssets(child, selectedAssets);
            }
        }
    }
}
