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

namespace MCPForUnity.Editor.Tools.Graphics
{
    internal static class SkyboxOps
    {
        static Texture CustomReflectionTexture
        {
            get =>
#if UNITY_2022_1_OR_NEWER
                RenderSettings.customReflectionTexture;
#else
                RenderSettings.customReflection;
#endif
            set {
#if UNITY_2022_1_OR_NEWER
                RenderSettings.customReflectionTexture = value;
#else
                RenderSettings.customReflection = value;
#endif
            }
        }
        // ---------------------------------------------------------------
        // skybox_get — read all environment settings
        // ---------------------------------------------------------------
        public static object GetEnvironment(JObject @params)
        {
            var skyMat = RenderSettings.skybox;
            var sun = RenderSettings.sun;

            object matInfo = null;
            if (skyMat != null)
            {
                var props = new List<object>();
                int count = skyMat.shader.GetPropertyCount();
                for (int i = 0; i < count; i++)
                {
                    string propName = skyMat.shader.GetPropertyName(i);
                    var propType = skyMat.shader.GetPropertyType(i);
                    object val = ReadMaterialProperty(skyMat, propName, propType);
                    props.Add(new { name = propName, type = propType.ToString(), value = val });
                }
                matInfo = new
                {
                    name = skyMat.name,
                    shader = skyMat.shader.name,
                    path = AssetDatabase.GetAssetPath(skyMat),
                    properties = props
                };
            }

            return new
            {
                success = true,
                message = "Environment settings retrieved.",
                data = new
                {
                    skybox = matInfo,
                    ambient = new
                    {
                        mode = RenderSettings.ambientMode.ToString(),
                        skyColor = ColorToArray(RenderSettings.ambientSkyColor),
                        equatorColor = ColorToArray(RenderSettings.ambientEquatorColor),
                        groundColor = ColorToArray(RenderSettings.ambientGroundColor),
                        ambientLight = ColorToArray(RenderSettings.ambientLight),
                        intensity = RenderSettings.ambientIntensity
                    },
                    fog = new
                    {
                        enabled = RenderSettings.fog,
                        mode = RenderSettings.fogMode.ToString(),
                        color = ColorToArray(RenderSettings.fogColor),
                        density = RenderSettings.fogDensity,
                        startDistance = RenderSettings.fogStartDistance,
                        endDistance = RenderSettings.fogEndDistance
                    },
                    reflection = new
                    {
                        intensity = RenderSettings.reflectionIntensity,
                        bounces = RenderSettings.reflectionBounces,
                        mode = RenderSettings.defaultReflectionMode.ToString(),
                        resolution = RenderSettings.defaultReflectionResolution,
                        customCubemap = CustomReflectionTexture != null
                            ? AssetDatabase.GetAssetPath(CustomReflectionTexture)
                            : null
                    },
                    sun = sun != null
                        ? (object)new { name = sun.gameObject.name, instanceID = sun.gameObject.GetInstanceIDCompat() }
                        : null,
                    subtractiveShadowColor = ColorToArray(RenderSettings.subtractiveShadowColor)
                }
            };
        }

        // ---------------------------------------------------------------
        // skybox_set_material — assign a skybox material
        // ---------------------------------------------------------------
        public static object SetMaterial(JObject @params)
        {
            var p = new ToolParams(@params);
            string materialPath = p.Get("material") ?? p.Get("path") ?? p.Get("material_path");
            if (string.IsNullOrEmpty(materialPath))
                return new ErrorResponse("'material' (asset path) is required.");

            var mat = AssetDatabase.LoadAssetAtPath<Material>(materialPath);
            if (mat == null)
                return new ErrorResponse($"Material not found at '{materialPath}'.");

            RenderSettings.skybox = mat;
            MarkSceneDirty();

            return new
            {
                success = true,
                message = $"Skybox set to '{mat.name}' (shader: {mat.shader.name}).",
                data = new
                {
                    material = mat.name,
                    shader = mat.shader.name,
                    path = materialPath
                }
            };
        }

