using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Profiling;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace FrameDoctor.Editor.Logic
{
    // Collects asset inventory from scene and project for cross-referencing in analysis.
    public class AssetInventoryService
    {
        // Collect all asset information from the current scene.
        public AssetInventoryData CollectSceneAssets()
        {
            var inventory = new AssetInventoryData();

            var scene = SceneManager.GetActiveScene();
            inventory.SceneName = scene.name;
            inventory.ScenePath = scene.path;

            var rootObjects = scene.GetRootGameObjects();

            foreach (var root in rootObjects)
            {
                CollectGameObjectHierarchy(root.transform, inventory);
            }

            // Collect cameras
            foreach (var camera in Object.FindObjectsOfType<Camera>())
            {
                inventory.Cameras.Add(CollectCameraInfo(camera));
            }

            // Collect lights
            foreach (var light in Object.FindObjectsOfType<Light>())
            {
                inventory.Lights.Add(CollectLightInfo(light));
            }

            // Calculate totals
            inventory.TotalTextureMemoryBytes = inventory.Textures.Sum(t => t.MemoryBytes);
            inventory.TotalMeshMemoryBytes = inventory.Meshes.Sum(m => m.MemoryBytes);
            inventory.TotalAudioMemoryBytes = inventory.AudioClips.Sum(a => a.MemoryBytes);

            return inventory;
        }

        private void CollectGameObjectHierarchy(Transform transform, AssetInventoryData inventory)
        {
            var go = transform.gameObject;

            // Collect scene object info
            inventory.SceneObjects.Add(CollectSceneObjectInfo(go));

            // Collect renderer assets
            var renderer = go.GetComponent<Renderer>();
            if (renderer != null)
            {
                CollectRendererAssets(renderer, inventory);
            }

            // Collect audio sources
            var audioSource = go.GetComponent<AudioSource>();
            if (audioSource != null && audioSource.clip != null)
            {
                if (!inventory.AudioClips.Any(a => a.Name == audioSource.clip.name))
                {
                    inventory.AudioClips.Add(CollectAudioInfo(audioSource.clip));
                }
            }

            // Recurse children
            foreach (Transform child in transform)
            {
                CollectGameObjectHierarchy(child, inventory);
            }
        }

        private SceneObjectInfo CollectSceneObjectInfo(GameObject go)
        {
            var components = go.GetComponents<Component>();
            var renderer = go.GetComponent<Renderer>();
            var meshFilter = go.GetComponent<MeshFilter>();

            // Use explicit null checks for Unity Objects (?.  doesn't work correctly with Unity's null semantics)
            string meshName = null;
            if (meshFilter != null && meshFilter.sharedMesh != null)
            {
                meshName = meshFilter.sharedMesh.name;
            }

            string materialName = null;
            if (renderer != null && renderer.sharedMaterial != null)
            {
                materialName = renderer.sharedMaterial.name;
            }

            return new SceneObjectInfo
            {
                Name = go.name,
                Path = GetGameObjectPath(go),
                IsStatic = go.isStatic,
                IsActive = go.activeInHierarchy,
                Layer = LayerMask.LayerToName(go.layer),
                ComponentCount = components.Length,
                ComponentTypes = components.Where(c => c != null).Select(c => c.GetType().Name).ToArray(),
                HasRenderer = renderer != null,
                HasCollider = go.GetComponent<Collider>() != null,
                HasRigidbody = go.GetComponent<Rigidbody>() != null,
                MeshName = meshName,
                MaterialName = materialName
            };
        }

        private void CollectRendererAssets(Renderer renderer, AssetInventoryData inventory)
        {
            // Collect mesh from MeshFilter or SkinnedMeshRenderer
            Mesh mesh = null;
            var meshFilter = renderer.GetComponent<MeshFilter>();
            if (meshFilter != null)
            {
                mesh = meshFilter.sharedMesh;
            }
            else if (renderer is SkinnedMeshRenderer skinnedRenderer)
            {
                mesh = skinnedRenderer.sharedMesh;
            }

            if (mesh != null && !inventory.Meshes.Any(m => m.Name == mesh.name))
            {
                inventory.Meshes.Add(CollectMeshInfo(mesh));
            }

            // Collect materials and their textures
            foreach (var material in renderer.sharedMaterials)
            {
                if (material == null)
                {
                    continue;
                }

                if (!inventory.Materials.Any(m => m.Name == material.name))
                {
                    inventory.Materials.Add(CollectMaterialInfo(material));
                }

                // Collect shader
                if (material.shader != null && !inventory.Shaders.Any(s => s.Name == material.shader.name))
                {
                    inventory.Shaders.Add(CollectShaderInfo(material.shader));
                }

                // Collect textures from material
                CollectMaterialTextures(material, inventory);
            }
        }

        private void CollectMaterialTextures(Material material, AssetInventoryData inventory)
        {
#if UNITY_EDITOR
            var shader = material.shader;
            var propertyCount = ShaderUtil.GetPropertyCount(shader);

            for (var i = 0; i < propertyCount; i++)
            {
                if (ShaderUtil.GetPropertyType(shader, i) != ShaderUtil.ShaderPropertyType.TexEnv)
                {
                    continue;
                }

                var propertyName = ShaderUtil.GetPropertyName(shader, i);
                var texture = material.GetTexture(propertyName) as Texture2D;

                if (texture != null && !inventory.Textures.Any(t => t.Name == texture.name))
                {
                    inventory.Textures.Add(CollectTextureInfo(texture));
                }
            }
#endif
        }

        private TextureAssetInfo CollectTextureInfo(Texture2D texture)
        {
            var info = new TextureAssetInfo
            {
                Name = texture.name,
                Width = texture.width,
                Height = texture.height,
                Format = texture.format.ToString(),
                MemoryBytes = Profiler.GetRuntimeMemorySizeLong(texture),
                HasMipmaps = texture.mipmapCount > 1,
                FilterMode = texture.filterMode.ToString(),
                WrapMode = texture.wrapMode.ToString(),
                AnisoLevel = texture.anisoLevel,
                IsRenderTexture = false
            };

#if UNITY_EDITOR
            var path = AssetDatabase.GetAssetPath(texture);
            info.AssetPath = path;

            if (!string.IsNullOrEmpty(path))
            {
                var importer = AssetImporter.GetAtPath(path) as TextureImporter;
                if (importer != null)
                {
                    info.ReadWriteEnabled = importer.isReadable;
                }
            }
#endif

            return info;
        }

        private MeshAssetInfo CollectMeshInfo(Mesh mesh)
        {
            var info = new MeshAssetInfo
            {
                Name = mesh.name,
                VertexCount = mesh.vertexCount,
                TriangleCount = mesh.triangles.Length / 3,
                SubMeshCount = mesh.subMeshCount,
                MemoryBytes = Profiler.GetRuntimeMemorySizeLong(mesh),
                BoundsSize = $"{mesh.bounds.size.x:F2}x{mesh.bounds.size.y:F2}x{mesh.bounds.size.z:F2}",
                HasNormals = mesh.normals != null && mesh.normals.Length > 0,
                HasTangents = mesh.tangents != null && mesh.tangents.Length > 0,
                HasUV = mesh.uv != null && mesh.uv.Length > 0,
                HasColors = mesh.colors != null && mesh.colors.Length > 0
            };

#if UNITY_EDITOR
            var path = AssetDatabase.GetAssetPath(mesh);
            info.AssetPath = path;

            if (!string.IsNullOrEmpty(path))
            {
                var importer = AssetImporter.GetAtPath(path) as ModelImporter;
                if (importer != null)
                {
                    info.ReadWriteEnabled = importer.isReadable;
                }
            }
#endif

            return info;
        }

        private MaterialAssetInfo CollectMaterialInfo(Material material)
        {
            var texturePropertyNames = new List<string>();

#if UNITY_EDITOR
            var shader = material.shader;
            var propertyCount = ShaderUtil.GetPropertyCount(shader);

            for (var i = 0; i < propertyCount; i++)
            {
                if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv)
                {
                    var propertyName = ShaderUtil.GetPropertyName(shader, i);
                    if (material.GetTexture(propertyName) != null)
                    {
                        texturePropertyNames.Add(propertyName);
                    }
                }
            }
#endif

            return new MaterialAssetInfo
            {
                Name = material.name,
                AssetPath = GetAssetPath(material),
                ShaderName = material.shader?.name ?? "Unknown",
                RenderQueue = material.renderQueue,
                GpuInstancingEnabled = material.enableInstancing,
                PassCount = material.passCount,
                TexturePropertyNames = texturePropertyNames.ToArray(),
                TextureCount = texturePropertyNames.Count
            };
        }

        private AudioAssetInfo CollectAudioInfo(AudioClip clip)
        {
            return new AudioAssetInfo
            {
                Name = clip.name,
                AssetPath = GetAssetPath(clip),
                DurationSeconds = clip.length,
                Channels = clip.channels,
                Frequency = clip.frequency,
                MemoryBytes = Profiler.GetRuntimeMemorySizeLong(clip),
                LoadType = clip.loadType.ToString(),
                Preload = clip.preloadAudioData,
                LoadInBackground = clip.loadInBackground
            };
        }

        private ShaderAssetInfo CollectShaderInfo(Shader shader)
        {
            return new ShaderAssetInfo
            {
                Name = shader.name,
                PassCount = shader.passCount,
                IsSupported = shader.isSupported,
                RenderQueue = shader.renderQueue,
                PropertyCount = shader.GetPropertyCount()
            };
        }

        private CameraInfo CollectCameraInfo(Camera camera)
        {
            return new CameraInfo
            {
                Name = camera.name,
                IsActive = camera.isActiveAndEnabled,
                Depth = camera.depth,
                ClearFlags = camera.clearFlags.ToString(),
                RenderingPath = camera.renderingPath.ToString(),
                FieldOfView = camera.fieldOfView,
                NearClip = camera.nearClipPlane,
                FarClip = camera.farClipPlane,
                UseOcclusionCulling = camera.useOcclusionCulling,
                CullingMask = camera.cullingMask,
                TargetDisplay = camera.targetDisplay
            };
        }

        private LightInfo CollectLightInfo(Light light)
        {
            return new LightInfo
            {
                Name = light.name,
                Type = light.type.ToString(),
                IsActive = light.isActiveAndEnabled,
                ShadowType = light.shadows.ToString(),
                Intensity = light.intensity,
                Range = light.range,
                RenderMode = light.renderMode.ToString()
            };
        }

        private string GetGameObjectPath(GameObject go)
        {
            var path = go.name;
            var parent = go.transform.parent;

            while (parent != null)
            {
                path = parent.name + "/" + path;
                parent = parent.parent;
            }

            return path;
        }

        private string GetAssetPath(Object obj)
        {
#if UNITY_EDITOR
            return AssetDatabase.GetAssetPath(obj);
#else
            return "";
#endif
        }
    }

    // Container for all collected asset inventory data.
    [System.Serializable]
    public class AssetInventoryData
    {
        public string SceneName { get; set; }
        public string ScenePath { get; set; }

        public List<TextureAssetInfo> Textures { get; set; } = new List<TextureAssetInfo>();
        public List<MeshAssetInfo> Meshes { get; set; } = new List<MeshAssetInfo>();
        public List<MaterialAssetInfo> Materials { get; set; } = new List<MaterialAssetInfo>();
        public List<AudioAssetInfo> AudioClips { get; set; } = new List<AudioAssetInfo>();
        public List<ShaderAssetInfo> Shaders { get; set; } = new List<ShaderAssetInfo>();
        public List<SceneObjectInfo> SceneObjects { get; set; } = new List<SceneObjectInfo>();
        public List<CameraInfo> Cameras { get; set; } = new List<CameraInfo>();
        public List<LightInfo> Lights { get; set; } = new List<LightInfo>();

        public long TotalTextureMemoryBytes { get; set; }
        public long TotalMeshMemoryBytes { get; set; }
        public long TotalAudioMemoryBytes { get; set; }
    }
}
