using CodeRemoteManager.SetupApp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Text.Json.Nodes;

namespace CodeRemoteManager.SetupApp.Tests;

[TestClass]
public sealed class ConfigurationMigrationTests
{
    [TestMethod]
    public void UpgradePreservesExistingAccountsWorkspacesAndProjectRoot()
    {
        const string existing = """
        {
          "codexCliPath": "old-codex.cmd",
          "engine": "codex",
          "bindHost": "127.0.0.1",
          "port": 43110,
          "dataDir": "private-data",
          "projectsRoot": "existing-project-root",
          "accounts": [
            { "id": "managed-a", "label": "Managed A", "codexHome": "home-a" },
            { "id": "history", "label": "History", "codexHome": "home-history", "historyOnly": true }
          ],
          "workspaces": [
            { "id": "workspace-a", "label": "Workspace A", "path": "project-a" },
            { "id": "workspace-b", "label": "Workspace B", "path": "project-b" }
          ]
        }
        """;

        var candidate = ConfigurationMigration.BuildCandidate(
            existing,
            new SetupConfigurationInput("current-codex.cmd", "https://device.example.ts.net", null),
            new SetupConfigurationDefaults("default-data", "desktop-home"));

        var accounts = candidate["accounts"]!.AsArray();
        var workspaces = candidate["workspaces"]!.AsArray();
        Assert.AreEqual(2, accounts.Count);
        Assert.AreEqual("managed-a", accounts[0]!["id"]!.GetValue<string>());
        Assert.AreEqual("Managed A", accounts[0]!["label"]!.GetValue<string>());
        Assert.IsTrue(accounts[1]!["historyOnly"]!.GetValue<bool>());
        Assert.AreEqual(2, workspaces.Count);
        Assert.AreEqual("workspace-a", workspaces[0]!["id"]!.GetValue<string>());
        Assert.AreEqual("existing-project-root", candidate["projectsRoot"]!.GetValue<string>());
    }

    [TestMethod]
    public void UpgradeAppendsOnlyNewOriginAndExplicitWorkspace()
    {
        const string existing = """
        {
          "codexCliPath": "old-codex.cmd",
          "engine": "codex",
          "bindHost": "127.0.0.1",
          "port": 43110,
          "allowedOrigins": ["https://previous.example.ts.net"],
          "dataDir": "private-data",
          "projectsRoot": "existing-project-root",
          "accounts": [
            { "id": "managed-a", "label": "Managed A", "codexHome": "home-a" },
            { "id": "history", "label": "History", "codexHome": "home-history", "historyOnly": true }
          ],
          "workspaces": [
            { "id": "workspace-a", "label": "Workspace A", "path": "C:\\Projects\\Existing" },
            { "id": "workspace-b", "label": "Workspace B", "path": "C:\\Projects\\Missing" }
          ]
        }
        """;

        var candidate = ConfigurationMigration.BuildCandidate(
            existing,
            new SetupConfigurationInput(
                "current-codex.cmd",
                "https://current.example.ts.net",
                "C:\\Projects\\New Project"),
            new SetupConfigurationDefaults("default-data", "desktop-home"));

        var origins = candidate["allowedOrigins"]!.AsArray()
            .Select(node => node!.GetValue<string>())
            .ToArray();
        CollectionAssert.AreEqual(
            new[] {
                "https://previous.example.ts.net",
                "http://127.0.0.1:43111",
                "https://current.example.ts.net"
            },
            origins);

        var workspaces = candidate["workspaces"]!.AsArray();
        Assert.AreEqual(3, workspaces.Count);
        Assert.AreEqual("workspace-a", workspaces[0]!["id"]!.GetValue<string>());
        Assert.AreEqual("workspace-b", workspaces[1]!["id"]!.GetValue<string>());
        Assert.AreEqual("C:\\Projects\\New Project", workspaces[2]!["path"]!.GetValue<string>());
        Assert.AreEqual("existing-project-root", candidate["projectsRoot"]!.GetValue<string>());
    }

