using System;
using System.Collections.Generic;
using System.Linq;

using AIRuntime.Editor;
using AIRuntime.Runtime.Agents;
using AIRuntime.Runtime.Capabilities;
using AIRuntime.Runtime.Capabilities.Permissions;
using AIRuntime.Runtime.Filesystem;
using AIRuntime.Runtime.Providers;
using AIRuntime.Runtime.Providers.Testing;

using NUnit.Framework;


namespace AIRuntime.Tests.EditMode
{
    public sealed class EditorPluginCatalogTests
    {
        private sealed class TestProviderPlugin :
            IAIRuntimeEditorProviderPlugin
        {
            private readonly string _id;
            private readonly string[] _aliases;


            public TestProviderPlugin(
                string id,
                params string[] aliases)
            {
                _id = id;
                _aliases = aliases;
            }


            public string Id => _id;

            public string DisplayName => _id;

            public bool RequiresApiKey => false;

            public string DefaultBaseUrl => "http://localhost";

            public string DefaultModel => "test-model";

            public IReadOnlyList<string> Aliases => _aliases;


            public IProvider CreateProvider(
                AIRuntimeEditorProviderSettings settings)
            {
                return new FakeProvider();
            }
        }


        private sealed class TestCapabilityPlugin :
            IAIRuntimeEditorCapabilityPlugin
        {
            public TestCapabilityPlugin(
                string name,
                AIRuntimeEditorPluginHost hosts)
            {
                Name = name;
                Hosts = hosts;
            }


            public string Name { get; }

            public string Label => Name;

            public CapabilityPermission Permission =>
                CapabilityPermission.ReadOnly;

            public AIRuntimeEditorPluginHost Hosts { get; }


            public ICapability CreateCapability()
            {
                return new RenamedCapability(
                    Name);
            }
        }


        private sealed class ConfigurationOwnedProviderPlugin :
            IAIRuntimeEditorProviderPlugin,
            IAIRuntimeEditorProviderConfigurationPlugin
        {
            public ConfigurationOwnedProviderPlugin(
                string id)
            {
                Id = id;
            }


            public string Id { get; }

            public string DisplayName => Id;

            public bool RequiresApiKey => false;

            public string DefaultBaseUrl => string.Empty;

            public string DefaultModel => string.Empty;

            public IReadOnlyList<string> Aliases => Array.Empty<string>();

            public bool RequiresModel => false;

            public bool RequiresBaseUrl => false;

            public float MaximumAgentTemperature => 1.5f;

            public string ConfigurationWarning => string.Empty;

            public string AgentOptionsWarning => string.Empty;


            public IProvider CreateProvider(
                AIRuntimeEditorProviderSettings settings)
            {
                return new FakeProvider();
            }


            public void DrawAdditionalSettings(
                AIRuntimeEditorProviderSettings settings)
            {
            }


            public void ValidateSettings(
                AIRuntimeEditorProviderSettings settings)
            {
                if (!string.Equals(
                        settings.ApplicationTitle,
                        "valid",
                        StringComparison.Ordinal))
                {
                    throw new InvalidOperationException(
                        "Provider-owned validation failed.");
                }
            }
        }


        private sealed class RenamedCapability :
            ICapability
        {
            private readonly FileExistsCapability _inner =
                new FileExistsCapability();


            public RenamedCapability(
                string name)
            {
                Name = name;
            }


            public string Name { get; }

            public string Description => _inner.Description;

            public CapabilityPermission Permission => _inner.Permission;

            public Newtonsoft.Json.Linq.JObject Parameters => _inner.Parameters;

            public Type ArgumentType => _inner.ArgumentType;


            public System.Threading.Tasks.Task<object> ExecuteObjectAsync(
                object arguments,
                CapabilityContext context)
            {
                return ((ICapability)_inner).ExecuteObjectAsync(
                    arguments,
                    context);
            }
        }


        private sealed class TestAgentPlugin :
            IAIRuntimeEditorAgentPlugin
        {
            public TestAgentPlugin(
                string id,
                string definitionId = null)
            {
                Id = id;
                DefinitionId =
                    definitionId ??
                    id + "Agent";
            }


            public string Id { get; }

            public string DisplayName => Id;

            public string DefinitionId { get; }


            public AgentDefinition CreateDefinition()
            {
                return new AgentDefinition(
                    DefinitionId,
                    DisplayName,
                    "Test plugin agent.",
                    "Test system prompt.");
            }
        }


        [Test]
        public void ProviderPlugin_AliasResolvesAndFactoryRuns()
        {
            string id =
                "Provider-" + Guid.NewGuid().ToString("N");

            string alias =
                "Alias-" + Guid.NewGuid().ToString("N");

            using (
                IDisposable registration =
                    AIRuntimeEditorPluginCatalog.RegisterProvider(
                        new TestProviderPlugin(
                            id,
                            alias)))
            {
                Assert.AreEqual(
                    id,
                    AIRuntimeEditorPluginCatalog.NormalizeProviderId(
                        alias));

                Assert.IsInstanceOf<FakeProvider>(
                    AIRuntimeEditorPluginCatalog.CreateProvider(
                        alias,
                        new AIRuntimeEditorProviderSettings()));
            }
        }


