// @tier: fast
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import {
  chmodSync,
  existsSync,
  mkdtempSync,
  readFileSync,
  rmSync,
  mkdirSync,
  utimesSync,
  writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '..', '..');
const CLI = path.join(REPO_ROOT, 'bin', 'section-c-still-needed.js');

function makeSession(log) {
  const session = mkdtempSync(path.join(tmpdir(), 'section-c-gate-'));
  writeFileSync(path.join(session, 'tmux-runner.log'), log);
  return session;
}

function makeSessionWithLogs(logs) {
  const session = mkdtempSync(path.join(tmpdir(), 'section-c-gate-'));
  for (const [name, contents] of Object.entries(logs)) {
    writeFileSync(path.join(session, name), contents);
  }
  return session;
}

function runGate(session) {
  const result = spawnSync(process.execPath, [CLI, '--session', session], {
    cwd: REPO_ROOT,
    encoding: 'utf8',
  });
  const artifactPath = path.join(session, 'bundle', 'section-c-still-needed.json');
  return {
    ...result,
    artifactPath,
    artifact: JSON.parse(readFileSync(artifactPath, 'utf8')),
  };
}

test('section-c-gate.symptom-present writes still_needed true with evidence', () => {
  const session = makeSession([
    'iteration 4 completed',
    'watcher output',
    '◤ FEED TERMINATED ◢',
    'iteration 5 starting',
    '',
  ].join('\n'));
  try {
    const result = runGate(session);
    assert.equal(result.status, 0, result.stderr);
    assert.equal(result.artifact.still_needed, true);
    assert.match(result.artifact.evidence, /FEED TERMINATED/);
    assert.match(result.stdout, /STILL_NEEDED/);
    assert.match(result.stdout, new RegExp(result.artifactPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
  } finally {
    rmSync(session, { recursive: true, force: true });
  }
});

test('section-c-gate.symptom-absent writes still_needed false for clean log', () => {
  const session = makeSession('iteration 4 completed\niteration 5 starting\n');
  try {
    const result = runGate(session);
    assert.equal(result.status, 0, result.stderr);
    assert.equal(result.artifact.still_needed, false);
    assert.match(result.artifact.evidence, /iteration 5 starting/);
  } finally {
    rmSync(session, { recursive: true, force: true });
  }
});

test('section-c-gate.pipeline-log-banner writes still_needed true when only pipeline-runner.log shows the symptom', () => {
  const session = makeSessionWithLogs({
    'tmux-runner.log': 'iteration 4 completed\niteration 5 starting\n',
    'pipeline-runner.log': 'phase handoff\n◤ FEED TERMINATED ◢\n',
  });
  try {
    const result = runGate(session);
    assert.equal(result.status, 0, result.stderr);
    assert.equal(result.artifact.still_needed, true);
    assert.match(result.artifact.evidence, /FEED TERMINATED/);
  } finally {
    rmSync(session, { recursive: true, force: true });
  }
});

test('section-c-gate.long-log-banner retains still_needed when the banner scrolls outside the trailing sample', () => {
  const lines = ['older line 0', '◤ FEED TERMINATED ◢'];
  for (let i = 0; i < 1005; i += 1) {
    lines.push(`iteration ${i} completed`);
  }
  const session = makeSession(lines.join('\n'));
  try {
    const result = runGate(session);
    assert.equal(result.status, 0, result.stderr);
    assert.equal(result.artifact.still_needed, true);
    assert.match(result.artifact.evidence, /outside the trailing evidence sample/);
  } finally {
    rmSync(session, { recursive: true, force: true });
  }
});

test('section-c-gate.missing-explicit-session exits zero and defaults still_needed true', () => {
  const fakeHome = mkdtempSync(path.join(tmpdir(), 'section-c-home-'));
  const missingSession = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions', '2026-05-12-missing');
  const runtimeArtifactPath = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'bundle', 'section-c-still-needed.json');
  const result = spawnSync(process.execPath, [CLI, '--session', missingSession], {
    cwd: REPO_ROOT,
    encoding: 'utf8',
    env: {
      ...process.env,
      HOME: fakeHome,
    },
  });
  const artifact = JSON.parse(readFileSync(runtimeArtifactPath, 'utf8'));

  try {
    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, true);
    assert.match(artifact.evidence, /No recent session found/);
  } finally {
    rmSync(fakeHome, { recursive: true, force: true });
  }
});

