import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { execSync } from 'node:child_process';
import { mkdtemp, rm, writeFile, mkdir } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

const HOOK_TEST_TIMEOUT_MS = process.platform === 'win32' ? 60_000 : 30_000;

let tempDir: string;

beforeEach(async () => {
  tempDir = await mkdtemp(join(tmpdir(), 'cf-stop-'));
});

afterEach(async () => {
  await rm(tempDir, {
    recursive: true,
    force: true,
    maxRetries: 10,
    retryDelay: 200,
  });
});

interface HookResult {
  exitCode: number;
  stdout: string;
  stderr: string;
}

function runHook(input: string, cwd: string): HookResult {
  const srcRoot = join(import.meta.dirname, '..', '..', '..');
  const scriptPath = join(srcRoot, 'src', 'presentation', 'hooks', 'stop.ts');
  try {
    const stdout = execSync(`"${srcRoot}/node_modules/.bin/tsx" "${scriptPath}"`, {
      input,
      encoding: 'utf-8',
      cwd,
      timeout: HOOK_TEST_TIMEOUT_MS,
      stdio: ['pipe', 'pipe', 'pipe'],
    });
    return { exitCode: 0, stdout, stderr: '' };
  } catch (error: unknown) {
    const e = error as {
      status: number;
      stdout: string;
      stderr: string;
    };
    return {
      exitCode: e.status,
      stdout: e.stdout ?? '',
      stderr: e.stderr ?? '',
    };
  }
}

describe('stop hook (integration)', () => {
  it('exits 0 when no active flow', () => {
    const result = runHook('{}', tempDir);
    expect(result.exitCode).toBe(0);
  });

  it('exits 2 when flow is active', async () => {
    const stateDir = join(tempDir, '.prompt-language');
    await mkdir(stateDir, { recursive: true });
    const state = {
      sessionId: 'test-session',
      flowSpec: {
        goal: 'Active task',
        nodes: [],
        completionGates: [],
        defaults: { maxIterations: 5, maxAttempts: 3 },
        warnings: [],
      },
      currentNodePath: [0],
      nodeProgress: {},
      variables: {},
      gateResults: {},
      gateDiagnostics: {},
      status: 'active',
      warnings: [],
    };
    await writeFile(join(stateDir, 'session-state.json'), JSON.stringify(state));

    const result = runHook('{}', tempDir);
    expect(result.exitCode).toBe(2);
    expect(result.stderr).toContain('[prompt-language]');
    expect(result.stderr).toContain('Active task');
  });

  it('exits 0 when stop_hook_active is true (prevents infinite loop)', async () => {
    const stateDir = join(tempDir, '.prompt-language');
    await mkdir(stateDir, { recursive: true });
    const state = {
      sessionId: 'test-session',
      flowSpec: {
        goal: 'Active task',
        nodes: [],
        completionGates: [],
        defaults: { maxIterations: 5, maxAttempts: 3 },
        warnings: [],
      },
      currentNodePath: [0],
      nodeProgress: {},
      variables: {},
      gateResults: {},
      gateDiagnostics: {},
      status: 'active',
      warnings: [],
    };
    await writeFile(join(stateDir, 'session-state.json'), JSON.stringify(state));

    const result = runHook(JSON.stringify({ stop_hook_active: true }), tempDir);
    expect(result.exitCode).toBe(0);
  });

  it('exits 0 when state file contains corrupted JSON (fail-open)', async () => {
    const stateDir = join(tempDir, '.prompt-language');
    await mkdir(stateDir, { recursive: true });
    await writeFile(join(stateDir, 'session-state.json'), '{{corrupted garbage');

    const result = runHook('{}', tempDir);
    expect(result.exitCode).toBe(0);
  });

  it('exits 0 when flow is completed', async () => {
    const stateDir = join(tempDir, '.prompt-language');
    await mkdir(stateDir, { recursive: true });
    const state = {
      sessionId: 'test-session',
      flowSpec: {
        goal: 'Done task',
        nodes: [],
        completionGates: [],
        defaults: { maxIterations: 5, maxAttempts: 3 },
        warnings: [],
      },
      currentNodePath: [0],
      nodeProgress: {},
      variables: {},
      gateResults: {},
      gateDiagnostics: {},
      status: 'completed',
      warnings: [],
    };
    await writeFile(join(stateDir, 'session-state.json'), JSON.stringify(state));

    const result = runHook('{}', tempDir);
    expect(result.exitCode).toBe(0);
  });

  it('marks running spawned children failed when a terminal flow stops', async () => {
    const stateDir = join(tempDir, '.prompt-language');
    await mkdir(stateDir, { recursive: true });
    const state = {
      version: 1,
      sessionId: 'test-session',
      flowSpec: {
        goal: 'Failed task',
        nodes: [],
        completionGates: [],
        defaults: { maxIterations: 5, maxAttempts: 3 },
        warnings: [],
      },
      currentNodePath: [0],
      nodeProgress: {},
      variables: {},
      gateResults: {},
      gateDiagnostics: {},
      status: 'failed',
      warnings: [],
      spawnedChildren: {
        child: {
          name: 'child',
          status: 'running',
          pid: 999999,
          stateDir: join(tempDir, '.prompt-language-child'),
        },
      },
      raceChildren: {},
      captureNonce: 'test-nonce-1234',
    };
    const statePath = join(stateDir, 'session-state.json');
    await writeFile(statePath, JSON.stringify(state));

    const result = runHook('{}', tempDir);
    expect(result.exitCode).toBe(0);

    const saved = JSON.parse(await (await import('node:fs/promises')).readFile(statePath, 'utf-8'));
    expect(saved.spawnedChildren.child.status).toBe('failed');
  });
});