        [Test]
        public void ProviderPlugin_OwnsOptionalRequirementsAndValidation()
        {
            string id =
                "ConfiguredProvider-" +
                Guid.NewGuid().ToString("N");

            using (
                IDisposable registration =
                    AIRuntimeEditorPluginCatalog.RegisterProvider(
                        new ConfigurationOwnedProviderPlugin(
                            id)))
            {
                Assert.DoesNotThrow(
                    () => AIRuntimeEditorPluginCatalog.ValidateProviderSettings(
                        id,
                        new AIRuntimeEditorProviderSettings
                        {
                            Model = string.Empty,
                            BaseUrl = string.Empty,
                            ApplicationTitle = "valid"
                        }));

                Assert.Throws<InvalidOperationException>(
                    () => AIRuntimeEditorPluginCatalog.ValidateProviderSettings(
                        id,
                        new AIRuntimeEditorProviderSettings
                        {
                            ApplicationTitle = "invalid"
                        }));
            }
        }


        [Test]
        public void CapabilityPlugin_IsVisibleOnlyInDeclaredHost()
        {
            string name =
                "test_capability_" + Guid.NewGuid().ToString("N");

            using (
                IDisposable registration =
                    AIRuntimeEditorPluginCatalog.RegisterCapability(
                        new TestCapabilityPlugin(
                            name,
                            AIRuntimeEditorPluginHost.Kanban)))
            {
                CollectionAssert.DoesNotContain(
                    AIRuntimeEditorPluginCatalog
                        .GetCapabilityPlugins(
                            AIRuntimeEditorPluginHost.ChatConsole)
                        .Select(plugin => plugin.Name)
                        .ToArray(),
                    name);

                Assert.AreEqual(
                    name,
                    AIRuntimeEditorPluginCatalog.CreateCapability(
                            name,
                            AIRuntimeEditorPluginHost.Kanban)
                        .Name);

                Assert.Throws<InvalidOperationException>(
                    () => AIRuntimeEditorPluginCatalog.CreateCapability(
                        name,
                        AIRuntimeEditorPluginHost.ChatConsole));
            }
        }


        [Test]
        public void AgentPlugin_AppearsWithoutChangingKanbanOrChatCode()
        {
            string id =
                "Agent-" + Guid.NewGuid().ToString("N");

            using (
                IDisposable registration =
                    AIRuntimeEditorPluginCatalog.RegisterAgent(
                        new TestAgentPlugin(
                            id)))
            {
                CollectionAssert.Contains(
                    AIRuntimeEditorPluginCatalog
                        .GetAgentPlugins()
                        .Select(plugin => plugin.Id)
                        .ToArray(),
                    id);

                Assert.AreEqual(
                    id + "Agent",
                    AIRuntimeEditorPluginCatalog
                        .CreateAgentDefinition(
                            id)
                        .Id);

                Assert.Throws<InvalidOperationException>(
                    () => AIRuntimeEditorPluginCatalog.RegisterAgent(
                        new TestAgentPlugin(
                            id)));
            }
        }


        [Test]
        public void AgentPlugin_DuplicateExecutionIdentityIsRejected()
        {
            string suffix =
                Guid.NewGuid().ToString("N");

            string definitionId =
                "SharedAgent-" + suffix;

            using (
                IDisposable registration =
                    AIRuntimeEditorPluginCatalog.RegisterAgent(
                        new TestAgentPlugin(
                            "Agent-A-" + suffix,
                            definitionId)))
            {
                InvalidOperationException exception =
                    Assert.Throws<InvalidOperationException>(
                        () => AIRuntimeEditorPluginCatalog.RegisterAgent(
                            new TestAgentPlugin(
                                "Agent-B-" + suffix,
                                definitionId)));

                StringAssert.Contains(
                    definitionId,
                    exception.Message);
            }
        }


        [Test]
        public void PluginRegistration_RejectsAmbiguousIdsAliasesAndHosts()
        {
            string suffix =
                Guid.NewGuid().ToString("N");

            Assert.Throws<ArgumentException>(
                () => AIRuntimeEditorPluginCatalog.RegisterProvider(
                    new TestProviderPlugin(
                        " Provider-" + suffix + " ")));

            Assert.Throws<InvalidOperationException>(
                () => AIRuntimeEditorPluginCatalog.RegisterProvider(
                    new TestProviderPlugin(
                        "Provider-" + suffix,
                        "Provider-" + suffix)));

            Assert.Throws<ArgumentException>(
                () => AIRuntimeEditorPluginCatalog.RegisterCapability(
                    new TestCapabilityPlugin(
                        "hostless_" + suffix,
                        AIRuntimeEditorPluginHost.None)));
        }
    }
}