test('section-c-gate.default-session-selection follows newest watcher log activity, not newest session directory', () => {
  const fakeHome = mkdtempSync(path.join(tmpdir(), 'section-c-home-'));
  const sessionsDir = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions');
  mkdirSync(sessionsDir, { recursive: true });

  const activeSession = path.join(sessionsDir, '2026-05-12-active');
  const idleSession = path.join(sessionsDir, '2026-05-12-idle');
  mkdirSync(activeSession);
  writeFileSync(path.join(activeSession, 'tmux-runner.log'), 'iteration 4 completed\n');
  mkdirSync(idleSession);
  writeFileSync(path.join(idleSession, 'tmux-runner.log'), 'iteration 4 completed\niteration 5 starting\n');

  const activeLog = path.join(activeSession, 'tmux-runner.log');
  const now = new Date();
  const newer = new Date(now.getTime() + 60_000);
  utimesSync(activeLog, newer, newer);

  const result = spawnSync(process.execPath, [CLI], {
    cwd: REPO_ROOT,
    encoding: 'utf8',
    env: {
      ...process.env,
      HOME: fakeHome,
    },
  });
  const artifactPath = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions', '2026-05-12-active', 'bundle', 'section-c-still-needed.json');
  const artifact = JSON.parse(readFileSync(artifactPath, 'utf8'));

  try {
    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, false);
    assert.match(artifact.evidence, /iteration 4 completed/);
  } finally {
    rmSync(fakeHome, { recursive: true, force: true });
  }
});

test('section-c-gate.default-session-selection prefers sessions with watcher logs over newer empty session dirs', () => {
  const fakeHome = mkdtempSync(path.join(tmpdir(), 'section-c-home-'));
  const sessionsDir = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions');
  mkdirSync(sessionsDir, { recursive: true });

  const activeSession = path.join(sessionsDir, '2026-05-12-active');
  const emptySession = path.join(sessionsDir, '2026-05-12-empty');
  mkdirSync(activeSession);
  writeFileSync(path.join(activeSession, 'tmux-runner.log'), 'iteration 4 completed\niteration 5 starting\n');
  mkdirSync(emptySession);

  const activeLog = path.join(activeSession, 'tmux-runner.log');
  const now = new Date();
  const older = new Date(now.getTime() - 60_000);
  const newer = new Date(now.getTime() + 60_000);
  utimesSync(activeLog, older, older);
  utimesSync(emptySession, newer, newer);

  const result = spawnSync(process.execPath, [CLI], {
    cwd: REPO_ROOT,
    encoding: 'utf8',
    env: {
      ...process.env,
      HOME: fakeHome,
    },
  });
  const artifactPath = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions', '2026-05-12-active', 'bundle', 'section-c-still-needed.json');
  const artifact = JSON.parse(readFileSync(artifactPath, 'utf8'));

  try {
    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, false);
    assert.match(artifact.evidence, /iteration 5 starting/);
  } finally {
    rmSync(fakeHome, { recursive: true, force: true });
  }
});

test('section-c-gate.default-session-selection honors PICKLE_DATA_ROOT override for session discovery', () => {
  const fakeDataRoot = mkdtempSync(path.join(tmpdir(), 'section-c-data-root-'));
  const sessionsDir = path.join(fakeDataRoot, 'sessions');
  mkdirSync(sessionsDir, { recursive: true });

  const session = path.join(sessionsDir, '2026-05-12-custom-root');
  mkdirSync(session);
  writeFileSync(path.join(session, 'pipeline-runner.log'), 'phase handoff\niteration 5 starting\n');

  const result = spawnSync(process.execPath, [CLI], {
    cwd: REPO_ROOT,
    encoding: 'utf8',
    env: {
      ...process.env,
      PICKLE_DATA_ROOT: fakeDataRoot,
    },
  });
  const artifactPath = path.join(session, 'bundle', 'section-c-still-needed.json');
  const artifact = JSON.parse(readFileSync(artifactPath, 'utf8'));

  try {
    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, false);
    assert.match(artifact.evidence, /iteration 5 starting/);
  } finally {
    rmSync(fakeDataRoot, { recursive: true, force: true });
  }
});

