import assert from "node:assert/strict";
import { afterEach, beforeEach, describe, it } from "node:test";
import { createNotifier, detectNotificationBackend } from "../src/terminal.ts";
import type { NotifyConfig } from "../src/types.ts";

const ESC = "\x1b";
const ST = `${ESC}\\`;
const savedEnv = {
  KITTY_WINDOW_ID: process.env.KITTY_WINDOW_ID,
  TMUX: process.env.TMUX,
};

let originalWrite: typeof process.stdout.write;
let output = "";

beforeEach(() => {
  delete process.env.KITTY_WINDOW_ID;
  delete process.env.TMUX;
});

afterEach(() => {
  process.stdout.write = originalWrite;
  output = "";
  for (const [key, value] of Object.entries(savedEnv)) {
    if (value === undefined) delete process.env[key];
    else process.env[key] = value;
  }
});

function captureStdout(): void {
  originalWrite = process.stdout.write;
  process.stdout.write = ((chunk: string | Uint8Array) => {
    output += chunk.toString();
    return true;
  }) as typeof process.stdout.write;
}

function config(title = "Title", body = "Body"): NotifyConfig {
  return {
    enabled: true,
    title,
    body,
    durationMs: 5_000,
    minDurationMs: 3_000,
    notificationId: "pi-session:unsafe",
    visibility: "unfocused",
    action: "focus",
  };
}

function captureSpawn(): { calls: unknown[][]; spawn: typeof import("node:child_process").spawn } {
  const calls: unknown[][] = [];
  const spawn = ((...args: unknown[]) => {
    calls.push(args);
    return { on() {}, unref() {} };
  }) as unknown as typeof import("node:child_process").spawn;
  return { calls, spawn };
}

describe("terminal notifications", () => {
  it("writes encoded Kitty OSC 99 payloads with exact-window focus metadata", () => {
    process.env.KITTY_WINDOW_ID = "1";
    const notifier = createNotifier();
    captureStdout();
    notifier.sendNotification(config("π - pi-notify", "done\nnow"));

    const title = Buffer.from("π - pi-notify", "utf8").toString("base64");
    const body = Buffer.from("done\nnow", "utf8").toString("base64");
    assert.equal(
      output,
      `${ESC}]99;i=pi-session-unsafe:d=0:e=1:o=unfocused:a=focus;${title}${ST}` +
        `${ESC}]99;i=pi-session-unsafe:p=body:e=1;${body}${ST}`,
    );
  });

  it("wraps Kitty OSC 99 sequences for tmux passthrough", () => {
    process.env.TMUX = "/tmp/tmux";
    const notifier = createNotifier({ readTmuxClientInfo: () => "xterm-kitty xterm-kitty" });
    captureStdout();
    notifier.sendNotification(config("Pi", "done"));

    const title = Buffer.from("Pi", "utf8").toString("base64");
    const body = Buffer.from("done", "utf8").toString("base64");
    const titleSequence = `${ESC}]99;i=pi-session-unsafe:d=0:e=1:o=unfocused:a=focus;${title}${ST}`.replaceAll(ESC, ESC + ESC);
    const bodySequence = `${ESC}]99;i=pi-session-unsafe:p=body:e=1;${body}${ST}`.replaceAll(ESC, ESC + ESC);
    assert.equal(output, `${ESC}Ptmux;${titleSequence}${ST}${ESC}Ptmux;${bodySequence}${ST}`);
  });

  it("uses AppleScript safely on macOS when Kitty is not detected", () => {
    const { calls, spawn } = captureSpawn();
    const notifier = createNotifier({ spawn, platform: "darwin" });
    captureStdout();
    notifier.sendNotification(config('Pi "notify"', "done\\now"));

    assert.equal(output, "");
    assert.deepEqual(calls, [
      [
        "osascript",
        ["-e", 'display notification "done\\\\now" with title "Pi \\"notify\\""'],
        { shell: false, detached: true, stdio: "ignore" },
      ],
    ]);
  });

  it("does not let a stale Kitty variable override the active tmux client", () => {
    process.env.TMUX = "/tmp/tmux";
    process.env.KITTY_WINDOW_ID = "stale";
    assert.equal(
      detectNotificationBackend(() => "xterm-ghostty ghostty", "darwin"),
      "apple-script",
    );
  });

  it("uses a sanitized OSC 777 fallback outside macOS", () => {
    const notifier = createNotifier({ platform: "linux" });
    captureStdout();
    notifier.sendNotification(config("Pi;bad", "done\u0007now"));
    assert.equal(output, `${ESC}]777;notify;Pi bad;done now\x07`);
  });
});
