/*
 * Tests for the `validateRegistrationCandidate` path-validator in
 * scripts/configure-mcp.ts.
 *
 * Pins the safety contract that fixes the production failure mode where
 * configure-mcp.ts iterated Claude Code's project history and wrote
 * .mcp.json into every path (including filesystem root '/'). Refusing
 * aggressively keeps unrelated trees clean and prevents single-bad-path
 * crashes from aborting the rest of setup.sh.
 *
 * Tested refusal categories:
 *   - filesystem root
 *   - $HOME / Desktop / ~/.claude (hub dirs, not project dirs)
 *   - ancestors of $HOME
 *   - non-existent paths
 *   - non-directories
 *   - non-writable directories
 *
 * Acceptance categories:
 *   - any plain writable directory under $HOME that isn't one of the above
 *
 * The validator is host-OS-aware; tests mock os.homedir() to a fixed
 * value so the assertions stay deterministic across machines.
 */

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

const MOCK_HOME = '/home/testuser';

vi.mock('os', async () => {
  const actual = await vi.importActual<typeof import('os')>('os');
  return {
    ...actual,
    platform: () => 'linux',
    homedir: () => MOCK_HOME,
  };
});

// Use the real temp dir for "exists + writable" cases; the rest are
// purely path-string checks that don't need the path to exist.
const REAL_TMP_BASE = os.tmpdir();

describe('validateRegistrationCandidate — refusal categories', () => {
  beforeEach(() => {
    vi.resetModules();
  });
  afterEach(() => {
    vi.clearAllMocks();
  });

  it('refuses filesystem root', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    const reason = validateRegistrationCandidate('/');
    expect(reason).toMatch(/filesystem root/i);
  });

  it('refuses $HOME itself', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    expect(validateRegistrationCandidate(MOCK_HOME)).toMatch(/HOME/);
  });

  it('refuses $HOME/Desktop', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    expect(validateRegistrationCandidate(path.join(MOCK_HOME, 'Desktop'))).toMatch(
      /Desktop/i,
    );
  });

  it('refuses ~/.claude', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    expect(validateRegistrationCandidate(path.join(MOCK_HOME, '.claude'))).toMatch(
      /Claude Code config/i,
    );
  });

  it('refuses ancestors of $HOME (e.g. /home, /Users)', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    // /home contains /home/testuser, so it's an ancestor.
    expect(validateRegistrationCandidate('/home')).toMatch(/ancestor of \$HOME/);
  });

  it('refuses non-existent paths', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    expect(
      validateRegistrationCandidate('/this/path/definitely/does/not/exist'),
    ).toMatch(/does not exist/);
  });

  it('refuses files (not directories)', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    // Use a known file (this test file itself).
    const reason = validateRegistrationCandidate(__filename);
    expect(reason).toMatch(/not a directory/);
  });

  it('refuses non-writable directories', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    // Create a temp directory with mode 0500 (read+execute, no write).
    const dir = fs.mkdtempSync(path.join(REAL_TMP_BASE, 'pf-ro-'));
    try {
      fs.chmodSync(dir, 0o500);
      expect(validateRegistrationCandidate(dir)).toMatch(/not writable/);
    } finally {
      // Restore + cleanup. chmod back so we can rmdir on linux.
      fs.chmodSync(dir, 0o700);
      fs.rmdirSync(dir);
    }
  });
});

describe('validateRegistrationCandidate — acceptance', () => {
  it('accepts a plain writable directory under $HOME', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    // Create a real temp dir (writable, exists, is a directory).
    const dir = fs.mkdtempSync(path.join(REAL_TMP_BASE, 'pf-ok-'));
    try {
      expect(validateRegistrationCandidate(dir)).toBeNull();
    } finally {
      fs.rmdirSync(dir);
    }
  });

  it('accepts a project-shaped directory (cwd of the test runner is fine)', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    // The test runner's cwd is the priority-forge repo root — always
    // writable, exists, is a directory, not in the refusal list.
    expect(validateRegistrationCandidate(process.cwd())).toBeNull();
  });
});

describe('validateRegistrationCandidate — relative paths', () => {
  it('resolves relative paths before checking refusal categories', async () => {
    const { validateRegistrationCandidate } = await import(
      '../../scripts/configure-mcp'
    );
    // '.' resolves to cwd which is fine.
    expect(validateRegistrationCandidate('.')).toBeNull();
  });
});
