import test from "node:test";
import assert from "node:assert/strict";

import {
  createSudoPasswordAutofill,
  prepareSudoAutofillInput,
} from "./terminalSudoAutofill";
import { recordTerminalCommandExecution } from "./terminalCommandExecution";
import { createPromptLineBreakState } from "./promptLineBreak";

function createFakeTerm(lineText = "$ echo ok", cursorX = lineText.length) {
  return {
    buffer: {
      active: {
        cursorX,
        cursorY: 0,
        baseY: 0,
        getLine(line: number) {
          if (line !== 0) return undefined;
          return {
            isWrapped: false,
            translateToString() {
              return lineText;
            },
          };
        },
      },
    },
  };
}

function createWrappedFakeTerm(rows: string[], cursorY: number, cursorX: number, cols: number) {
  return {
    cols,
    buffer: {
      active: {
        cursorX,
        cursorY,
        baseY: 0,
        getLine(line: number) {
          const lineText = rows[line];
          if (lineText === undefined) return undefined;
          return {
            isWrapped: line > 0,
            translateToString() {
              return lineText;
            },
          };
        },
      },
    },
  };
}

test("sudo autofill input preparation arms on a submitted sudo command without altering input", () => {
  const writes: string[] = [];
  const autofill = createSudoPasswordAutofill({
    password: "secret",
    write: (data) => writes.push(data),
    onHint: () => true,
  });

  assert.equal(prepareSudoAutofillInput("\r", "sudo whoami", autofill), "\r");
  autofill.handleOutput("[sudo] password for alice: ");
  autofill.confirmFill();
  assert.deepEqual(writes, ["secret\n"]);
});

test("sudo autofill input preparation arms on a single-line pasted sudo command", () => {
  const writes: string[] = [];
  const autofill = createSudoPasswordAutofill({
    password: "secret",
    write: (data) => writes.push(data),
    onHint: () => true,
  });

  assert.equal(prepareSudoAutofillInput("sudo whoami\n", null, autofill), "sudo whoami\n");
  autofill.handleOutput("[sudo] password for alice: ");
  autofill.confirmFill();
  assert.deepEqual(writes, ["secret\n"]);
});

test("sudo autofill input preparation preserves bracketed pasted sudo commands", () => {
  const autofill = createSudoPasswordAutofill({
    password: "secret",
    write: () => {},
  });

  assert.equal(
    prepareSudoAutofillInput("\x1b[200~sudo whoami\n\x1b[201~", null, autofill),
    "\x1b[200~sudo whoami\n\x1b[201~",
  );
});

test("sudo autofill input preparation leaves ordinary commands unchanged", () => {
  const autofill = createSudoPasswordAutofill({
    password: "secret",
    write: () => {},
  });

  assert.equal(prepareSudoAutofillInput("\r", "echo ok", autofill), "\r");
  assert.equal(prepareSudoAutofillInput("x", "sudo whoami", autofill), "x");
  assert.equal(prepareSudoAutofillInput("sudo whoami\nsudo id\n", null, autofill), "sudo whoami\nsudo id\n");
});

test("command execution arms prompt line break even without command history callback", () => {
  const promptState = createPromptLineBreakState();
  const commandBufferRef = { current: "echo ok" };

  const recordedCommand = recordTerminalCommandExecution("echo ok", {
    host: {
      id: "host-1",
      label: "Host",
    },
    sessionId: "session-1",
    commandBufferRef,
    promptLineBreakStateRef: { current: promptState },
  });

  assert.equal(commandBufferRef.current, "");
  assert.equal(recordedCommand, "echo ok");
  assert.equal(promptState.pendingCommand, true);
});

test("command execution caches the current prompt instead of prompt-like command text", () => {
  const promptState = createPromptLineBreakState();
  const commandBufferRef = { current: "echo > out" };

  recordTerminalCommandExecution("echo > out", {
    host: {
      id: "host-1",
      label: "Host",
    },
    sessionId: "session-1",
    commandBufferRef,
    promptLineBreakStateRef: { current: promptState },
  }, createFakeTerm("$ echo > out") as never);

  assert.equal(promptState.lastPromptText, "$ ");
  assert.equal(promptState.pendingCommand, true);
});

