using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using MCPForUnity.Runtime.Helpers;

namespace MCPForUnity.Editor.Tools.Graphics
{
    internal static class VolumeOps
    {
        // === volume_create ===
        // Params: name (string), is_global (bool, default true), weight (float, default 1),
        //         priority (float, default 0), profile_path (string, optional - path to save VolumeProfile asset),
        //         effects (array of {type, ...params}, optional - effects to add immediately)
        internal static object CreateVolume(JObject @params)
        {
            var p = new ToolParams(@params);
            string name = p.Get("name") ?? "Volume";
            bool isGlobal = p.GetBool("is_global", true);
            float weight = p.GetFloat("weight") ?? 1.0f;
            float priority = p.GetFloat("priority") ?? 0f;
            string profilePath = p.Get("profile_path");
            if (!string.IsNullOrEmpty(profilePath))
            {
                if (!profilePath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) &&
                    !profilePath.StartsWith("Assets\\", StringComparison.OrdinalIgnoreCase))
                    profilePath = "Assets/" + profilePath;
                if (!profilePath.EndsWith(".asset", StringComparison.OrdinalIgnoreCase))
                    profilePath += ".asset";
            }

            var go = new GameObject(name);
            Undo.RegisterCreatedObjectUndo(go, $"Create Volume '{name}'");

            // Add Volume component via reflection
            var volumeComp = go.AddComponent(GraphicsHelpers.VolumeType);

            // Set properties via reflection
            SetProperty(volumeComp, "isGlobal", isGlobal);
            SetProperty(volumeComp, "weight", weight);
            SetProperty(volumeComp, "priority", priority);

            // Create or load VolumeProfile
            object profile;
            if (!string.IsNullOrEmpty(profilePath))
            {
                // Load existing or create new profile asset
                profile = AssetDatabase.LoadAssetAtPath(profilePath, GraphicsHelpers.VolumeProfileType);
                if (profile == null)
                {
                    profile = ScriptableObject.CreateInstance(GraphicsHelpers.VolumeProfileType);
                    // Ensure directory exists
                    var dir = System.IO.Path.GetDirectoryName(profilePath);
                    if (!string.IsNullOrEmpty(dir) && !System.IO.Directory.Exists(dir))
                        System.IO.Directory.CreateDirectory(dir);
                    AssetDatabase.CreateAsset((UnityEngine.Object)profile, profilePath);
                }
            }
            else
            {
                // Create embedded profile (not saved as asset)
                profile = ScriptableObject.CreateInstance(GraphicsHelpers.VolumeProfileType);
            }

            // Assign profile (sharedProfile is a public field, handled by SetProperty's field fallback)
            SetProperty(volumeComp, "sharedProfile", profile);

            // Add initial effects if provided
            var effectsToken = p.GetRaw("effects") as JArray;
            var addedEffects = new List<string>();
            if (effectsToken != null)
            {
                foreach (var effectDef in effectsToken)
                {
                    if (effectDef is JObject effectObj)
                    {
                        string effectType = ParamCoercion.CoerceString(effectObj["type"], null);
                        if (string.IsNullOrEmpty(effectType)) continue;

                        var type = GraphicsHelpers.ResolveVolumeComponentType(effectType);
                        if (type == null) continue;

                        // profile.Add(type, true)
                        var addMethod = GraphicsHelpers.VolumeProfileType.GetMethod("Add",
                            new[] { typeof(Type), typeof(bool) });
                        if (addMethod != null)
                        {
                            var component = addMethod.Invoke(profile, new object[] { type, true });
                            if (component != null)
                            {
                                // Set parameters — support both nested {"parameters": {...}} and flat fields
                                var paramObj = effectObj["parameters"] as JObject;
                                if (paramObj != null)
                                {
                                    foreach (var pp in paramObj.Properties())
                                        SetVolumeParameter(component, pp.Name, pp.Value);
                                }
                                else
                                {
                                    foreach (var prop in effectObj.Properties())
                                    {
                                        if (prop.Name == "type") continue;
                                        SetVolumeParameter(component, prop.Name, prop.Value);
                                    }
                                }
                                addedEffects.Add(effectType);
                            }
                        }
                    }
                }
                if (profile is UnityEngine.Object profileObj)
                    EditorUtility.SetDirty(profileObj);
            }

