import { describe, expect, it, vi } from "vitest";
import { matrixOnboardingAdapter } from "./onboarding.js";
import {
  installMatrixOnboardingEnvRestoreHooks,
  createMatrixWizardPrompter,
  runMatrixAddAccountAllowlistConfigure,
  runMatrixInteractiveConfigure,
} from "./onboarding.test-harness.js";
import { installMatrixTestRuntime } from "./test-runtime.js";
import type { CoreConfig } from "./types.js";

vi.mock("./matrix/deps.js", () => ({
  ensureMatrixSdkInstalled: vi.fn(async () => {}),
  isMatrixSdkAvailable: vi.fn(() => true),
}));

describe("matrix onboarding", () => {
  installMatrixOnboardingEnvRestoreHooks();

  it("offers env shortcut for non-default account when scoped env vars are present", async () => {
    installMatrixTestRuntime();

    process.env.MATRIX_HOMESERVER = "https://matrix.env.example.org";
    process.env.MATRIX_USER_ID = "@env:example.org";
    process.env.MATRIX_PASSWORD = "env-password"; // pragma: allowlist secret
    process.env.MATRIX_ACCESS_TOKEN = "";
    process.env.MATRIX_OPS_HOMESERVER = "https://matrix.ops.env.example.org";
    process.env.MATRIX_OPS_ACCESS_TOKEN = "ops-env-token";

    const confirmMessages: string[] = [];
    const prompter = createMatrixWizardPrompter({
      select: {
        "Matrix already configured. What do you want to do?": "add-account",
        "Matrix auth method": "token",
      },
      text: {
        "Matrix account name": "ops",
      },
      onConfirm: (message) => {
        confirmMessages.push(message);
        return message.startsWith("Matrix env vars detected");
      },
    });

    const result = await runMatrixInteractiveConfigure({
      cfg: {
        channels: {
          matrix: {
            accounts: {
              default: {
                homeserver: "https://matrix.main.example.org",
                accessToken: "main-token",
              },
            },
          },
        },
      } as CoreConfig,
      prompter,
      shouldPromptAccountIds: true,
      configured: true,
    });

    expect(result).not.toBe("skip");
    if (result !== "skip") {
      const opsAccount = result.cfg.channels?.["matrix"]?.accounts?.ops as
        | {
            enabled?: boolean;
            homeserver?: string;
            accessToken?: string;
          }
        | undefined;
      expect(result.accountId).toBe("ops");
      expect(opsAccount).toMatchObject({
        enabled: true,
      });
      expect(opsAccount?.homeserver).toBeUndefined();
      expect(opsAccount?.accessToken).toBeUndefined();
    }
    expect(
      confirmMessages.some((message) =>
        message.startsWith(
          "Matrix env vars detected (MATRIX_OPS_HOMESERVER (+ auth vars)). Use env values?",
        ),
      ),
    ).toBe(true);
  });

  it("routes env-shortcut add-account flow through Matrix invite auto-join setup", async () => {
    installMatrixTestRuntime();

    process.env.MATRIX_HOMESERVER = "https://matrix.env.example.org";
    process.env.MATRIX_USER_ID = "@env:example.org";
    process.env.MATRIX_PASSWORD = "env-password"; // pragma: allowlist secret
    process.env.MATRIX_ACCESS_TOKEN = "";
    process.env.MATRIX_OPS_HOMESERVER = "https://matrix.ops.env.example.org";
    process.env.MATRIX_OPS_ACCESS_TOKEN = "ops-env-token";

    const notes: string[] = [];
    const prompter = createMatrixWizardPrompter({
      notes,
      select: {
        "Matrix already configured. What do you want to do?": "add-account",
        "Matrix rooms access": "allowlist",
        "Matrix invite auto-join": "allowlist",
      },
      text: {
        "Matrix account name": "ops",
        "Matrix rooms allowlist (comma-separated)": "!ops-room:example.org",
        "Matrix invite auto-join allowlist (comma-separated)": "#ops-invites:example.org",
      },
      confirm: {
        "Configure Matrix rooms access?": true,
        "Configure Matrix invite auto-join?": true,
      },
      onConfirm: (message) => message.startsWith("Matrix env vars detected"),
    });

    const result = await runMatrixInteractiveConfigure({
      cfg: {
        channels: {
          matrix: {
            accounts: {
              default: {
                homeserver: "https://matrix.main.example.org",
                accessToken: "main-token",
              },
            },
          },
        },
      } as CoreConfig,
      prompter,
      shouldPromptAccountIds: true,
      configured: true,
    });

    expect(result).not.toBe("skip");
    if (result === "skip") {
      return;
    }

    expect(result.accountId).toBe("ops");
    expect(result.cfg.channels?.matrix?.accounts?.ops).toMatchObject({
      enabled: true,
      groupPolicy: "allowlist",
      groups: {
        "!ops-room:example.org": { enabled: true },
      },
      autoJoin: "allowlist",
      autoJoinAllowlist: ["#ops-invites:example.org"],
    });
    expect(notes.join("\n")).toContain("WARNING: Matrix invite auto-join defaults to off.");
  });

  it("promotes legacy top-level Matrix config before adding a named account", async () => {
    installMatrixTestRuntime();

    const prompter = createMatrixWizardPrompter({
      select: {
        "Matrix already configured. What do you want to do?": "add-account",
        "Matrix auth method": "token",
      },
      text: {
        "Matrix account name": "ops",
        "Matrix homeserver URL": "https://matrix.ops.example.org",
        "Matrix access token": "ops-token",
        "Matrix device name (optional)": "",
      },
      onConfirm: async () => false,
    });

    const result = await runMatrixInteractiveConfigure({
      cfg: {
        channels: {
          matrix: {
            homeserver: "https://matrix.main.example.org",
            userId: "@main:example.org",
            accessToken: "main-token",
            avatarUrl: "mxc://matrix.main.example.org/main-avatar",
          },
        },
      } as CoreConfig,
      prompter,
      shouldPromptAccountIds: true,
      configured: true,
    });

    expect(result).not.toBe("skip");
    if (result === "skip") {
      return;
    }

    expect(result.cfg.channels?.matrix?.homeserver).toBeUndefined();
    expect(result.cfg.channels?.matrix?.accessToken).toBeUndefined();
    expect(result.cfg.channels?.matrix?.avatarUrl).toBeUndefined();
    expect(result.cfg.channels?.matrix?.accounts?.default).toMatchObject({
      homeserver: "https://matrix.main.example.org",
      userId: "@main:example.org",
      accessToken: "main-token",
      avatarUrl: "mxc://matrix.main.example.org/main-avatar",
    });
    expect(result.cfg.channels?.matrix?.accounts?.ops).toMatchObject({
      name: "ops",
      homeserver: "https://matrix.ops.example.org",
      accessToken: "ops-token",
    });
  });

  it("reuses an existing raw default-like key during onboarding promotion when defaultAccount is unset", async () => {
    installMatrixTestRuntime();

    const prompter = createMatrixWizardPrompter({
      select: {
        "Matrix already configured. What do you want to do?": "add-account",
        "Matrix auth method": "token",
      },
      text: {
        "Matrix account name": "ops",
        "Matrix homeserver URL": "https://matrix.ops.example.org",
        "Matrix access token": "ops-token",
        "Matrix device name (optional)": "",
      },
      onConfirm: async () => false,
    });

    const result = await runMatrixInteractiveConfigure({
      cfg: {
        channels: {
          matrix: {
            homeserver: "https://matrix.main.example.org",
            userId: "@main:example.org",
            accessToken: "main-token",
            avatarUrl: "mxc://matrix.main.example.org/main-avatar",
            accounts: {
              Default: {
                enabled: true,
                deviceName: "Legacy raw key",
              },
              support: {
                homeserver: "https://matrix.support.example.org",
                accessToken: "support-token",
              },
            },
          },
        },
      } as CoreConfig,
      prompter,
      shouldPromptAccountIds: true,
      configured: true,
    });

    expect(result).not.toBe("skip");
    if (result === "skip") {
      return;
    }

    expect(result.cfg.channels?.matrix?.accounts?.Default).toMatchObject({
      enabled: true,
      deviceName: "Legacy raw key",
      homeserver: "https://matrix.main.example.org",
      userId: "@main:example.org",
      accessToken: "main-token",
      avatarUrl: "mxc://matrix.main.example.org/main-avatar",
    });
    expect(result.cfg.channels?.matrix?.accounts?.default).toBeUndefined();
    expect(result.cfg.channels?.matrix?.accounts?.support).toMatchObject({
      homeserver: "https://matrix.support.example.org",
      accessToken: "support-token",
    });
    expect(result.cfg.channels?.matrix?.accounts?.ops).toMatchObject({
      name: "ops",
      homeserver: "https://matrix.ops.example.org",
      accessToken: "ops-token",
    });
  });

  it("includes device env var names in auth help text", async () => {
    installMatrixTestRuntime();

    const notes: string[] = [];
    const prompter = createMatrixWizardPrompter({
      notes,
      onText: async () => {
        throw new Error("stop-after-help");
      },
      onConfirm: async () => false,
      onSelect: async () => "token",
    });

    await expect(
      runMatrixInteractiveConfigure({
        cfg: { channels: {} } as CoreConfig,
        prompter,
      }),
    ).rejects.toThrow("stop-after-help");

    const noteText = notes.join("\n");
    expect(noteText).toContain("MATRIX_DEVICE_ID");
    expect(noteText).toContain("MATRIX_DEVICE_NAME");
    expect(noteText).toContain("MATRIX_<ACCOUNT_ID>_DEVICE_ID");
    expect(noteText).toContain("MATRIX_<ACCOUNT_ID>_DEVICE_NAME");
  });

  it("prompts for private-network access when onboarding an internal http homeserver", async () => {
    installMatrixTestRuntime();

    const prompter = createMatrixWizardPrompter({
      select: {
        "Matrix auth method": "token",
      },
      text: {
        "Matrix homeserver URL": "http://localhost.localdomain:8008",
        "Matrix access token": "ops-token",
        "Matrix device name (optional)": "",
      },
      confirm: {
        "Allow private/internal Matrix homeserver traffic for this account?": true,
        "Enable end-to-end encryption (E2EE)?": false,
      },
      onConfirm: async () => false,
    });

    const result = await runMatrixInteractiveConfigure({
      cfg: {} as CoreConfig,
      prompter,
    });

    expect(result).not.toBe("skip");
    if (result === "skip") {
      return;
    }

    expect(result.cfg.channels?.matrix).toMatchObject({
      homeserver: "http://localhost.localdomain:8008",
      network: {
        dangerouslyAllowPrivateNetwork: true,
      },
      accessToken: "ops-token",
    });
  });

  it("preserves SecretRef access tokens when keeping existing credentials", async () => {
    installMatrixTestRuntime();

    process.env.MATRIX_ACCESS_TOKEN = "env-token";

    const prompter = createMatrixWizardPrompter({
      select: {
        "Matrix already configured. What do you want to do?": "update",
      },
      text: {
        "Matrix homeserver URL": "https://matrix.example.org",
        "Matrix device name (optional)": "OpenClaw Gateway",
      },
      confirm: {
        "Matrix credentials already configured. Keep them?": true,
        "Enable end-to-end encryption (E2EE)?": false,
        "Configure Matrix rooms access?": false,
        "Configure Matrix invite auto-join?": false,
      },
    });

    const result = await runMatrixInteractiveConfigure({
      cfg: {
        channels: {
          matrix: {
            homeserver: "https://matrix.example.org",
            accessToken: { source: "env", provider: "default", id: "MATRIX_ACCESS_TOKEN" },
          },
        },
        secrets: {
          defaults: {
            env: "default",
          },
        },
      } as CoreConfig,
      prompter,
      configured: true,
    });

    expect(result).not.toBe("skip");
    if (result === "skip") {
      return;
    }

    expect(result.cfg.channels?.matrix?.accessToken).toEqual({
      source: "env",
      provider: "default",
      id: "MATRIX_ACCESS_TOKEN",
    });
  });

  it("resolves status using the overridden Matrix account", async () => {
    const status = await matrixOnboardingAdapter.getStatus({
      cfg: {
        channels: {
          matrix: {
            defaultAccount: "default",
            accounts: {
              default: {
                homeserver: "https://matrix.default.example.org",
              },
              ops: {
                homeserver: "https://matrix.ops.example.org",
                accessToken: "ops-token",
              },
            },
          },
        },
      } as CoreConfig,
      options: undefined,
      accountOverrides: {
        matrix: "ops",
      },
    });

    expect(status.configured).toBe(true);
    expect(status.selectionHint).toBe("configured");
    expect(status.statusLines).toEqual(["Matrix: configured"]);
  });

  it("writes allowlists and room access to the selected Matrix account", async () => {
    installMatrixTestRuntime();
    const notes: string[] = [];

    const result = await runMatrixAddAccountAllowlistConfigure({
      cfg: {
        channels: {
          matrix: {
            accounts: {
              default: {
                homeserver: "https://matrix.main.example.org",
                accessToken: "main-token",
              },
            },
          },
        },
      } as CoreConfig,
      allowFromInput: "@alice:example.org",
      roomsAllowlistInput: "!ops-room:example.org",
      autoJoinAllowlistInput: "#ops-invites:example.org",
      deviceName: "Ops Gateway",
      notes,
    });

    expect(result).not.toBe("skip");
    if (result === "skip") {
      return;
    }

    expect(result.accountId).toBe("ops");
    expect(result.cfg.channels?.["matrix"]?.accounts?.ops).toMatchObject({
      homeserver: "https://matrix.ops.example.org",
      accessToken: "ops-token",
      deviceName: "Ops Gateway",
      dm: {
        policy: "allowlist",
        allowFrom: ["@alice:example.org"],
      },
      groupPolicy: "allowlist",
      autoJoin: "allowlist",
      autoJoinAllowlist: ["#ops-invites:example.org"],
      groups: {
        "!ops-room:example.org": { enabled: true },
      },
    });
    expect(result.cfg.channels?.["matrix"]?.dm).toBeUndefined();
    expect(result.cfg.channels?.["matrix"]?.groups).toBeUndefined();
    expect(notes.join("\n")).toContain("WARNING: Matrix invite auto-join defaults to off.");
  });

  it("clears Matrix invite auto-join allowlists when switching auto-join off", async () => {
    installMatrixTestRuntime();
    const notes: string[] = [];

    const prompter = createMatrixWizardPrompter({
      notes,
      select: {
        "Matrix already configured. What do you want to do?": "update",
        "Matrix invite auto-join": "off",
      },
      text: {
        "Matrix homeserver URL": "https://matrix.example.org",
        "Matrix device name (optional)": "OpenClaw Gateway",
      },
      confirm: {
        "Matrix credentials already configured. Keep them?": true,
        "Enable end-to-end encryption (E2EE)?": false,
        "Configure Matrix rooms access?": false,
        "Configure Matrix invite auto-join?": true,
        "Update Matrix invite auto-join?": true,
      },
    });

    const result = await runMatrixInteractiveConfigure({
      cfg: {
        channels: {
          matrix: {
            homeserver: "https://matrix.example.org",
            accessToken: "matrix-token",
            autoJoin: "allowlist",
            autoJoinAllowlist: ["#ops:example.org"],
          },
        },
      } as CoreConfig,
      prompter,
      configured: true,
    });

    expect(result).not.toBe("skip");
    if (result === "skip") {
      return;
    }

    expect(result.cfg.channels?.matrix?.autoJoin).toBe("off");
    expect(result.cfg.channels?.matrix?.autoJoinAllowlist).toBeUndefined();
    expect(notes.join("\n")).toContain("Matrix invite auto-join remains off.");
    expect(notes.join("\n")).toContain(
      "Agents will not join invited rooms or fresh DM-style invites until you change autoJoin.",
    );
  });

  it("re-prompts Matrix invite auto-join allowlists until entries are stable invite targets", async () => {
    installMatrixTestRuntime();
    const notes: string[] = [];
    let inviteAllowlistPrompts = 0;

    const prompter = createMatrixWizardPrompter({
      notes,
      select: {
        "Matrix already configured. What do you want to do?": "update",
        "Matrix invite auto-join": "allowlist",
      },
      text: {
        "Matrix homeserver URL": "https://matrix.example.org",
        "Matrix device name (optional)": "OpenClaw Gateway",
      },
      confirm: {
        "Matrix credentials already configured. Keep them?": true,
        "Enable end-to-end encryption (E2EE)?": false,
        "Configure Matrix rooms access?": false,
        "Configure Matrix invite auto-join?": true,
        "Update Matrix invite auto-join?": true,
      },
      onText: async (message) => {
        if (message === "Matrix invite auto-join allowlist (comma-separated)") {
          inviteAllowlistPrompts += 1;
          return inviteAllowlistPrompts === 1 ? "Project Room" : "#ops:example.org";
        }
        throw new Error(`unexpected text prompt: ${message}`);
      },
    });

    const result = await runMatrixInteractiveConfigure({
      cfg: {
        channels: {
          matrix: {
            homeserver: "https://matrix.example.org",
            accessToken: "matrix-token",
          },
        },
      } as CoreConfig,
      prompter,
      configured: true,
    });

    expect(result).not.toBe("skip");
    if (result === "skip") {
      return;
    }

    expect(inviteAllowlistPrompts).toBe(2);
    expect(result.cfg.channels?.matrix?.autoJoin).toBe("allowlist");
    expect(result.cfg.channels?.matrix?.autoJoinAllowlist).toEqual(["#ops:example.org"]);
    expect(notes.join("\n")).toContain(
      "Use only stable Matrix invite targets for auto-join: !roomId:server, #alias:server, or *.",
    );
    expect(notes.join("\n")).toContain("Invalid: Project Room");
  });

  it("reports account-scoped DM config keys for named accounts", () => {
    const resolveConfigKeys = matrixOnboardingAdapter.dmPolicy?.resolveConfigKeys;
    expect(resolveConfigKeys).toBeDefined();
    if (!resolveConfigKeys) {
      return;
    }

    expect(
      resolveConfigKeys(
        {
          channels: {
            matrix: {
              accounts: {
                default: {
                  homeserver: "https://matrix.main.example.org",
                },
                ops: {
                  homeserver: "https://matrix.ops.example.org",
                },
              },
            },
          },
        } as CoreConfig,
        "ops",
      ),
    ).toEqual({
      policyKey: "channels.matrix.accounts.ops.dm.policy",
      allowFromKey: "channels.matrix.accounts.ops.dm.allowFrom",
    });
  });

  it("reports configured when only the effective default Matrix account is configured", async () => {
    installMatrixTestRuntime();

    const status = await matrixOnboardingAdapter.getStatus({
      cfg: {
        channels: {
          matrix: {
            defaultAccount: "ops",
            accounts: {
              ops: {
                homeserver: "https://matrix.ops.example.org",
                accessToken: "ops-token",
              },
            },
          },
        },
      } as CoreConfig,
      accountOverrides: {},
    });

    expect(status.configured).toBe(true);
    expect(status.statusLines).toContain("Matrix: configured");
    expect(status.selectionHint).toBe("configured");
  });

  it("asks for defaultAccount when multiple named Matrix accounts exist", async () => {
    installMatrixTestRuntime();

    const status = await matrixOnboardingAdapter.getStatus({
      cfg: {
        channels: {
          matrix: {
            accounts: {
              assistant: {
                homeserver: "https://matrix.assistant.example.org",
                accessToken: "assistant-token",
              },
              ops: {
                homeserver: "https://matrix.ops.example.org",
                accessToken: "ops-token",
              },
            },
          },
        },
      } as CoreConfig,
      accountOverrides: {},
    });

    expect(status.configured).toBe(false);
    expect(status.statusLines).toEqual([
      'Matrix: set "channels.matrix.defaultAccount" to select a named account',
    ]);
    expect(status.selectionHint).toBe("set defaultAccount");
  });
});