    [TestMethod]
    public void UpgradePreservesUnknownTopLevelAndNestedFields()
    {
        const string existing = """
        {
          "codexCliPath": "old-codex.cmd",
          "engine": "codex",
          "bindHost": "127.0.0.1",
          "port": 43110,
          "dataDir": "private-data",
          "projectsRoot": "existing-project-root",
          "futureTopLevel": { "enabled": true, "largeValue": 9223372036854775807 },
          "accounts": [
            {
              "id": "history",
              "label": "History",
              "codexHome": "home-history",
              "historyOnly": true,
              "futureAccountSetting": { "mode": "preserve" }
            }
          ],
          "workspaces": [
            {
              "id": "workspace-a",
              "label": "Workspace A",
              "path": "C:\\Projects\\Existing",
              "futureWorkspaceSetting": ["one", "two"]
            }
          ]
        }
        """;

        var candidate = ConfigurationMigration.BuildCandidate(
            existing,
            new SetupConfigurationInput("current-codex.cmd", "https://device.example.ts.net", null),
            new SetupConfigurationDefaults("default-data", "desktop-home"));

        Assert.IsTrue(candidate["futureTopLevel"]!["enabled"]!.GetValue<bool>());
        Assert.AreEqual(
            "preserve",
            candidate["accounts"]![0]!["futureAccountSetting"]!["mode"]!.GetValue<string>());
        Assert.AreEqual(
            "two",
            candidate["workspaces"]![0]!["futureWorkspaceSetting"]![1]!.GetValue<string>());
        StringAssert.Contains(candidate.ToJsonString(), "9223372036854775807");
    }

    [TestMethod]
    public void UpgradeRejectsCorruptedJsonWithSafeError()
    {
        var error = Assert.ThrowsException<ConfigurationMigrationException>(() =>
            ConfigurationMigration.BuildCandidate(
                "{ broken",
                new SetupConfigurationInput("current-codex.cmd", "https://device.example.ts.net", null),
                new SetupConfigurationDefaults("default-data", "desktop-home")));

        Assert.AreEqual("configuration_json_invalid", error.Message);
    }

    [DataTestMethod]
    [DataRow("0")]
    [DataRow("2")]
    [DataRow("\"1\"")]
    public void UpgradeRejectsUnsupportedSchemaWithoutRewritingIt(string schemaVersion)
    {
        var existing = $$"""
        {
          "schemaVersion": {{schemaVersion}},
          "codexCliPath": "old-codex.cmd",
          "engine": "codex",
          "bindHost": "127.0.0.1",
          "port": 43110,
          "dataDir": "private-data",
          "accounts": [{ "id": "managed", "label": "Managed", "codexHome": "home" }],
          "workspaces": [{ "id": "workspace", "label": "Workspace", "path": "project" }]
        }
        """;

        var error = Assert.ThrowsException<ConfigurationMigrationException>(() =>
            ConfigurationMigration.BuildCandidate(
                existing,
                new SetupConfigurationInput("current-codex.cmd", "https://device.example.ts.net", null),
                new SetupConfigurationDefaults("default-data", "desktop-home")));

        Assert.AreEqual("configuration_schema_unsupported", error.Message);
    }

    [DataTestMethod]
    [DataRow("accounts")]
    [DataRow("workspaces")]
    public void UpgradeRejectsDuplicateConfiguredIds(string duplicatedList)
    {
        var accounts = duplicatedList == "accounts"
            ? "[{ \"id\": \"duplicate\", \"label\": \"One\", \"codexHome\": \"home-one\" }, { \"id\": \"duplicate\", \"label\": \"Two\", \"codexHome\": \"home-two\" }]"
            : "[{ \"id\": \"account\", \"label\": \"Account\", \"codexHome\": \"home\" }]";
        var workspaces = duplicatedList == "workspaces"
            ? "[{ \"id\": \"duplicate\", \"label\": \"One\", \"path\": \"project-one\" }, { \"id\": \"duplicate\", \"label\": \"Two\", \"path\": \"project-two\" }]"
            : "[{ \"id\": \"workspace\", \"label\": \"Workspace\", \"path\": \"project\" }]";
        var existing = $$"""
        {
          "codexCliPath": "old-codex.cmd",
          "engine": "codex",
          "bindHost": "127.0.0.1",
          "port": 43110,
          "dataDir": "private-data",
          "accounts": {{accounts}},
          "workspaces": {{workspaces}}
        }
        """;

        var error = Assert.ThrowsException<ConfigurationMigrationException>(() =>
            ConfigurationMigration.BuildCandidate(
                existing,
                new SetupConfigurationInput("current-codex.cmd", "https://device.example.ts.net", null),
                new SetupConfigurationDefaults("default-data", "desktop-home")));

        Assert.AreEqual("configuration_duplicate_id", error.Message);
    }