test("command execution does not write interactive program input to shell history", () => {
  const cases = [
    { lineText: "sftp> get file", command: "get file" },
    { lineText: "cqlsh:cycling> select * from cyclist", command: "select * from cyclist" },
    { lineText: "hive (default)> select 1", command: "select 1" },
    { lineText: "trino:tpch> select 1", command: "select 1" },
    { lineText: "lftp user@example.com:~> ls", command: "ls" },
    { lineText: "irb(main):001> puts 1", command: "puts 1" },
    { lineText: "pry(main)> whereami", command: "whereami" },
    { lineText: "[1] pry(main)> whereami", command: "whereami" },
    { lineText: "SQL> select 1", command: "select 1" },
    { lineText: "test> db.stats()", command: "db.stats()" },
    { lineText: "test> db", command: "db" },
    { lineText: "test> const x = 1", command: "const x = 1" },
    { lineText: "test> await db.users.findOne()", command: "await db.users.findOne()" },
    { lineText: "rs0:PRIMARY> db.stats()", command: "db.stats()" },
    { lineText: "rs0 [direct: primary] test> db.stats()", command: "db.stats()" },
    { lineText: "rs0 [direct: primary] reporting> db.stats()", command: "db.stats()" },
    { lineText: "rs0 [direct: primary] reporting> const x = 1", command: "const x = 1" },
    { lineText: "rs0 [direct: primary] reporting> await db.users.findOne()", command: "await db.users.findOne()" },
    { lineText: "Atlas a [primary] reporting> db.stats()", command: "db.stats()" },
    { lineText: "Atlas a [primary] reporting> await db.users.findOne()", command: "await db.users.findOne()" },
    { lineText: "test> print(1)", command: "print(1)" },
    { lineText: "rs0 primary test> db.stats()", command: "db.stats()" },
    { lineText: "test> rs.status()", command: "rs.status()" },
    { lineText: "rs0 primary reporting> exit", command: "exit" },
    { lineText: "admin@localhost:27017> db.stats()", command: "db.stats()" },
  ];

  for (const { lineText, command } of cases) {
    const commandBufferRef = { current: command };
    const promptState = createPromptLineBreakState();
    const recorded: string[] = [];

    const recordedCommand = recordTerminalCommandExecution(command, {
      host: {
        id: "host-1",
        label: "Host",
      },
      sessionId: "session-1",
      commandBufferRef,
      promptLineBreakStateRef: { current: promptState },
      onCommandExecuted(nextCommand) {
        recorded.push(nextCommand);
      },
    }, createFakeTerm(lineText) as never);

    assert.deepEqual(recorded, [], lineText);
    assert.equal(recordedCommand, null, lineText);
    assert.equal(commandBufferRef.current, "", lineText);
    assert.equal(promptState.lastPromptText, "", lineText);
    assert.equal(promptState.pendingCommand, true, lineText);
  }
});

test("command execution does not record interactive input before echo appears", () => {
  const cases = [
    { lineText: "test> ", command: "rs.status()" },
    { lineText: "test> ", command: "db" },
    { lineText: "test> ", command: "const x = 1" },
    { lineText: "test> ", command: "await db.users.findOne()" },
    { lineText: "rs0 [direct: primary] reporting> ", command: "const x = 1" },
    { lineText: "rs0 [direct: primary] reporting> ", command: "await db.users.findOne()" },
    { lineText: "rs0 [direct: primary] test> ", command: "db.stats()" },
    { lineText: "rs0 [direct: primary] reporting> ", command: "db.stats()" },
    { lineText: "Atlas a [primary] reporting> ", command: "db.stats()" },
  ];

  for (const { lineText, command } of cases) {
    const commandBufferRef = { current: command };
    const recorded: string[] = [];

    recordTerminalCommandExecution(command, {
      host: {
        id: "host-1",
        label: "Host",
      },
      sessionId: "session-1",
      commandBufferRef,
      onCommandExecuted(nextCommand) {
        recorded.push(nextCommand);
      },
    }, createFakeTerm(lineText) as never);

    assert.deepEqual(recorded, [], lineText);
    assert.equal(commandBufferRef.current, "", lineText);
  }
});

