using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Data.Sqlite;
using YMhut.Box.Core.App;
using YMhut.Box.Core.Plugins;
using YMhut.Box.Core.Settings;
using YMhut.Box.Core.Tools;

namespace YMhut.Box.Tests;

[TestClass]
public sealed class PluginTests
{
    [TestMethod]
    public async Task PluginRegistryLoadsValidManifestAndToolModule()
    {
        using var workspace = TempWorkspace();
        var pluginRoot = CreatePlugin(workspace.Path, "hello-tools", """
{
  "id": "hello-tools",
  "name": "Hello Tools",
  "version": "1.0.0",
  "author": "tester",
  "description": "Test plugin",
  "entry": "index.html",
  "permissions": ["Input", "Output", "Log", "Storage"],
  "surfaces": [
    { "kind": "ToolboxTool", "id": "hello", "name": "Hello", "description": "Hello tool", "entry": "index.html", "category": "dev" },
    { "kind": "NavPage", "id": "home", "name": "Home", "description": "Home page", "entry": "index.html" }
  ],
  "resources": ["index.html"]
}
""");
        File.WriteAllText(Path.Combine(pluginRoot, "index.html"), "<h1>Hello</h1>");
        var paths = AppPaths.ForCurrentUser(workspace.Path);
        var stateStore = new PluginStateStore(paths);
        await stateStore.SetEnabledAsync("hello-tools", true);

        var registry = new PluginRegistryService(paths, stateStore);
        var plugins = await registry.LoadPluginsAsync();
        var tools = await registry.LoadEnabledToolModulesAsync();

        Assert.HasCount(1, plugins);
        Assert.IsTrue(plugins[0].IsValid, string.Join(", ", plugins[0].Errors));
        Assert.HasCount(1, tools);
        Assert.AreEqual("plugin:hello-tools:hello", tools[0].Id);
    }

    [TestMethod]
    public async Task PluginRegistryRejectsInvalidAndCoreAssetOverride()
    {
        using var workspace = TempWorkspace();
        var pluginRoot = CreatePlugin(workspace.Path, "bad", """
{
  "id": "plugin:bad",
  "name": "Bad",
  "version": "1.0.0",
  "author": "tester",
  "description": "Bad plugin",
  "entry": "index.html",
  "permissions": [],
  "surfaces": [
    { "kind": "ToolboxTool", "id": "json_formatter", "name": "Override", "description": "conflict", "entry": "index.html" }
  ],
  "resources": ["Assets/icons/app_icon.ico"]
}
""");
        File.WriteAllText(Path.Combine(pluginRoot, "index.html"), "<h1>Bad</h1>");
        var paths = AppPaths.ForCurrentUser(workspace.Path);
        var registry = new PluginRegistryService(paths, new PluginStateStore(paths));

        var plugin = (await registry.LoadPluginsAsync()).Single();

        Assert.IsFalse(plugin.IsValid);
        Assert.IsTrue(plugin.Errors.Any(error => error.Contains("plugin:", StringComparison.OrdinalIgnoreCase)));
        Assert.IsTrue(plugin.Errors.Any(error => error.Contains("built-in", StringComparison.OrdinalIgnoreCase)));
        Assert.IsTrue(plugin.Errors.Any(error => error.Contains("Core asset", StringComparison.OrdinalIgnoreCase)));
    }

    [TestMethod]
    public async Task PluginStateStorePersistsStatePermissionsSurfacesAndKv()
    {
        using var workspace = TempWorkspace();
        var paths = AppPaths.ForCurrentUser(workspace.Path);
        var store = new PluginStateStore(paths);

        await store.SetEnabledAsync("demo", true);
        await store.SetPermissionAsync("demo", PluginPermission.Log, true);
        await store.SetSurfaceMountedAsync("demo", "tool", true);
        await store.SetValueAsync("demo", "name", "value");

        var secondStore = new PluginStateStore(paths);
        var state = await secondStore.GetStateAsync("demo");
        var values = await secondStore.ListValuesAsync("demo");

        Assert.IsTrue(state.Enabled);
        CollectionAssert.Contains(state.GrantedPermissions.ToList(), PluginPermission.Log);
        CollectionAssert.Contains(state.MountedSurfaceIds.ToList(), "tool");
        Assert.AreEqual("value", values["name"]);
    }