test('section-c-gate.default-session-selection skips unreadable watcher logs and falls back to the next readable session', () => {
  const fakeHome = mkdtempSync(path.join(tmpdir(), 'section-c-home-'));
  const sessionsDir = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions');
  mkdirSync(sessionsDir, { recursive: true });

  const unreadableSession = path.join(sessionsDir, '2026-05-12-unreadable');
  const readableSession = path.join(sessionsDir, '2026-05-12-readable');
  mkdirSync(unreadableSession);
  mkdirSync(readableSession);

  const unreadableLog = path.join(unreadableSession, 'tmux-runner.log');
  const readableLog = path.join(readableSession, 'tmux-runner.log');
  writeFileSync(unreadableLog, 'iteration 9 completed\n');
  writeFileSync(readableLog, 'iteration 4 completed\niteration 5 starting\n');

  const now = new Date();
  const older = new Date(now.getTime() - 60_000);
  const newer = new Date(now.getTime() + 60_000);
  utimesSync(readableLog, older, older);
  utimesSync(unreadableLog, newer, newer);
  chmodSync(unreadableLog, 0o000);

  const result = spawnSync(process.execPath, [CLI], {
    cwd: REPO_ROOT,
    encoding: 'utf8',
    env: {
      ...process.env,
      HOME: fakeHome,
    },
  });
  const artifactPath = path.join(readableSession, 'bundle', 'section-c-still-needed.json');
  const artifact = JSON.parse(readFileSync(artifactPath, 'utf8'));

  try {
    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, false);
    assert.match(artifact.evidence, /iteration 5 starting/);
    assert.equal(result.stdout.includes(unreadableSession), false, 'CLI should not pin the unreadable session');
  } finally {
    chmodSync(unreadableLog, 0o644);
    rmSync(fakeHome, { recursive: true, force: true });
  }
});

test('section-c-gate.default-session-selection ignores directory-shaped watcher log paths when ranking sessions', () => {
  const fakeHome = mkdtempSync(path.join(tmpdir(), 'section-c-home-'));
  const sessionsDir = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions');
  mkdirSync(sessionsDir, { recursive: true });

  const bogusSession = path.join(sessionsDir, '2026-05-12-bogus');
  const readableSession = path.join(sessionsDir, '2026-05-12-readable');
  mkdirSync(path.join(bogusSession, 'tmux-runner.log'), { recursive: true });
  mkdirSync(readableSession);
  const readableLog = path.join(readableSession, 'tmux-runner.log');
  writeFileSync(readableLog, 'iteration 4 completed\niteration 5 starting\n');

  const now = new Date();
  const older = new Date(now.getTime() - 60_000);
  const newer = new Date(now.getTime() + 60_000);
  utimesSync(readableLog, older, older);
  utimesSync(path.join(bogusSession, 'tmux-runner.log'), newer, newer);

  const result = spawnSync(process.execPath, [CLI], {
    cwd: REPO_ROOT,
    encoding: 'utf8',
    env: {
      ...process.env,
      HOME: fakeHome,
    },
  });
  const artifactPath = path.join(readableSession, 'bundle', 'section-c-still-needed.json');
  const artifact = JSON.parse(readFileSync(artifactPath, 'utf8'));

  try {
    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, false);
    assert.match(artifact.evidence, /iteration 5 starting/);
    assert.equal(result.stdout.includes(bogusSession), false, 'CLI should not pin the bogus session');
  } finally {
    rmSync(fakeHome, { recursive: true, force: true });
  }
});

test('section-c-gate.default-session-selection fails open when the runtime sessions dir is unreadable', () => {
  const fakeHome = mkdtempSync(path.join(tmpdir(), 'section-c-home-'));
  const sessionsDir = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions');
  mkdirSync(sessionsDir, { recursive: true });
  chmodSync(sessionsDir, 0o000);

  try {
    const result = spawnSync(process.execPath, [CLI], {
      cwd: REPO_ROOT,
      encoding: 'utf8',
      env: {
        ...process.env,
        HOME: fakeHome,
      },
    });
    const artifactPath = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'bundle', 'section-c-still-needed.json');
    const artifact = JSON.parse(readFileSync(artifactPath, 'utf8'));

    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, true);
    assert.match(artifact.evidence, /No recent session found/);
  } finally {
    chmodSync(sessionsDir, 0o755);
    rmSync(fakeHome, { recursive: true, force: true });
  }
});

test('section-c-gate.no-session writes runtime artifact outside the tracked repo bundle tree', () => {
  const fakeHome = mkdtempSync(path.join(tmpdir(), 'section-c-home-'));
  const result = spawnSync(process.execPath, [CLI], {
    cwd: REPO_ROOT,
    encoding: 'utf8',
    env: {
      ...process.env,
      HOME: fakeHome,
    },
  });
  const artifactPath = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'bundle', 'section-c-still-needed.json');
  const artifact = JSON.parse(readFileSync(artifactPath, 'utf8'));

  try {
    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, true);
    assert.equal(artifactPath.startsWith(path.join(REPO_ROOT, 'bundle')), false);
  } finally {
    rmSync(fakeHome, { recursive: true, force: true });
  }
});