test("command execution publishes submitted commands even when history recording is skipped", () => {
  const commandBufferRef = { current: "cd /srv/app" };
  const history: string[] = [];
  const submitted: string[] = [];

  const recordedCommand = recordTerminalCommandExecution("cd /srv/app", {
    host: {
      id: "host-1",
      label: "Host",
    },
    sessionId: "session-1",
    commandBufferRef,
    onCommandExecuted(nextCommand) {
      history.push(nextCommand);
    },
    onCommandSubmitted(nextCommand) {
      submitted.push(nextCommand);
    },
  }, createFakeTerm("sftp> cd /srv/app") as never);

  assert.deepEqual(history, []);
  assert.deepEqual(submitted, ["cd /srv/app"]);
  assert.equal(recordedCommand, null);
  assert.equal(commandBufferRef.current, "");
});

test("command execution does not record wrapped interactive program input", () => {
  const cases = [
    { rows: ["Atlas a [primary]", " reporting> db.stats()"], command: "db.stats()" },
    { rows: ["test> d", "b"], command: "db" },
  ];

  for (const { rows, command } of cases) {
    const commandBufferRef = { current: command };
    const recorded: string[] = [];

    recordTerminalCommandExecution(command, {
      host: {
        id: "host-1",
        label: "Host",
      },
      sessionId: "session-1",
      commandBufferRef,
      onCommandExecuted(nextCommand) {
        recorded.push(nextCommand);
      },
    }, createWrappedFakeTerm(rows, 1, rows[1].length, 20) as never);

    assert.deepEqual(recorded, [], rows[0]);
    assert.equal(commandBufferRef.current, "", rows[0]);
  }
});

test("command execution records non-Mongo-looking default-name greater-than prompts", () => {
  const prompts = ["test> ", "admin> ", "local> ", "config> "];
  const commands = ["deploy", "exit", "help", "show dbs"];

  for (const prompt of prompts) {
    for (const command of commands) {
      const commandBufferRef = { current: command };
      const recorded: string[] = [];

      recordTerminalCommandExecution(command, {
        host: {
          id: "host-1",
          label: "Host",
        },
        sessionId: "session-1",
        commandBufferRef,
        onCommandExecuted(nextCommand) {
          recorded.push(nextCommand);
        },
      }, createFakeTerm(`${prompt}${command}`) as never);

      assert.deepEqual(recorded, [command], `${prompt}${command}`);
      assert.equal(commandBufferRef.current, "", `${prompt}${command}`);
    }
  }
});

test("command execution records wrapped non-Mongo-looking default-name greater-than prompts", () => {
  const cases = [
    { rows: ["test> hel", "p"], command: "help" },
    { rows: ["test> show ", "dbs"], command: "show dbs" },
    { rows: ["admin> ex", "it"], command: "exit" },
    { rows: ["local> dep", "loy"], command: "deploy" },
  ];

  for (const { rows, command } of cases) {
    const commandBufferRef = { current: command };
    const recorded: string[] = [];

    recordTerminalCommandExecution(command, {
      host: {
        id: "host-1",
        label: "Host",
      },
      sessionId: "session-1",
      commandBufferRef,
      onCommandExecuted(nextCommand) {
        recorded.push(nextCommand);
      },
    }, createWrappedFakeTerm(rows, 1, rows[1].length, 20) as never);

    assert.deepEqual(recorded, [command], rows[0]);
    assert.equal(commandBufferRef.current, "", rows[0]);
  }
});