    [TestMethod]
    public void FirstRunCreatesVersionedBootstrapConfiguration()
    {
        var candidate = ConfigurationMigration.BuildCandidate(
            null,
            new SetupConfigurationInput(
                "current-codex.cmd",
                "https://device.example.ts.net",
                "C:\\Projects\\First Project"),
            new SetupConfigurationDefaults("private-data", "desktop-home"));

        Assert.AreEqual(1, candidate["schemaVersion"]!.GetValue<int>());
        Assert.AreEqual("current-codex.cmd", candidate["codexCliPath"]!.GetValue<string>());
        Assert.AreEqual("codex", candidate["engine"]!.GetValue<string>());
        Assert.AreEqual("127.0.0.1", candidate["bindHost"]!.GetValue<string>());
        Assert.AreEqual(43110, candidate["port"]!.GetValue<int>());
        Assert.AreEqual("private-data", candidate["dataDir"]!.GetValue<string>());
        Assert.AreEqual("C:\\Projects", candidate["projectsRoot"]!.GetValue<string>());
        Assert.AreEqual("desktop-codex", candidate["accounts"]![0]!["id"]!.GetValue<string>());
        Assert.AreEqual("desktop-home", candidate["accounts"]![0]!["codexHome"]!.GetValue<string>());
        Assert.AreEqual(1, candidate["workspaces"]!.AsArray().Count);
        Assert.AreEqual(":danger-full-access", candidate["permissionCeiling"]!["permissionProfile"]!.GetValue<string>());
        Assert.AreEqual("never", candidate["permissionCeiling"]!["approvalPolicy"]!.GetValue<string>());
    }

    [TestMethod]
    public void UpgradeAddsFullPermissionCeilingButPreservesAnExplicitLocalReduction()
    {
        const string legacy = """
        {
          "codexCliPath": "old-codex.cmd", "engine": "codex", "bindHost": "127.0.0.1",
          "port": 43110, "dataDir": "private-data",
          "accounts": [{ "id": "managed", "label": "Managed", "codexHome": "home" }],
          "workspaces": [{ "id": "workspace", "label": "Workspace", "path": "project" }]
        }
        """;
        var upgraded = ConfigurationMigration.BuildCandidate(
            legacy,
            new SetupConfigurationInput("current-codex.cmd", "https://device.example.ts.net", null),
            new SetupConfigurationDefaults("default-data", "desktop-home"));
        Assert.AreEqual(":danger-full-access", upgraded["permissionCeiling"]!["permissionProfile"]!.GetValue<string>());
        Assert.AreEqual("never", upgraded["permissionCeiling"]!["approvalPolicy"]!.GetValue<string>());

        var reduced = JsonNode.Parse(legacy)!.AsObject();
        reduced["permissionCeiling"] = new JsonObject
        {
            ["permissionProfile"] = ":workspace",
            ["approvalPolicy"] = "on-request"
        };
        var preserved = ConfigurationMigration.BuildCandidate(
            reduced.ToJsonString(),
            new SetupConfigurationInput("current-codex.cmd", "https://device.example.ts.net", null),
            new SetupConfigurationDefaults("default-data", "desktop-home"));
        Assert.AreEqual(":workspace", preserved["permissionCeiling"]!["permissionProfile"]!.GetValue<string>());
        Assert.AreEqual("on-request", preserved["permissionCeiling"]!["approvalPolicy"]!.GetValue<string>());
    }
}