            GraphicsHelpers.MarkDirty(volumeComp);

            return new
            {
                success = true,
                message = $"Created {(isGlobal ? "global" : "local")} Volume '{name}'" +
                         (addedEffects.Count > 0 ? $" with effects: {string.Join(", ", addedEffects)}" : ""),
                data = new
                {
                    instanceID = go.GetInstanceIDCompat(),
                    isGlobal,
                    weight,
                    priority,
                    profilePath = profilePath ?? "(embedded)",
                    effects = addedEffects
                }
            };
        }

        // === volume_add_effect ===
        // Params: target (string/int), effect (string - type name like "Bloom")
        internal static object AddEffect(JObject @params)
        {
            var p = new ToolParams(@params);
            string effectName = p.Get("effect");
            if (string.IsNullOrEmpty(effectName))
                return new ErrorResponse("'effect' parameter is required (e.g., 'Bloom', 'Vignette').");

            var volume = GraphicsHelpers.FindVolume(@params);
            if (volume == null)
                return new ErrorResponse("Volume not found. Specify 'target' (name or instance ID).");

            var effectType = GraphicsHelpers.ResolveVolumeComponentType(effectName);
            if (effectType == null)
            {
                var available = GraphicsHelpers.GetAvailableEffectTypes()
                    .Select(t => t.Name).ToList();
                return new ErrorResponse(
                    $"Effect type '{effectName}' not found. Available: {string.Join(", ", available.Take(20))}");
            }

            var profile = GetProperty(volume, "sharedProfile");
            if (profile == null)
                return new ErrorResponse("Volume has no profile assigned.");

            // Check if effect already exists
            var components = GetProperty(profile, "components") as System.Collections.IList;
            if (components != null)
            {
                foreach (var comp in components)
                {
                    if (comp != null && comp.GetType() == effectType)
                        return new ErrorResponse($"Effect '{effectName}' already exists on this Volume. Use volume_set_effect to modify it.");
                }
            }

            // profile.Add(effectType, true) -- 'true' means override all params
            var addMethod = GraphicsHelpers.VolumeProfileType.GetMethod("Add",
                new[] { typeof(Type), typeof(bool) });
            if (addMethod == null)
                return new ErrorResponse("Could not find VolumeProfile.Add method.");

            var component = addMethod.Invoke(profile, new object[] { effectType, true });
            if (component == null)
                return new ErrorResponse($"Failed to add effect '{effectName}'.");

            if (profile is UnityEngine.Object profileObj)
                EditorUtility.SetDirty(profileObj);
            GraphicsHelpers.MarkDirty(volume);

            return new
            {
                success = true,
                message = $"Added '{effectName}' to Volume '{(volume as Component)?.gameObject.name}'.",
                data = new { effect = effectName, volumeInstanceID = (volume as Component)?.gameObject.GetInstanceIDCompat() }
            };
        }

        // === volume_set_effect ===
        // Params: target (string/int), effect (string), parameters (dict of field->value)
        internal static object SetEffect(JObject @params)
        {
            var p = new ToolParams(@params);
            string effectName = p.Get("effect");
            if (string.IsNullOrEmpty(effectName))
                return new ErrorResponse("'effect' parameter is required.");

            var volume = GraphicsHelpers.FindVolume(@params);
            if (volume == null)
                return new ErrorResponse("Volume not found. Specify 'target'.");

            var profile = GetProperty(volume, "sharedProfile");
            if (profile == null)
                return new ErrorResponse("Volume has no profile assigned.");

            var effectType = GraphicsHelpers.ResolveVolumeComponentType(effectName);
            if (effectType == null)
                return new ErrorResponse($"Effect type '{effectName}' not found.");