test("command execution records short commands when standard prompt echo lags by one character", () => {
  const cases = [
    { lineText: "$ l", command: "ls" },
    { lineText: "$ c", command: "cd" },
    { lineText: "prod-web> l", command: "ls" },
    { lineText: "prod> l", command: "ls" },
    { lineText: "prod.web> l", command: "ls" },
    { lineText: "user@host:~$ l", command: "ls" },
    { lineText: "[user@host ~]$ l", command: "ls" },
    { lineText: "➜  netcatty $ l", command: "ls" },
    { lineText: "➜  git l", command: "ls" },
    { lineText: "➜  git np", command: "npm" },
  ];

  for (const { lineText, command } of cases) {
    const commandBufferRef = { current: command };
    const recorded: string[] = [];

    recordTerminalCommandExecution(command, {
      host: {
        id: "host-1",
        label: "Host",
      },
      sessionId: "session-1",
      commandBufferRef,
      onCommandExecuted(nextCommand) {
        recorded.push(nextCommand);
      },
    }, createFakeTerm(lineText) as never);

    assert.deepEqual(recorded, [command], lineText);
    assert.equal(commandBufferRef.current, "", lineText);
  }
});

test("command execution records direct sends from themed bare directory prompts", () => {
  const cases = [
    { lineText: "➜  netcatty ", command: "ls", promptText: "➜  netcatty " },
    { lineText: "➜  git ", command: "npm", promptText: "➜  git " },
    { lineText: "➜  git ", command: "git status", promptText: "➜  git " },
    { lineText: "➜  make ", command: "sudo", promptText: "➜  make " },
    { lineText: "➜  make ", command: "make build", promptText: "➜  make " },
    { lineText: "➜  node ", command: "yarn", promptText: "➜  node " },
  ];

  for (const { lineText, command, promptText } of cases) {
    const commandBufferRef = { current: command };
    const promptState = createPromptLineBreakState();
    const recorded: string[] = [];

    recordTerminalCommandExecution(command, {
      host: {
        id: "host-1",
        label: "Host",
      },
      sessionId: "session-1",
      commandBufferRef,
      promptLineBreakStateRef: { current: promptState },
      onCommandExecuted(nextCommand) {
        recorded.push(nextCommand);
      },
    }, createFakeTerm(lineText) as never);

    assert.deepEqual(recorded, [command], lineText);
    assert.equal(commandBufferRef.current, "", lineText);
    assert.equal(promptState.lastPromptText, promptText, lineText);
    assert.equal(promptState.pendingCommand, true, lineText);
  }
});

test("command execution still records host-style greater-than prompts", () => {
  const prompts = [
    "prod-web> ",
    "prod> ",
    "prod.web> ",
    "server> ",
    "staging> ",
    "webdb> ",
    "prod.db> ",
  ];
  const commands = ["deploy", "exit", "show dbs", "use app", "it", "help", "print(1)", "db.stats()"];

  for (const prompt of prompts) {
    for (const command of commands) {
      const commandBufferRef = { current: command };
      const recorded: string[] = [];

      recordTerminalCommandExecution(command, {
        host: {
          id: "host-1",
          label: "Host",
        },
        sessionId: "session-1",
        commandBufferRef,
        onCommandExecuted(nextCommand) {
          recorded.push(nextCommand);
        },
      }, createFakeTerm(`${prompt}${command}`) as never);

      assert.deepEqual(recorded, [command], `${prompt}${command}`);
      assert.equal(commandBufferRef.current, "", `${prompt}${command}`);
    }
  }
});

test("command execution records direct sends from host-style greater-than prompts", () => {
  const cases = [
    { lineText: "server> ", command: "exit" },
    { lineText: "staging> ", command: "show dbs" },
    { lineText: "server> ", command: "db.stats()" },
    { lineText: "webdb> ", command: "deploy" },
    { lineText: "prod.db> ", command: "deploy" },
    { lineText: "test> ", command: "deploy" },
    { lineText: "test> ", command: "exit" },
    { lineText: "test> ", command: "help" },
    { lineText: "test> ", command: "show dbs" },
    { lineText: "admin> ", command: "deploy" },
  ];

  for (const { lineText, command } of cases) {
    const commandBufferRef = { current: command };
    const recorded: string[] = [];

    recordTerminalCommandExecution(command, {
      host: {
        id: "host-1",
        label: "Host",
      },
      sessionId: "session-1",
      commandBufferRef,
      onCommandExecuted(nextCommand) {
        recorded.push(nextCommand);
      },
    }, createFakeTerm(lineText) as never);

    assert.deepEqual(recorded, [command], lineText);
    assert.equal(commandBufferRef.current, "", lineText);
  }
});