    [TestMethod]
    public async Task PluginToolsMergeIntoToolCatalogWhenEnabled()
    {
        using var workspace = TempWorkspace();
        var pluginRoot = CreatePlugin(workspace.Path, "merge-demo", """
{
  "id": "merge-demo",
  "name": "Merge Demo",
  "version": "1.0.0",
  "author": "tester",
  "description": "Merge plugin",
  "entry": "index.html",
  "permissions": [],
  "surfaces": [
    { "kind": "ToolboxTool", "id": "tool", "name": "Merged Tool", "description": "Merged", "entry": "index.html" }
  ],
  "resources": ["index.html"]
}
""");
        File.WriteAllText(Path.Combine(pluginRoot, "index.html"), "<h1>Merged</h1>");
        var paths = AppPaths.ForCurrentUser(workspace.Path);
        var store = new PluginStateStore(paths);
        await store.SetEnabledAsync("merge-demo", true);
        var registry = new PluginRegistryService(paths, store);

        var catalog = new ToolCatalog(ToolCatalog.DefaultModules().Concat(await registry.LoadEnabledToolModulesAsync()));

        Assert.IsNotNull(catalog.GetById("plugin:merge-demo:tool"));
    }

    [TestMethod]
    public async Task PluginRegistryHonorsSettingsEnabledAndCustomRoot()
    {
        using var workspace = TempWorkspace();
        var paths = AppPaths.ForCurrentUser(workspace.Path);
        var settings = new AppSettingsService(new AppSettingsStore(paths.Root));
        await settings.LoadAsync();
        var store = new PluginStateStore(paths);
        var defaultPluginRoot = CreatePlugin(workspace.Path, "default-demo", BasicManifest("default-demo"));
        File.WriteAllText(Path.Combine(defaultPluginRoot, "index.html"), "<h1>Default</h1>");
        await store.SetEnabledAsync("default-demo", true);
        var registry = new PluginRegistryService(paths, store, settingsService: settings);

        Assert.IsFalse(settings.Current.PluginsEnabled);
        Assert.HasCount(0, await registry.LoadEnabledToolModulesAsync());

        var customPluginsRoot = Path.Combine(workspace.Path, "ExternalPlugins");
        var customPluginRoot = CreatePluginAtRoot(customPluginsRoot, "custom-demo", BasicManifest("custom-demo"));
        File.WriteAllText(Path.Combine(customPluginRoot, "index.html"), "<h1>Custom</h1>");
        await store.SetEnabledAsync("custom-demo", true);
        await settings.UpdateAsync(value =>
        {
            value.PluginsEnabled = true;
            value.PluginRootPath = customPluginsRoot;
        });

        Assert.AreEqual(Path.GetFullPath(customPluginsRoot), registry.PluginsRoot);
        var tools = await registry.LoadEnabledToolModulesAsync();
        Assert.HasCount(1, tools);
        Assert.AreEqual("plugin:custom-demo:tool", tools[0].Id);
    }

    [TestMethod]
    public async Task PluginRegistryRequiresReadmeFile()
    {
        using var workspace = TempWorkspace();
        var pluginRoot = CreatePlugin(workspace.Path, "no-readme", BasicManifest("no-readme"));
        File.WriteAllText(Path.Combine(pluginRoot, "index.html"), "<h1>No readme</h1>");
        File.Delete(Path.Combine(pluginRoot, "README.md"));
        var paths = AppPaths.ForCurrentUser(workspace.Path);
        var registry = new PluginRegistryService(paths, new PluginStateStore(paths));

        var plugin = (await registry.LoadPluginsAsync()).Single();

        Assert.IsFalse(plugin.IsValid);
        Assert.IsTrue(plugin.Errors.Any(error => error.Contains("README", StringComparison.OrdinalIgnoreCase)));
    }