            // Find the effect component in the profile
            var components = GetProperty(profile, "components") as System.Collections.IList;
            if (components == null)
                return new ErrorResponse("Could not read profile components.");

            object targetComponent = null;
            foreach (var comp in components)
            {
                if (comp != null && comp.GetType() == effectType)
                {
                    targetComponent = comp;
                    break;
                }
            }

            if (targetComponent == null)
                return new ErrorResponse($"Effect '{effectName}' not found on this Volume. Use volume_add_effect first.");

            // Set parameters
            var parameters = p.GetRaw("parameters") as JObject;
            if (parameters == null)
                return new ErrorResponse("'parameters' dict is required.");

            var setParams = new List<string>();
            var failedParams = new List<string>();
            foreach (var prop in parameters.Properties())
            {
                if (SetVolumeParameter(targetComponent, prop.Name, prop.Value))
                    setParams.Add(prop.Name);
                else
                    failedParams.Add(prop.Name);
            }

            if (profile is UnityEngine.Object profileObj)
                EditorUtility.SetDirty(profileObj);

            var msg = $"Set {setParams.Count} parameter(s) on '{effectName}'";
            if (failedParams.Count > 0)
                msg += $". Failed: {string.Join(", ", failedParams)}";

            return new
            {
                success = true,
                message = msg,
                data = new { effect = effectName, set = setParams, failed = failedParams }
            };
        }

        // === volume_remove_effect ===
        // Params: target, effect
        internal static object RemoveEffect(JObject @params)
        {
            var p = new ToolParams(@params);
            string effectName = p.Get("effect");
            if (string.IsNullOrEmpty(effectName))
                return new ErrorResponse("'effect' parameter is required.");

            var volume = GraphicsHelpers.FindVolume(@params);
            if (volume == null)
                return new ErrorResponse("Volume not found.");

            var effectType = GraphicsHelpers.ResolveVolumeComponentType(effectName);
            if (effectType == null)
                return new ErrorResponse($"Effect type '{effectName}' not found.");

            var profile = GetProperty(volume, "sharedProfile");
            if (profile == null)
                return new ErrorResponse("Volume has no profile.");

            // Check if effect exists before removing
            bool found = false;
            var components = GetProperty(profile, "components") as System.Collections.IList;
            if (components != null)
            {
                foreach (var comp in components)
                {
                    if (comp != null && comp.GetType() == effectType)
                    {
                        found = true;
                        break;
                    }
                }
            }
            if (!found)
                return new ErrorResponse($"Effect '{effectName}' not found on this Volume.");

            var removeMethod = GraphicsHelpers.VolumeProfileType.GetMethod("Remove",
                new[] { typeof(Type) });
            if (removeMethod == null)
                return new ErrorResponse("Could not find VolumeProfile.Remove method.");

            removeMethod.Invoke(profile, new object[] { effectType });

            if (profile is UnityEngine.Object profileObj)
                EditorUtility.SetDirty(profileObj);
            GraphicsHelpers.MarkDirty(volume);

            return new
            {
                success = true,
                message = $"Removed '{effectName}' from Volume.",
                data = new { effect = effectName }
            };
        }

        // === volume_get_info ===
        // Params: target (optional -- if omitted, returns info for all volumes)
        internal static object GetInfo(JObject @params)
        {
            var volume = GraphicsHelpers.FindVolume(@params);
            if (volume == null)
                return new ErrorResponse("Volume not found.");

            var info = BuildVolumeInfo(volume);
            return new
            {
                success = true,
                message = $"Volume info for '{(volume as Component)?.gameObject.name}'.",
                data = info
            };
        }

        // === volume_set_properties ===
        // Params: target, weight, priority, is_global, blend_distance
        //         OR properties dict with those keys
        internal static object SetProperties(JObject @params)
        {
            var p = new ToolParams(@params);
            var volume = GraphicsHelpers.FindVolume(@params);
            if (volume == null)
                return new ErrorResponse("Volume not found.");

