using System;
using System.IO;
using JetBrains.Annotations;
using LLM.Editor.Core;
using LLM.Editor.Helper;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

namespace LLM.Editor.Commands
{
    public enum AssetType
    {
        Folder,
        Text,
        Script,
        Material,
        AssemblyDefinition,
        Prefab
    }

    [Serializable]
    public class CreateAssetParams
    {
        public AssetType assetType;
        public string assetName;
        public string logicalName; // A unique name for the LLM to reference this object later
        public string path; // Relative to Assets folder
        public string content;
        public string sourceAssetGuid; // Optional, for creating prefabs from models
        public string sourceGameObjectIdentifier; // Optional, for creating prefabs from scene objects
        public string shaderName; // Optional, for creating materials
    }

    [UsedImplicitly]
    public class CreateAssetCommand : ICommand
    {
        private readonly CreateAssetParams _params;

        public CreateAssetCommand(string jsonParams)
        {
            _params = jsonParams?.FromJson<CreateAssetParams>();
        }

        public CommandOutcome Execute(Data.CommandContext context)
        {
            if (_params == null || string.IsNullOrEmpty(_params.assetName))
            {
                context.ErrorMessage = "Invalid parameters: assetName is required.";
                return CommandOutcome.Error;
            }
            
            var relativePath = _params.path?.Replace("Assets/", string.Empty) ?? "";
            var fullPath = Path.Combine(Application.dataPath, relativePath, _params.assetName);
            var assetPathForDb = $"Assets/{Path.Combine(relativePath, _params.assetName)}";

            var dirName = Path.GetDirectoryName(fullPath);
            if (!string.IsNullOrEmpty(dirName) && !Directory.Exists(dirName))
            {
                Directory.CreateDirectory(dirName);
            }

            try
            {
                switch (_params.assetType)
                {
                    case AssetType.Folder:
                        Directory.CreateDirectory(fullPath);
                        assetPathForDb += "/";
                        break;
                    case AssetType.Text:
                        File.WriteAllText(fullPath, _params.content ?? "");
                        break;
                    case AssetType.Script:
                        assetPathForDb += ".cs";
                        fullPath += ".cs";
                        File.WriteAllText(fullPath, _params.content ?? "using UnityEngine;\n\npublic class NewScript : MonoBehaviour\n{\n}\n");
                        break;
                    case AssetType.Material:
                        assetPathForDb += ".mat";
                        var shader = string.IsNullOrEmpty(_params.shaderName) ? Shader.Find("Standard") : Shader.Find(_params.shaderName);
                        if (shader == null)
                        {
                            Debug.LogWarning($"[CreateAssetCommand] Shader '{_params.shaderName}' not found. Falling back to Standard.");
                            shader = Shader.Find("Standard");
                        }
                        var material = new Material(shader);
                        AssetDatabase.CreateAsset(material, assetPathForDb);
                        break;
                    case AssetType.AssemblyDefinition:
                        assetPathForDb += ".asmdef";
                        var asmdefContent = _params.content ?? $"{{\n    \"name\": \"{_params.assetName}\"\n}}";
                        File.WriteAllText(assetPathForDb, asmdefContent);
                        break;
                    case AssetType.Prefab:
                        assetPathForDb += ".prefab";
                        CreatePrefab(context, assetPathForDb);
                        break;
                    default:
                        context.ErrorMessage = $"Unsupported asset type: {_params.assetType}";
                        return CommandOutcome.Error;
                }
                
                AssetDatabase.Refresh();

                // Folders don't have GUIDs in the same way assets do, so we handle them differently.
                // Their path is their identifier.
                if (_params.assetType == AssetType.Folder)
                {
                    var logicalNameForFolder = !string.IsNullOrEmpty(_params.logicalName) ? _params.logicalName : $"{_params.assetName}_{Guid.NewGuid():N}";
                    var pId = new Data.PersistentIdentifier { logicalName = logicalNameForFolder, path = assetPathForDb };
                    context.IdentifierMap[logicalNameForFolder] = pId;
                    SessionManager.SaveIdentifierMap(context.IdentifierMap);
                    context.CurrentSubject = logicalNameForFolder;
                    Debug.Log($"[CreateAssetCommand] Successfully created folder '{_params.assetName}' and mapped it to logical name '{logicalNameForFolder}'.");
                    return CommandOutcome.Success;
                }
                
                // For all other assets, we require a valid GUID.
                var assetGuid = AssetDatabase.AssetPathToGUID(assetPathForDb);
                if (string.IsNullOrEmpty(assetGuid))
                {
                    AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
                    assetGuid = AssetDatabase.AssetPathToGUID(assetPathForDb);
                    if (string.IsNullOrEmpty(assetGuid))
                    {
                        throw new Exception($"Could not get GUID for newly created asset at path: {assetPathForDb}");
                    }
                }
                
                var logicalName = !string.IsNullOrEmpty(_params.logicalName) ? _params.logicalName : $"{_params.assetName}_{Guid.NewGuid():N}";
                var pIdAsset = new Data.PersistentIdentifier { logicalName = logicalName, guid = assetGuid };
                context.IdentifierMap[logicalName] = pIdAsset;
                SessionManager.SaveIdentifierMap(context.IdentifierMap);
                
                context.CurrentSubject = logicalName;

                Debug.Log($"[CreateAssetCommand] Successfully created asset '{_params.assetName}' and mapped it to logical name '{logicalName}'.");
                return CommandOutcome.Success;
            }
            catch (Exception e)
            {
                context.ErrorMessage = $"Failed to create asset '{_params.assetName}'. Exception: {e.Message}";
                Debug.LogException(e);
                return CommandOutcome.Error;
            }
        }

        private void CreatePrefab(Data.CommandContext context, string finalPrefabPath)
        {
            GameObject sourceObject = null;

            if (!string.IsNullOrEmpty(_params.sourceGameObjectIdentifier))
            {
                var resolvedObject = CommandUtility.ResolveIdentifier(context, _params.sourceGameObjectIdentifier);
                if (resolvedObject is GameObject go)
                {
                    sourceObject = go;
                }
                else
                {
                    throw new Exception($"Could not resolve scene object with logical name: '{_params.sourceGameObjectIdentifier}'");
                }
            }
            else if (!string.IsNullOrEmpty(_params.sourceAssetGuid))
            {
                var sourcePath = AssetDatabase.GUIDToAssetPath(_params.sourceAssetGuid);
                sourceObject = AssetDatabase.LoadAssetAtPath<GameObject>(sourcePath);
                if (sourceObject == null)
                {
                    throw new Exception($"Could not find source asset with GUID: {_params.sourceAssetGuid}");
                }
            }
            else
            {
                sourceObject = new GameObject(_params.assetName);
                // Save the new empty object as a prefab, then immediately destroy the instance.
                PrefabUtility.SaveAsPrefabAsset(sourceObject, finalPrefabPath);
                Object.DestroyImmediate(sourceObject);
                return;
            }
            
            // For existing scene objects or assets, save as a prefab.
            PrefabUtility.SaveAsPrefabAssetAndConnect(sourceObject, finalPrefabPath, InteractionMode.UserAction);
        }
    }
}