        // ---------------------------------------------------------------
        // skybox_set_properties — modify properties on the current skybox material
        // ---------------------------------------------------------------
        public static object SetMaterialProperties(JObject @params)
        {
            var p = new ToolParams(@params);
            var skyMat = RenderSettings.skybox;
            if (skyMat == null)
                return new ErrorResponse("No skybox material is set.");

            var propsRaw = p.GetRaw("properties") ?? p.GetRaw("parameters");
            if (propsRaw == null || propsRaw.Type != JTokenType.Object)
                return new ErrorResponse("'properties' dict is required.");

            var set = new List<string>();
            var failed = new List<string>();

            foreach (var kvp in (JObject)propsRaw)
            {
                string propName = kvp.Key;
                if (!skyMat.HasProperty(propName))
                {
                    string altName = "_" + propName;
                    if (skyMat.HasProperty(altName))
                        propName = altName;
                    else
                    {
                        failed.Add(kvp.Key);
                        continue;
                    }
                }

                if (SetMaterialProperty(skyMat, propName, kvp.Value))
                    set.Add(kvp.Key);
                else
                    failed.Add(kvp.Key);
            }

            EditorUtility.SetDirty(skyMat);
            AssetDatabase.SaveAssets();
            MarkSceneDirty();

            return new
            {
                success = true,
                message = $"Set {set.Count} property(ies) on skybox material '{skyMat.name}'.",
                data = new { material = skyMat.name, set, failed }
            };
        }

        // ---------------------------------------------------------------
        // skybox_set_ambient — set ambient lighting mode and colors
        // ---------------------------------------------------------------
        public static object SetAmbient(JObject @params)
        {
            var p = new ToolParams(@params);

            string modeStr = p.Get("ambient_mode") ?? p.Get("mode");
            if (!string.IsNullOrEmpty(modeStr))
            {
                if (Enum.TryParse<AmbientMode>(modeStr, true, out var mode))
                    RenderSettings.ambientMode = mode;
                else
                    return new ErrorResponse(
                        $"Invalid ambient mode '{modeStr}'. Valid: Skybox, Trilight, Flat, Custom.");
            }

            var skyColor = ParseColorToken(p.GetRaw("color") ?? p.GetRaw("sky_color"));
            if (skyColor.HasValue)
                RenderSettings.ambientSkyColor = skyColor.Value;

            var equatorColor = ParseColorToken(p.GetRaw("equator_color"));
            if (equatorColor.HasValue)
                RenderSettings.ambientEquatorColor = equatorColor.Value;

            var groundColor = ParseColorToken(p.GetRaw("ground_color"));
            if (groundColor.HasValue)
                RenderSettings.ambientGroundColor = groundColor.Value;

            var intensity = p.GetFloat("intensity");
            if (intensity.HasValue)
                RenderSettings.ambientIntensity = intensity.Value;

            MarkSceneDirty();

            return new
            {
                success = true,
                message = $"Ambient lighting updated (mode: {RenderSettings.ambientMode}).",
                data = new
                {
                    mode = RenderSettings.ambientMode.ToString(),
                    skyColor = ColorToArray(RenderSettings.ambientSkyColor),
                    equatorColor = ColorToArray(RenderSettings.ambientEquatorColor),
                    groundColor = ColorToArray(RenderSettings.ambientGroundColor),
                    intensity = RenderSettings.ambientIntensity
                }
            };
        }

        // ---------------------------------------------------------------
        // skybox_set_fog — enable/configure fog
        // ---------------------------------------------------------------
        public static object SetFog(JObject @params)
        {
            var p = new ToolParams(@params);

            var enabledToken = p.GetRaw("fog_enabled") ?? p.GetRaw("enabled");
            if (enabledToken != null && enabledToken.Type != JTokenType.Null)
                RenderSettings.fog = ParamCoercion.CoerceBool(enabledToken, RenderSettings.fog);

            string modeStr = p.Get("fog_mode") ?? p.Get("mode");
            if (!string.IsNullOrEmpty(modeStr))
            {
                if (Enum.TryParse<FogMode>(modeStr, true, out var fogMode))
                    RenderSettings.fogMode = fogMode;
                else
                    return new ErrorResponse(
                        $"Invalid fog mode '{modeStr}'. Valid: Linear, Exponential, ExponentialSquared.");
            }