            // Unpack "properties" dict into top-level params so callers can use either style
            var propsDict = p.GetRaw("properties") as JObject;
            if (propsDict != null)
            {
                foreach (var prop in propsDict.Properties())
                {
                    if (@params[prop.Name] == null)
                        @params[prop.Name] = prop.Value;
                }
                p = new ToolParams(@params);
            }

            var changed = new List<string>();

            var weight = p.GetFloat("weight");
            if (weight.HasValue) { SetProperty(volume, "weight", weight.Value); changed.Add("weight"); }

            var priority = p.GetFloat("priority");
            if (priority.HasValue) { SetProperty(volume, "priority", priority.Value); changed.Add("priority"); }

            if (p.Has("is_global")) { SetProperty(volume, "isGlobal", p.GetBool("is_global")); changed.Add("isGlobal"); }

            var blendDist = p.GetFloat("blend_distance");
            if (blendDist.HasValue) { SetProperty(volume, "blendDistance", blendDist.Value); changed.Add("blendDistance"); }

            if (changed.Count == 0)
                return new ErrorResponse("No properties specified. Use: weight, priority, is_global, blend_distance.");

            GraphicsHelpers.MarkDirty(volume);
            return new
            {
                success = true,
                message = $"Updated Volume properties: {string.Join(", ", changed)}",
                data = new { changed }
            };
        }

        // === volume_list_effects ===
        // No params needed -- lists all available VolumeComponent types
        internal static object ListEffects(JObject @params)
        {
            var types = GraphicsHelpers.GetAvailableEffectTypes();
            var effectList = types.Select(t => new
            {
                name = t.Name,
                fullName = t.FullName,
                ns = t.Namespace
            }).ToList();

            return new
            {
                success = true,
                message = $"Found {effectList.Count} available volume effects.",
                data = new { pipeline = GraphicsHelpers.GetPipelineName(), effects = effectList }
            };
        }

        // === volume_create_profile ===
        // Params: path (string -- asset path like "Assets/Settings/MyProfile.asset")
        internal static object CreateProfile(JObject @params)
        {
            var p = new ToolParams(@params);
            string path = p.Get("path");
            if (string.IsNullOrEmpty(path))
                return new ErrorResponse("'path' parameter is required (e.g., 'Settings/MyProfile' or 'Assets/Settings/MyProfile.asset').");

            // Auto-prepend Assets/ if missing (paths are relative to Assets/ by convention)
            if (!path.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) &&
                !path.StartsWith("Assets\\", StringComparison.OrdinalIgnoreCase))
                path = "Assets/" + path;

            if (!path.EndsWith(".asset", StringComparison.OrdinalIgnoreCase))
                path += ".asset";

            // Ensure directory exists
            var dir = System.IO.Path.GetDirectoryName(path);
            if (!string.IsNullOrEmpty(dir) && !AssetDatabase.IsValidFolder(dir))
            {
                // Create folders recursively
                var parts = dir.Replace("\\", "/").Split('/');
                string current = parts[0];
                for (int i = 1; i < parts.Length; i++)
                {
                    string next = current + "/" + parts[i];
                    if (!AssetDatabase.IsValidFolder(next))
                        AssetDatabase.CreateFolder(current, parts[i]);
                    current = next;
                }
            }

            var profile = ScriptableObject.CreateInstance(GraphicsHelpers.VolumeProfileType);
            AssetDatabase.CreateAsset(profile, path);
            AssetDatabase.SaveAssets();

