import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../runtime-api.js";

const verificationMocks = vi.hoisted(() => ({
  bootstrapMatrixVerification: vi.fn(),
}));

vi.mock("./matrix/actions/verification.js", () => ({
  bootstrapMatrixVerification: verificationMocks.bootstrapMatrixVerification,
}));

import { matrixPlugin } from "./channel.js";
import { installMatrixTestRuntime } from "./test-runtime.js";
import type { CoreConfig } from "./types.js";

describe("matrix setup post-write bootstrap", () => {
  const log = vi.fn();
  const error = vi.fn();
  const exit = vi.fn((code: number): never => {
    throw new Error(`exit ${code}`);
  });
  const runtime: RuntimeEnv = {
    log,
    error,
    exit,
  };

  beforeEach(() => {
    verificationMocks.bootstrapMatrixVerification.mockReset();
    log.mockClear();
    error.mockClear();
    exit.mockClear();
    installMatrixTestRuntime();
  });

  it("bootstraps verification for newly added encrypted accounts", async () => {
    const previousCfg = {
      channels: {
        matrix: {
          encryption: true,
        },
      },
    } as CoreConfig;
    const input = {
      homeserver: "https://matrix.example.org",
      userId: "@flurry:example.org",
      password: "secret", // pragma: allowlist secret
    };
    const nextCfg = matrixPlugin.setup!.applyAccountConfig({
      cfg: previousCfg,
      accountId: "default",
      input,
    }) as CoreConfig;
    verificationMocks.bootstrapMatrixVerification.mockResolvedValue({
      success: true,
      verification: {
        backupVersion: "7",
      },
      crossSigning: {},
      pendingVerifications: 0,
      cryptoBootstrap: null,
    });

    await matrixPlugin.setup!.afterAccountConfigWritten?.({
      previousCfg,
      cfg: nextCfg,
      accountId: "default",
      input,
      runtime,
    });

    expect(verificationMocks.bootstrapMatrixVerification).toHaveBeenCalledWith({
      accountId: "default",
    });
    expect(log).toHaveBeenCalledWith('Matrix verification bootstrap: complete for "default".');
    expect(log).toHaveBeenCalledWith('Matrix backup version for "default": 7');
    expect(error).not.toHaveBeenCalled();
  });

  it("does not bootstrap verification for already configured accounts", async () => {
    const previousCfg = {
      channels: {
        matrix: {
          accounts: {
            flurry: {
              encryption: true,
              homeserver: "https://matrix.example.org",
              userId: "@flurry:example.org",
              accessToken: "token",
            },
          },
        },
      },
    } as CoreConfig;
    const input = {
      homeserver: "https://matrix.example.org",
      userId: "@flurry:example.org",
      accessToken: "new-token",
    };
    const nextCfg = matrixPlugin.setup!.applyAccountConfig({
      cfg: previousCfg,
      accountId: "flurry",
      input,
    }) as CoreConfig;

    await matrixPlugin.setup!.afterAccountConfigWritten?.({
      previousCfg,
      cfg: nextCfg,
      accountId: "flurry",
      input,
      runtime,
    });

    expect(verificationMocks.bootstrapMatrixVerification).not.toHaveBeenCalled();
    expect(log).not.toHaveBeenCalled();
    expect(error).not.toHaveBeenCalled();
  });

  it("logs a warning when verification bootstrap fails", async () => {
    const previousCfg = {
      channels: {
        matrix: {
          encryption: true,
        },
      },
    } as CoreConfig;
    const input = {
      homeserver: "https://matrix.example.org",
      userId: "@flurry:example.org",
      password: "secret", // pragma: allowlist secret
    };
    const nextCfg = matrixPlugin.setup!.applyAccountConfig({
      cfg: previousCfg,
      accountId: "default",
      input,
    }) as CoreConfig;
    verificationMocks.bootstrapMatrixVerification.mockResolvedValue({
      success: false,
      error: "no room-key backup exists on the homeserver",
      verification: {
        backupVersion: null,
      },
      crossSigning: {},
      pendingVerifications: 0,
      cryptoBootstrap: null,
    });

    await matrixPlugin.setup!.afterAccountConfigWritten?.({
      previousCfg,
      cfg: nextCfg,
      accountId: "default",
      input,
      runtime,
    });

    expect(error).toHaveBeenCalledWith(
      'Matrix verification bootstrap warning for "default": no room-key backup exists on the homeserver',
    );
  });

  it("bootstraps a newly added env-backed default account when encryption is already enabled", async () => {
    const previousEnv = {
      MATRIX_HOMESERVER: process.env.MATRIX_HOMESERVER,
      MATRIX_ACCESS_TOKEN: process.env.MATRIX_ACCESS_TOKEN,
    };
    process.env.MATRIX_HOMESERVER = "https://matrix.example.org";
    process.env.MATRIX_ACCESS_TOKEN = "env-token";
    try {
      const previousCfg = {
        channels: {
          matrix: {
            encryption: true,
          },
        },
      } as CoreConfig;
      const input = {
        useEnv: true,
      };
      const nextCfg = matrixPlugin.setup!.applyAccountConfig({
        cfg: previousCfg,
        accountId: "default",
        input,
      }) as CoreConfig;
      verificationMocks.bootstrapMatrixVerification.mockResolvedValue({
        success: true,
        verification: {
          backupVersion: "9",
        },
        crossSigning: {},
        pendingVerifications: 0,
        cryptoBootstrap: null,
      });

      await matrixPlugin.setup!.afterAccountConfigWritten?.({
        previousCfg,
        cfg: nextCfg,
        accountId: "default",
        input,
        runtime,
      });

      expect(verificationMocks.bootstrapMatrixVerification).toHaveBeenCalledWith({
        accountId: "default",
      });
      expect(log).toHaveBeenCalledWith('Matrix verification bootstrap: complete for "default".');
    } finally {
      for (const [key, value] of Object.entries(previousEnv)) {
        if (value === undefined) {
          delete process.env[key];
        } else {
          process.env[key] = value;
        }
      }
    }
  });

  it("rejects default useEnv setup when no Matrix auth env vars are available", () => {
    const previousEnv = {
      MATRIX_HOMESERVER: process.env.MATRIX_HOMESERVER,
      MATRIX_USER_ID: process.env.MATRIX_USER_ID,
      MATRIX_ACCESS_TOKEN: process.env.MATRIX_ACCESS_TOKEN,
      MATRIX_PASSWORD: process.env.MATRIX_PASSWORD,
      MATRIX_DEFAULT_HOMESERVER: process.env.MATRIX_DEFAULT_HOMESERVER,
      MATRIX_DEFAULT_USER_ID: process.env.MATRIX_DEFAULT_USER_ID,
      MATRIX_DEFAULT_ACCESS_TOKEN: process.env.MATRIX_DEFAULT_ACCESS_TOKEN,
      MATRIX_DEFAULT_PASSWORD: process.env.MATRIX_DEFAULT_PASSWORD,
    };
    for (const key of Object.keys(previousEnv)) {
      delete process.env[key];
    }
    try {
      expect(
        matrixPlugin.setup!.validateInput?.({
          cfg: {} as CoreConfig,
          accountId: "default",
          input: { useEnv: true },
        }),
      ).toContain("Set Matrix env vars for the default account");
    } finally {
      for (const [key, value] of Object.entries(previousEnv)) {
        if (value === undefined) {
          delete process.env[key];
        } else {
          process.env[key] = value;
        }
      }
    }
  });

  it("clears allowPrivateNetwork when deleting the default Matrix account config", () => {
    const updated = matrixPlugin.config.deleteAccount?.({
      cfg: {
        channels: {
          matrix: {
            homeserver: "http://localhost.localdomain:8008",
            allowPrivateNetwork: true,
            accounts: {
              ops: {
                enabled: true,
              },
            },
          },
        },
      } as CoreConfig,
      accountId: "default",
    }) as CoreConfig;

    expect(updated.channels?.matrix).toEqual({
      accounts: {
        ops: {
          enabled: true,
        },
      },
    });
  });
});