            var fogColor = ParseColorToken(p.GetRaw("fog_color") ?? p.GetRaw("color"));
            if (fogColor.HasValue)
                RenderSettings.fogColor = fogColor.Value;

            var density = p.GetFloat("fog_density") ?? p.GetFloat("density");
            if (density.HasValue)
                RenderSettings.fogDensity = density.Value;

            var start = p.GetFloat("fog_start") ?? p.GetFloat("start");
            if (start.HasValue)
                RenderSettings.fogStartDistance = start.Value;

            var end = p.GetFloat("fog_end") ?? p.GetFloat("end");
            if (end.HasValue)
                RenderSettings.fogEndDistance = end.Value;

            MarkSceneDirty();

            return new
            {
                success = true,
                message = $"Fog settings updated (enabled: {RenderSettings.fog}, mode: {RenderSettings.fogMode}).",
                data = new
                {
                    enabled = RenderSettings.fog,
                    mode = RenderSettings.fogMode.ToString(),
                    color = ColorToArray(RenderSettings.fogColor),
                    density = RenderSettings.fogDensity,
                    startDistance = RenderSettings.fogStartDistance,
                    endDistance = RenderSettings.fogEndDistance
                }
            };
        }

        // ---------------------------------------------------------------
        // skybox_set_reflection — configure environment reflections
        // ---------------------------------------------------------------
        public static object SetReflection(JObject @params)
        {
            var p = new ToolParams(@params);

            var intensity = p.GetFloat("intensity");
            if (intensity.HasValue)
                RenderSettings.reflectionIntensity = intensity.Value;

            var bounces = p.GetInt("bounces");
            if (bounces.HasValue)
                RenderSettings.reflectionBounces = bounces.Value;

            string modeStr = p.Get("reflection_mode") ?? p.Get("mode");
            if (!string.IsNullOrEmpty(modeStr))
            {
                if (Enum.TryParse<DefaultReflectionMode>(modeStr, true, out var mode))
                    RenderSettings.defaultReflectionMode = mode;
                else
                    return new ErrorResponse(
                        $"Invalid reflection mode '{modeStr}'. Valid: Skybox, Custom.");
            }

            var resolution = p.GetInt("resolution");
            if (resolution.HasValue)
                RenderSettings.defaultReflectionResolution = resolution.Value;

            string cubemapPath = p.Get("path") ?? p.Get("cubemap_path");
            if (!string.IsNullOrEmpty(cubemapPath))
            {
                var cubemap = AssetDatabase.LoadAssetAtPath<Texture>(cubemapPath);
                if (cubemap != null)
                    CustomReflectionTexture = cubemap;
                else
                    return new ErrorResponse($"Cubemap not found at '{cubemapPath}'.");
            }

            MarkSceneDirty();

            return new
            {
                success = true,
                message = $"Reflection settings updated (intensity: {RenderSettings.reflectionIntensity}, bounces: {RenderSettings.reflectionBounces}).",
                data = new
                {
                    intensity = RenderSettings.reflectionIntensity,
                    bounces = RenderSettings.reflectionBounces,
                    mode = RenderSettings.defaultReflectionMode.ToString(),
                    resolution = RenderSettings.defaultReflectionResolution,
                    customCubemap = CustomReflectionTexture != null
                        ? AssetDatabase.GetAssetPath(CustomReflectionTexture)
                        : null
                }
            };
        }

        // ---------------------------------------------------------------
        // skybox_set_sun — set the sun source light
        // ---------------------------------------------------------------
        public static object SetSun(JObject @params)
        {
            var p = new ToolParams(@params);
            string target = p.Get("target") ?? p.Get("name");
            if (string.IsNullOrEmpty(target))
                return new ErrorResponse("'target' (light GameObject name or instance ID) is required.");

            GameObject go = null;
            if (int.TryParse(target, out int instanceId))
                go = GameObjectLookup.ResolveInstanceID(instanceId) as GameObject;
            if (go == null)
                go = GameObject.Find(target);
            if (go == null)
                return new ErrorResponse($"GameObject '{target}' not found.");