            return new
            {
                success = true,
                message = $"Created VolumeProfile at '{path}'.",
                data = new { path }
            };
        }

        // === ListVolumes (used by VolumesResource) ===
        internal static object ListVolumes(JObject @params)
        {
            if (!GraphicsHelpers.HasVolumeSystem)
                return new { success = true, message = "Volume system not available.", data = new { volumes = new List<object>() } };

            var allVolumes = UnityFindObjectsCompat.FindAll(GraphicsHelpers.VolumeType);
            var volumeList = new List<object>();

            foreach (Component vol in allVolumes)
            {
                volumeList.Add(BuildVolumeInfo(vol));
            }

            return new
            {
                success = true,
                message = $"Found {volumeList.Count} volume(s).",
                data = new { pipeline = GraphicsHelpers.GetPipelineName(), volumes = volumeList }
            };
        }

        // --- Helper: Build info object for a single Volume ---
        private static object BuildVolumeInfo(object volumeComponent)
        {
            var comp = volumeComponent as Component;
            if (comp == null) return null;

            bool isGlobal = GetPropertyValue<bool>(volumeComponent, "isGlobal", true);
            float weight = GetPropertyValue<float>(volumeComponent, "weight", 1f);
            float priority = GetPropertyValue<float>(volumeComponent, "priority", 0f);
            float blendDistance = GetPropertyValue<float>(volumeComponent, "blendDistance", 0f);

            var profile = GetProperty(volumeComponent, "sharedProfile");
            string profileName = profile is UnityEngine.Object profileObj2 ? profileObj2.name : null;
            string profilePath = profile is UnityEngine.Object po ? AssetDatabase.GetAssetPath(po) : null;

            var effectsList = new List<object>();
            if (profile != null)
            {
                var components = GetProperty(profile, "components") as System.Collections.IList;
                if (components != null)
                {
                    foreach (var effect in components)
                    {
                        if (effect == null) continue;
                        var effectType = effect.GetType();
                        bool active = GetPropertyValue<bool>(effect, "active", true);

                        // Collect overridden parameters
                        var overriddenParams = new List<string>();
                        foreach (var field in effectType.GetFields(BindingFlags.Public | BindingFlags.Instance))
                        {
                            var fieldValue = field.GetValue(effect);
                            if (fieldValue == null) continue;
                            var overrideProp = fieldValue.GetType().GetProperty("overrideState");
                            if (overrideProp != null)
                            {
                                bool overridden = (bool)overrideProp.GetValue(fieldValue);
                                if (overridden)
                                    overriddenParams.Add(field.Name);
                            }
                        }

                        effectsList.Add(new
                        {
                            type = effectType.Name,
                            active,
                            overridden_params = overriddenParams
                        });
                    }
                }
            }

            return new
            {
                name = comp.gameObject.name,
                instance_id = comp.gameObject.GetInstanceIDCompat(),
                is_global = isGlobal,
                weight,
                priority,
                blend_distance = blendDistance,
                profile = profileName,
                profile_path = profilePath ?? "",
                effects = effectsList
            };
        }

        // --- Helper: Set a VolumeParameter field value via reflection ---
        // VolumeParameter<T> has: overrideState (bool), value (T)
        internal static bool SetVolumeParameter(object component, string fieldName, JToken value)
        {
            if (component == null || string.IsNullOrEmpty(fieldName)) return false;

            var field = component.GetType().GetField(fieldName,
                BindingFlags.Public | BindingFlags.Instance);
            if (field == null)
            {
                // Try camelCase conversion from snake_case
                string camelCase = StringCaseUtility.ToCamelCase(fieldName);
                field = component.GetType().GetField(camelCase,
                    BindingFlags.Public | BindingFlags.Instance);
            }
            if (field == null) return false;

            var param = field.GetValue(component);
            if (param == null) return false;

            // Set value with type conversion, then enable override on success
            var valueProp = param.GetType().GetProperty("value");
            if (valueProp == null) return false;

            try
            {
                object converted = ConvertToParameterType(value, valueProp.PropertyType);
                valueProp.SetValue(param, converted);

                var overrideProp = param.GetType().GetProperty("overrideState");
                if (overrideProp != null)
                    overrideProp.SetValue(param, true);

                return true;
            }
            catch (Exception ex)
            {
                McpLog.Warn($"[VolumeOps] Failed to set '{fieldName}': {ex.Message}");
                return false;
            }
        }

        // --- Helper: Convert JToken to target parameter type ---
        private static object ConvertToParameterType(JToken value, Type targetType)
        {
            if (value == null || value.Type == JTokenType.Null) return null;

            // Handle Color
            if (targetType == typeof(Color))
            {
                if (value is JArray arr && arr.Count >= 3)
                {
                    float r = arr[0].Value<float>();
                    float g = arr[1].Value<float>();
                    float b = arr[2].Value<float>();
                    float a = arr.Count >= 4 ? arr[3].Value<float>() : 1f;
                    return new Color(r, g, b, a);
                }
                // Try hex string
                if (value.Type == JTokenType.String)
                {
                    if (ColorUtility.TryParseHtmlString(value.ToString(), out Color c))
                        return c;
                }
            }

            // Handle Vector2
            if (targetType == typeof(Vector2))
            {
                if (value is JArray arr && arr.Count >= 2)
                    return new Vector2(arr[0].Value<float>(), arr[1].Value<float>());
            }

            // Handle Vector3
            if (targetType == typeof(Vector3))
            {
                if (value is JArray arr && arr.Count >= 3)
                    return new Vector3(arr[0].Value<float>(), arr[1].Value<float>(), arr[2].Value<float>());
            }

            // Handle Vector4
            if (targetType == typeof(Vector4))
            {
                if (value is JArray arr && arr.Count >= 4)
                    return new Vector4(arr[0].Value<float>(), arr[1].Value<float>(),
                                      arr[2].Value<float>(), arr[3].Value<float>());
            }

            // Handle enums
            if (targetType.IsEnum)
            {
                string str = value.ToString();
                if (Enum.TryParse(targetType, str, true, out object enumVal))
                    return enumVal;
                // Try as int
                if (int.TryParse(str, out int intVal))
                    return Enum.ToObject(targetType, intVal);
            }

            // Handle bool
            if (targetType == typeof(bool))
                return ParamCoercion.CoerceBool(value, false);

            // Handle float
            if (targetType == typeof(float))
                return ParamCoercion.CoerceFloat(value, 0f);

            // Handle int
            if (targetType == typeof(int))
                return ParamCoercion.CoerceInt(value, 0);

            // Handle Texture2D (by asset path)
            if (targetType == typeof(Texture2D) || targetType == typeof(Texture))
            {
                string path = value.ToString();
                return AssetDatabase.LoadAssetAtPath<Texture2D>(path);
            }

            // Fallback: try Convert
            try
            {
                return Convert.ChangeType(value.ToObject<object>(), targetType);
            }
            catch
            {
                return value.ToObject<object>();
            }
        }

        // --- Reflection helpers (with field fallback for Volume.sharedProfile etc.) ---
        private static object GetProperty(object obj, string name)
        {
            if (obj == null) return null;
            var prop = obj.GetType().GetProperty(name,
                BindingFlags.Public | BindingFlags.Instance);
            if (prop != null) return prop.GetValue(obj);
            // Fallback: try as a field (e.g., Volume.sharedProfile is a public field, not a property)
            var field = obj.GetType().GetField(name,
                BindingFlags.Public | BindingFlags.Instance);
            return field?.GetValue(obj);
        }

        private static T GetPropertyValue<T>(object obj, string name, T defaultValue)
        {
            var val = GetProperty(obj, name);
            if (val is T typed) return typed;
            return defaultValue;
        }

        private static void SetProperty(object obj, string name, object value)
        {
            if (obj == null) return;
            var prop = obj.GetType().GetProperty(name,
                BindingFlags.Public | BindingFlags.Instance);
            if (prop != null && prop.CanWrite)
            {
                prop.SetValue(obj, value);
                return;
            }
            // Fallback: try as a field (e.g., Volume.sharedProfile is a public field, not a property)
            var field = obj.GetType().GetField(name,
                BindingFlags.Public | BindingFlags.Instance);
            field?.SetValue(obj, value);
        }
    }
}