    [TestMethod]
    public async Task PluginSnapshotRoundTripsEnabledToolModules()
    {
        using var workspace = TempWorkspace();
        var pluginRoot = CreatePlugin(workspace.Path, "snapshot-demo", BasicManifest("snapshot-demo"));
        File.WriteAllText(Path.Combine(pluginRoot, "index.html"), "<h1>Snapshot</h1>");
        var paths = AppPaths.ForCurrentUser(workspace.Path);
        var store = new PluginStateStore(paths);
        await store.SetEnabledAsync("snapshot-demo", true);
        var registry = new PluginRegistryService(paths, store);
        var plugin = (await registry.LoadPluginsAsync()).Single();
        var tool = (await registry.LoadEnabledToolModulesAsync()).Single();

        var snapshot = new PluginSnapshot(
            true,
            registry.PluginsRoot,
            [LoadedPluginDto.FromLoadedPlugin(plugin)],
            [PluginToolDto.FromPluginToolModule(tool)],
            DateTimeOffset.Now);
        var message = new PluginHostMessage(PluginHostProtocol.SnapshotChanged, Snapshot: snapshot);
        var roundTrip = PluginHostProtocol.Deserialize(PluginHostProtocol.Serialize(message));

        Assert.IsNotNull(roundTrip?.Snapshot);
        Assert.HasCount(1, roundTrip.Snapshot.EnabledTools);
        Assert.AreEqual("plugin:snapshot-demo:tool", roundTrip.Snapshot.EnabledTools[0].ToToolModule().Id);
    }

    [TestMethod]
    public async Task BuiltInPluginInstallerCopiesMissingPluginOnly()
    {
        using var workspace = TempWorkspace();
        var paths = AppPaths.ForCurrentUser(Path.Combine(workspace.Path, "App"));
        var installer = new BuiltInPluginInstallerService(paths);
        await installer.EnsureInstalledAsync();

        var installed = Path.Combine(paths.Root, "Plugins", "ipcheck-demo", "index.html");
        var installedReadme = Path.Combine(paths.Root, "Plugins", "ipcheck-demo", "README.md");
        var installedManifest = Path.Combine(paths.Root, "Plugins", "ipcheck-demo", PluginManifest.FileName);
        Assert.IsTrue(File.Exists(installed));
        Assert.IsTrue(File.Exists(installedReadme));
        Assert.IsTrue(File.Exists(installedManifest));
        Assert.IsTrue(File.ReadAllText(installed).Contains("IPCheck 网络工具箱", StringComparison.OrdinalIgnoreCase));
        var manifestText = File.ReadAllText(installedManifest);
        Assert.IsTrue(manifestText.Contains("\"Http\"", StringComparison.OrdinalIgnoreCase));
        Assert.IsTrue(manifestText.Contains("\"OpenExternal\"", StringComparison.OrdinalIgnoreCase));
        Assert.IsTrue(manifestText.Contains("\"README.md\"", StringComparison.OrdinalIgnoreCase));

        File.WriteAllText(installed, "<h1>User version</h1>");
        await installer.EnsureInstalledAsync();

        Assert.AreEqual("<h1>User version</h1>", File.ReadAllText(installed));
    }

    [TestMethod]
    public async Task BuiltInSamplePluginIsValidAndDocumentsBridgeCapabilities()
    {
        using var workspace = TempWorkspace();
        var paths = AppPaths.ForCurrentUser(Path.Combine(workspace.Path, "App"));
        var installer = new BuiltInPluginInstallerService(paths);
        await installer.EnsureInstalledAsync();

        var stateStore = new PluginStateStore(paths);
        var registry = new PluginRegistryService(paths, stateStore);
        var plugin = (await registry.LoadPluginsAsync()).Single(item => item.Manifest.Id == "ipcheck-demo");

        Assert.IsTrue(plugin.IsValid, string.Join("; ", plugin.Errors));
        CollectionAssert.Contains(plugin.Manifest.Permissions.ToList(), PluginPermission.Storage);
        CollectionAssert.Contains(plugin.Manifest.Permissions.ToList(), PluginPermission.Output);
        CollectionAssert.Contains(plugin.Manifest.Permissions.ToList(), PluginPermission.OpenExternal);
        CollectionAssert.Contains(plugin.Manifest.Resources.ToList(), "README.md");

        var readme = File.ReadAllText(Path.Combine(plugin.RootPath, "README.md"));
        StringAssert.Contains(readme, "Bridge 能力示例");
        StringAssert.Contains(readme, "安全浏览器");
        StringAssert.Contains(readme, "AI 实现提示");
    }

