/**
 * SET 56d — stage pipeline progression + App/CSS surface assertions.
 */
import { describe, expect, it } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import {
  STAGE_LABELS,
  STAGE_ORDER,
  canonicalizePhase,
  formatStageElapsed,
  stageProgress,
} from '../web/src/stagePipeline';

const APP = path.resolve(import.meta.dirname, '../web/src/App.tsx');
const CSS = path.resolve(import.meta.dirname, '../web/src/styles.css');

function appSrc(): string {
  return fs.readFileSync(APP, 'utf8');
}

function cssSrc(): string {
  return fs.readFileSync(CSS, 'utf8');
}

const T0 = Date.parse('2026-07-27T12:00:00.000Z');
function ts(offsetMs: number): string {
  return new Date(T0 + offsetMs).toISOString();
}

describe('SET 56d — stageProgress fixtures', () => {
  it('(a) running mid-build: encode/gate/ir done, build active, rest pending', () => {
    const events = [
      { ts: ts(0), phase: 'encode' },
      { ts: ts(1_000), phase: 'gate' },
      { ts: ts(2_000), phase: 'ir' },
      { ts: ts(5_000), phase: 'build' },
      { ts: ts(8_000), phase: 'build' },
    ];
    const rows = stageProgress(events, true, T0 + 10_000);
    expect(rows.map(r => r.phase)).toEqual([...STAGE_ORDER]);
    expect(rows.map(r => r.status)).toEqual([
      'done', 'done', 'done', 'active', 'pending', 'pending', 'pending',
    ]);
    expect(rows.filter(r => r.status === 'active')).toHaveLength(1);
    expect(rows[3]!.label).toBe('Build');
  });

  it('(b) finished clean: all done', () => {
    const events = STAGE_ORDER.map((phase, i) => ({
      ts: ts(i * 1_000),
      phase,
    }));
    const rows = stageProgress(events, false);
    expect(rows.every(r => r.status === 'done')).toBe(true);
  });

  it('(c) not running, newest in auth, never reached done → auth failed', () => {
    const events = [
      { ts: ts(0), phase: 'encode' },
      { ts: ts(1_000), phase: 'gate' },
      { ts: ts(2_000), phase: 'ir' },
      { ts: ts(3_000), phase: 'build' },
      { ts: ts(4_000), phase: 'auth' },
    ];
    const rows = stageProgress(events, false);
    expect(rows.map(r => r.status)).toEqual([
      'done', 'done', 'done', 'done', 'failed', 'pending', 'pending',
    ]);
    expect(rows[4]!.phase).toBe('auth');
    expect(rows[4]!.status).toBe('failed');
  });

  it('(d) unknown phase inherits previous stage', () => {
    expect(canonicalizePhase('weird', 'build')).toBe('build');
    expect(canonicalizePhase('weird', null)).toBe('encode');
    const events = [
      { ts: ts(0), phase: 'encode' },
      { ts: ts(1_000), phase: 'gate' },
      { ts: ts(2_000), phase: 'mystery-phase' },
      { ts: ts(3_000), phase: 'ir' },
    ];
    const rows = stageProgress(events, true, T0 + 4_000);
    // mystery inherits gate; newest is ir → ir active, encode/gate done
    expect(rows.map(r => r.status)).toEqual([
      'done', 'done', 'active', 'pending', 'pending', 'pending', 'pending',
    ]);
  });

  it('(e) elapsedMs from fixture timestamps', () => {
    const events = [
      { ts: ts(0), phase: 'encode' },
      { ts: ts(2_500), phase: 'gate' },
      { ts: ts(5_000), phase: 'ir' },
      { ts: ts(9_000), phase: 'build' },
    ];
    const now = T0 + 12_000;
    const rows = stageProgress(events, true, now);
    expect(rows[0]!.elapsedMs).toBe(2_500); // gate first − encode first
    expect(rows[1]!.elapsedMs).toBe(2_500); // ir − gate
    expect(rows[2]!.elapsedMs).toBe(4_000); // build − ir
    expect(rows[3]!.elapsedMs).toBe(3_000); // now − build first (last started, running)
    expect(rows[4]!.elapsedMs).toBeNull();
    expect(formatStageElapsed(65_000)).toBe('1m05s');
    expect(formatStageElapsed(9_000)).toBe('9s');
  });

  it('labels match canonical display names', () => {
    expect(Object.values(STAGE_LABELS)).toEqual([
      'Encode', 'Spec gate', 'IR contract', 'Build', 'Authenticate', 'RHA', 'Done',
    ]);
  });
});

describe('SET 56d — App + CSS surface', () => {
  it('stage-break, stageProgress, CSS, and phase tag present', () => {
    const src = appSrc();
    const css = cssSrc();
    expect(src).toContain('stageProgress');
    expect(src).toContain('stage-break');
    expect(src).toContain('stage-stepper');
    expect(src).toMatch(/\[\$\{e\.phase\}\]/);
    expect(css).toContain('.logrow.stage-break');
    expect(css).toContain('.stage-done');
    expect(css).toContain('.stage-active');
    expect(css).toContain('.stage-failed');
    expect(css).toContain('.stage-pending');
  });
});

describe('SET 56d — mock e2e mid-run acceptance', () => {
  it('mid-run: exactly one stage-active, ≥1 stage-break, phase tags on rows', () => {
    const events = [
      { ts: ts(0), phase: 'encode', featureId: null, message: 'encode start' },
      { ts: ts(500), phase: 'encode', featureId: null, message: 'encode ok' },
      { ts: ts(1_000), phase: 'gate', featureId: null, message: 'gate open' },
      { ts: ts(2_000), phase: 'ir', featureId: 'f1', message: 'ir freeze' },
      { ts: ts(5_000), phase: 'build', featureId: 'f1', message: 'building' },
    ];
    const stages = stageProgress(events, true, T0 + 6_000);
    expect(stages.filter(s => s.status === 'active')).toHaveLength(1);
    expect(stages.find(s => s.status === 'active')!.phase).toBe('build');

    // Simulate the App render path: stage-breaks + phase tags.
    let prevCanon: ReturnType<typeof canonicalizePhase> | null = null;
    let breaks = 0;
    const rows: string[] = [];
    for (const e of events) {
      const canon = canonicalizePhase(e.phase, prevCanon);
      if (prevCanon !== null && canon !== prevCanon) {
        breaks += 1;
        rows.push(`— ${STAGE_LABELS[canon]} —`);
      }
      prevCanon = canon;
      rows.push(`[+offset] [${e.phase}] ${e.featureId ? `${e.featureId} · ` : ''}${e.message}`);
    }
    expect(breaks).toBeGreaterThanOrEqual(1);
    expect(rows.some(r => r.includes('[build]'))).toBe(true);
    expect(rows.some(r => r.startsWith('— '))).toBe(true);

    // App wiring must be live (not title-only).
    const src = appSrc();
    expect(src).toContain('className="logrow stage-break"');
    expect(src).toContain('className={`stage-step stage-${s.status}`}');
    expect(src).toMatch(/\[\$\{e\.phase\}\]/);
  });
});
