import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';

import {
  stageTaskId,
  stageDir,
  stageBriefPath,
  stageResultPath,
  buildStageBrief,
  mapStageToTask,
} from '../src/stage-adapter.js';
import { STAGE_SEQUENCE, STAGE_SPECS } from '../src/constants.js';

describe('stageTaskId', () => {
  it('generates correct task ID', () => {
    expect(stageTaskId('20260330-120000', '00-intake')).toBe('FORGE-20260330-120000-00');
    expect(stageTaskId('20260330-120000', '05-coding')).toBe('FORGE-20260330-120000-05');
  });

  it('throws for unknown stage', () => {
    expect(() => stageTaskId('run1', 'unknown-stage')).toThrow('Unknown Forge stage');
  });
});

describe('stageDir', () => {
  it('returns correct directory path', () => {
    expect(stageDir('/runs/abc', '00-intake')).toBe('/runs/abc/00-intake');
  });
});

describe('stageBriefPath', () => {
  it('returns brief.md inside stage directory', () => {
    expect(stageBriefPath('/runs/abc', '00-intake')).toBe('/runs/abc/00-intake/brief.md');
  });
});

describe('stageResultPath', () => {
  it('returns result.json inside stage directory', () => {
    expect(stageResultPath('/runs/abc', '05-coding')).toBe('/runs/abc/05-coding/result.json');
  });
});

describe('buildStageBrief', () => {
  it('includes all sections', () => {
    const brief = buildStageBrief({
      stageName: '00-intake',
      stagePrompt: 'Parse the brief into structured data.',
      briefContent: '# My Brief\n\nImplement feature X.',
      projectRoot: '/project',
      runId: 'abc',
      runDir: '/runs/abc',
    });

    expect(brief).toContain('# Forge Pipeline Stage: 00-intake');
    expect(brief).toContain('Run ID: abc');
    expect(brief).toContain('Project Root: /project');
    expect(brief).toContain('# My Brief');
    expect(brief).toContain('Implement feature X.');
    expect(brief).toContain('Parse the brief into structured data.');
    expect(brief).toContain('/runs/abc/');
  });
});

describe('mapStageToTask', () => {
  let tmpDir: string;
  let runDir: string;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-stage-adapter-'));
    runDir = path.join(tmpDir, 'runs', 'test-run');
    fs.mkdirSync(runDir, { recursive: true });
  });

  afterEach(() => {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  it('maps intake stage correctly', () => {
    const task = mapStageToTask({
      stageName: '00-intake',
      briefContent: '# Test Brief',
      projectRoot: tmpDir,
      runId: 'test-run',
      runDir,
    });

    expect(task.id).toBe('FORGE-test-run-00');
    expect(task.title).toBe('Forge Intake');
    expect(task.status).toBe('pending');
    expect(task.dispatch).toBe('exec');
    expect(task.type).toBe('research');
    expect(task.timeoutSeconds).toBe(120);
    expect(task.qualityGates).toEqual([]);
    expect(task.dependsOn).toBeUndefined(); // First stage has no deps
    expect(task.worktree).toBe(path.resolve(tmpDir));
  });

  it('writes brief to disk', () => {
    mapStageToTask({
      stageName: '00-intake',
      briefContent: '# Test Brief',
      projectRoot: tmpDir,
      runId: 'test-run',
      runDir,
    });

    const briefPath = path.join(runDir, '00-intake', 'brief.md');
    expect(fs.existsSync(briefPath)).toBe(true);
    const content = fs.readFileSync(briefPath, 'utf-8');
    expect(content).toContain('# Test Brief');
  });

  it('sets depends_on for non-first stages', () => {
    const task = mapStageToTask({
      stageName: '00b-discovery',
      briefContent: '# Test',
      projectRoot: tmpDir,
      runId: 'test-run',
      runDir,
    });

    expect(task.dependsOn).toEqual(['FORGE-test-run-00']);
  });

  it('includes metadata with stage info', () => {
    const task = mapStageToTask({
      stageName: '05-coding',
      briefContent: '# Test',
      projectRoot: tmpDir,
      runId: 'test-run',
      runDir,
    });

    expect(task.metadata['stageName']).toBe('05-coding');
    expect(task.metadata['stageNumber']).toBe('05');
    expect(task.metadata['gate']).toBe('lint-build-test');
    expect(task.metadata['runId']).toBe('test-run');
  });

  it('yolo dispatch does not set worktree', () => {
    const task = mapStageToTask({
      stageName: '05-coding',
      briefContent: '# Test',
      projectRoot: tmpDir,
      runId: 'test-run',
      runDir,
    });

    expect(task.dispatch).toBe('yolo');
    expect(task.worktree).toBeUndefined();
  });

  it('throws for unknown stage', () => {
    expect(() =>
      mapStageToTask({
        stageName: 'unknown',
        briefContent: 'test',
        projectRoot: tmpDir,
        runId: 'r1',
        runDir,
      }),
    ).toThrow('Unknown Forge stage');
  });

  it('all stages in STAGE_SEQUENCE have specs', () => {
    for (const stage of STAGE_SEQUENCE) {
      expect(STAGE_SPECS[stage]).toBeDefined();
    }
  });
});