    [TestMethod]
    public void PluginDocsAndToolResultAssetsExposeRequiredSections()
    {
        var root = FindRepositoryRoot();
        var pluginDocs = File.ReadAllText(Path.Combine(root, "docs", "plugins", "README.md"));
        var aiDocs = File.ReadAllText(Path.Combine(root, "docs", "plugins", "AI-INTEGRATION.md"));
        var helpPage = File.ReadAllText(Path.Combine(root, "src", "box-winUI", "Views", "PluginDocsPage.cs"));
        var resultJs = File.ReadAllText(Path.Combine(root, "src", "box-winUI", "Assets", "tool-results", "result.js"));
        var resultCss = File.ReadAllText(Path.Combine(root, "src", "box-winUI", "Assets", "tool-results", "result.css"));
        var pageJs = File.ReadAllText(Path.Combine(root, "src", "box-winUI", "Assets", "tool-pages", "tool-page.js"));
        var pageCss = File.ReadAllText(Path.Combine(root, "src", "box-winUI", "Assets", "tool-pages", "tool-page.css"));

        StringAssert.Contains(pluginDocs, "权限");
        StringAssert.Contains(pluginDocs, "安全边界");
        StringAssert.Contains(pluginDocs, "独立窗口");
        StringAssert.Contains(aiDocs, "Acceptance Checklist");
        StringAssert.Contains(helpPage, "输出区");
        StringAssert.Contains(helpPage, "常见问题");

        StringAssert.Contains(resultJs, "openSystemBrowser");
        StringAssert.Contains(pageJs, "openSystemBrowser");
        StringAssert.Contains(resultCss, "rank-1");
        StringAssert.Contains(pageCss, "rank-1");
        StringAssert.Contains(resultCss, "prefers-reduced-motion");
        StringAssert.Contains(pageCss, "prefers-reduced-motion");
    }

    private static string CreatePlugin(string root, string folder, string manifest)
    {
        var pluginRoot = Path.Combine(root, "Plugins", folder);
        Directory.CreateDirectory(pluginRoot);
        File.WriteAllText(Path.Combine(pluginRoot, PluginManifest.FileName), manifest);
        File.WriteAllText(Path.Combine(pluginRoot, "README.md"), "# Test plugin");
        return pluginRoot;
    }

    private static string CreatePluginAtRoot(string pluginsRoot, string folder, string manifest)
    {
        var pluginRoot = Path.Combine(pluginsRoot, folder);
        Directory.CreateDirectory(pluginRoot);
        File.WriteAllText(Path.Combine(pluginRoot, PluginManifest.FileName), manifest);
        File.WriteAllText(Path.Combine(pluginRoot, "README.md"), "# Test plugin");
        return pluginRoot;
    }

    private static string BasicManifest(string id) => $$"""
{
  "id": "{{id}}",
  "name": "{{id}}",
  "version": "1.0.0",
  "author": "tester",
  "description": "Test plugin",
  "entry": "index.html",
  "permissions": [],
  "surfaces": [
    { "kind": "ToolboxTool", "id": "tool", "name": "Tool", "description": "Tool", "entry": "index.html" }
  ],
  "resources": ["index.html"]
}
""";

    private static TempDirectory TempWorkspace()
    {
        return new TempDirectory(Path.Combine(Path.GetTempPath(), "ymhut-plugin-tests", Guid.NewGuid().ToString("N")));
    }

    private static string FindRepositoryRoot()
    {
        var directory = new DirectoryInfo(AppContext.BaseDirectory);
        while (directory is not null)
        {
            if (Directory.Exists(Path.Combine(directory.FullName, "src")) &&
                Directory.Exists(Path.Combine(directory.FullName, "docs")) &&
                File.Exists(Path.Combine(directory.FullName, "YMhut.Box.Native.sln")))
            {
                return directory.FullName;
            }

            directory = directory.Parent;
        }

        throw new DirectoryNotFoundException("Could not locate repository root from test output path.");
    }

    private sealed class TempDirectory : IDisposable
    {
        public TempDirectory(string path)
        {
            Path = path;
            Directory.CreateDirectory(path);
        }

        public string Path { get; }

        public void Dispose()
        {
            SqliteConnection.ClearAllPools();
            if (Directory.Exists(Path))
            {
                Directory.Delete(Path, recursive: true);
            }
        }
    }
}