test('section-c-gate.missing-explicit-session falls back to the runtime artifact instead of creating a fake session dir', () => {
  const fakeHome = mkdtempSync(path.join(tmpdir(), 'section-c-home-'));
  const missingSession = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions', '2026-05-12-missing');
  const runtimeArtifactPath = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'bundle', 'section-c-still-needed.json');
  const result = spawnSync(process.execPath, [CLI, '--session', missingSession], {
    cwd: REPO_ROOT,
    encoding: 'utf8',
    env: {
      ...process.env,
      HOME: fakeHome,
    },
  });
  const artifact = JSON.parse(readFileSync(runtimeArtifactPath, 'utf8'));

  try {
    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, true);
    assert.equal(result.stdout.includes(runtimeArtifactPath), true);
    assert.equal(result.stdout.includes(path.join(missingSession, 'bundle')), false);
    assert.equal(readFileSync(runtimeArtifactPath, 'utf8').includes('No recent session found'), true);
    assert.equal(result.stdout.includes(missingSession), false);
    assert.equal(existsSync(missingSession), false);
  } finally {
    rmSync(fakeHome, { recursive: true, force: true });
  }
});

test('section-c-gate.unreadable-explicit-session falls back to the runtime artifact instead of crashing on session bundle writes', () => {
  const fakeHome = mkdtempSync(path.join(tmpdir(), 'section-c-home-'));
  const session = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions', '2026-05-12-unreadable');
  const runtimeArtifactPath = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'bundle', 'section-c-still-needed.json');
  mkdirSync(session, { recursive: true });
  writeFileSync(path.join(session, 'tmux-runner.log'), 'iteration 4 completed\n');
  chmodSync(session, 0o000);

  try {
    const result = spawnSync(process.execPath, [CLI, '--session', session], {
      cwd: REPO_ROOT,
      encoding: 'utf8',
      env: {
        ...process.env,
        HOME: fakeHome,
      },
    });
    const artifact = JSON.parse(readFileSync(runtimeArtifactPath, 'utf8'));

    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, true);
    assert.match(artifact.evidence, /not writable/);
    assert.equal(result.stdout.includes(runtimeArtifactPath), true);
    assert.equal(result.stdout.includes(path.join(session, 'bundle')), false);
    assert.equal(existsSync(path.join(session, 'bundle', 'section-c-still-needed.json')), false);
  } finally {
    chmodSync(session, 0o755);
    rmSync(fakeHome, { recursive: true, force: true });
  }
});

test('section-c-gate.file-explicit-session falls back to the runtime artifact instead of crashing on non-directory bundle writes', () => {
  const fakeHome = mkdtempSync(path.join(tmpdir(), 'section-c-home-'));
  const sessionFile = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'sessions', '2026-05-12-file');
  const runtimeArtifactPath = path.join(fakeHome, '.local', 'share', 'pickle-rick', 'bundle', 'section-c-still-needed.json');
  mkdirSync(path.dirname(sessionFile), { recursive: true });
  writeFileSync(sessionFile, 'not a session directory\n');

  try {
    const result = spawnSync(process.execPath, [CLI, '--session', sessionFile], {
      cwd: REPO_ROOT,
      encoding: 'utf8',
      env: {
        ...process.env,
        HOME: fakeHome,
      },
    });
    const artifact = JSON.parse(readFileSync(runtimeArtifactPath, 'utf8'));

    assert.equal(result.status, 0, result.stderr);
    assert.equal(artifact.still_needed, true);
    assert.match(artifact.evidence, /not writable/);
    assert.equal(result.stdout.includes(runtimeArtifactPath), true);
    assert.equal(result.stdout.includes(path.join(sessionFile, 'bundle')), false);
  } finally {
    rmSync(fakeHome, { recursive: true, force: true });
  }
});

test('section-c-gate.cli-guard rejects unknown arguments', () => {
  const result = spawnSync(process.execPath, [CLI, '--bogus'], {
    cwd: REPO_ROOT,
    encoding: 'utf8',
  });
  assert.equal(result.status, 2);
  assert.match(result.stderr, /Usage:/);
});