            var light = go.GetComponent<Light>();
            if (light == null)
                return new ErrorResponse($"'{go.name}' does not have a Light component.");

            RenderSettings.sun = light;
            MarkSceneDirty();

            return new
            {
                success = true,
                message = $"Sun source set to '{go.name}'.",
                data = new
                {
                    name = go.name,
                    instanceID = go.GetInstanceIDCompat(),
                    lightType = light.type.ToString()
                }
            };
        }

        // ---------------------------------------------------------------
        // Helpers
        // ---------------------------------------------------------------

        private static float[] ColorToArray(Color c)
        {
            return new[] { c.r, c.g, c.b, c.a };
        }

        private static Color ArrayToColor(float[] arr)
        {
            return new Color(
                arr[0], arr[1], arr[2],
                arr.Length >= 4 ? arr[3] : 1f);
        }

        private static Color? ParseColorToken(JToken token)
        {
            if (token == null || token.Type == JTokenType.Null) return null;
            if (token is JArray arr && arr.Count >= 3)
            {
                return new Color(
                    (float)arr[0], (float)arr[1], (float)arr[2],
                    arr.Count >= 4 ? (float)arr[3] : 1f);
            }
            return null;
        }

        private static object ReadMaterialProperty(Material mat, string propName, ShaderPropertyType propType)
        {
            switch (propType)
            {
                case ShaderPropertyType.Color:
                    return ColorToArray(mat.GetColor(propName));
                case ShaderPropertyType.Float:
                case ShaderPropertyType.Range:
                    return mat.GetFloat(propName);
                case ShaderPropertyType.Int:
                    return mat.GetInt(propName);
                case ShaderPropertyType.Vector:
                    var v = mat.GetVector(propName);
                    return new[] { v.x, v.y, v.z, v.w };
                case ShaderPropertyType.Texture:
                    var tex = mat.GetTexture(propName);
                    return tex != null ? AssetDatabase.GetAssetPath(tex) : null;
                default:
                    return null;
            }
        }

        private static bool SetMaterialProperty(Material mat, string propName, JToken value)
        {
            int propIdx = mat.shader.FindPropertyIndex(propName);
            if (propIdx < 0) return false;

            var propType = mat.shader.GetPropertyType(propIdx);
            try
            {
                switch (propType)
                {
                    case ShaderPropertyType.Color:
                        if (value is JArray colorArr && colorArr.Count >= 3)
                        {
                            mat.SetColor(propName, new Color(
                                (float)colorArr[0], (float)colorArr[1], (float)colorArr[2],
                                colorArr.Count >= 4 ? (float)colorArr[3] : 1f));
                            return true;
                        }
                        return false;
                    case ShaderPropertyType.Float:
                    case ShaderPropertyType.Range:
                        mat.SetFloat(propName, (float)value);
                        return true;
                    case ShaderPropertyType.Int:
                        mat.SetInt(propName, (int)value);
                        return true;
                    case ShaderPropertyType.Vector:
                        if (value is JArray vecArr && vecArr.Count >= 2)
                        {
                            mat.SetVector(propName, new Vector4(
                                (float)vecArr[0], (float)vecArr[1],
                                vecArr.Count >= 3 ? (float)vecArr[2] : 0f,
                                vecArr.Count >= 4 ? (float)vecArr[3] : 0f));
                            return true;
                        }
                        return false;
                    case ShaderPropertyType.Texture:
                        if (value.Type == JTokenType.String)
                        {
                            var tex = AssetDatabase.LoadAssetAtPath<Texture>(value.ToString());
                            if (tex != null) { mat.SetTexture(propName, tex); return true; }
                        }
                        else if (value.Type == JTokenType.Null)
                        {
                            mat.SetTexture(propName, null);
                            return true;
                        }
                        return false;
                    default:
                        return false;
                }
            }
            catch
            {
                return false;
            }
        }

        private static void MarkSceneDirty()
        {
            UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(
                UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene());
        }
    }
}
