/**
 * validate.test.ts — Tests for preflight-develop-plan validation.
 */
import { describe, it, expect } from 'vitest';
import { PreflightInputSchema } from '../types.js';
import type { DevelopmentPlan, ReadinessInfo } from '../types.js';

// ---------------------------------------------------------------------------
// Zod input schema
// ---------------------------------------------------------------------------

describe('PreflightInputSchema', () => {
  it('accepts valid input', () => {
    const result = PreflightInputSchema.safeParse({
      baRoot: '.smartstack/ba',
      projectPath: '/path/to/project',
    });
    expect(result.success).toBe(true);
  });

  it('accepts input with specific waves', () => {
    const result = PreflightInputSchema.safeParse({
      baRoot: '.smartstack/ba',
      projectPath: '/path/to/project',
      waves: [1, 2],
    });
    expect(result.success).toBe(true);
    if (result.success) {
      expect(result.data.waves).toEqual([1, 2]);
    }
  });

  it('rejects empty baRoot', () => {
    const result = PreflightInputSchema.safeParse({
      baRoot: '',
      projectPath: '/path/to/project',
    });
    expect(result.success).toBe(false);
  });

  it('rejects missing projectPath', () => {
    const result = PreflightInputSchema.safeParse({
      baRoot: '.smartstack/ba',
    });
    expect(result.success).toBe(false);
  });

  it('rejects wave 0 (external deps are not developable)', () => {
    const result = PreflightInputSchema.safeParse({
      baRoot: '.smartstack/ba',
      projectPath: '/path/to/project',
      waves: [0, 1],
    });
    expect(result.success).toBe(false);
  });
});

// ---------------------------------------------------------------------------
// Readiness verdict patterns
// ---------------------------------------------------------------------------

describe('readiness verdict detection', () => {
  const VERDICT_RE =
    /Verdict\s*:\s*(GO|NO-GO)(?:.*?score\s+(\d+))?(?:.*?(\d+)\s+err)?(?:.*?(\d+)\s+warn)?/;

  it('detects GO with high score', () => {
    const line = '_2026-05-26 · Verdict : GO · score 92/100 · 0 err · 1 warn · 20 ok_';
    const match = VERDICT_RE.exec(line);
    expect(match).not.toBeNull();
    expect(match![1]).toBe('GO');
    expect(Number(match![2])).toBe(92);
    expect(Number(match![3])).toBe(0);
  });

  it('detects NO-GO', () => {
    const line = '_2026-05-26 · Verdict : NO-GO · score 65/100 · 5 err · 3 warn · 10 ok_';
    const match = VERDICT_RE.exec(line);
    expect(match).not.toBeNull();
    expect(match![1]).toBe('NO-GO');
    expect(Number(match![2])).toBe(65);
    expect(Number(match![3])).toBe(5);
  });

  it('GO with score < 80 is detectable', () => {
    const line = '_2026-05-26 · Verdict : GO · score 75/100 · 0 err · 8 warn_';
    const match = VERDICT_RE.exec(line);
    expect(match).not.toBeNull();
    expect(match![1]).toBe('GO');
    expect(Number(match![2])).toBe(75);
    // Caller checks score < 80 → blocker
  });
});

// ---------------------------------------------------------------------------
// Plan structure validation
// ---------------------------------------------------------------------------

describe('plan structure checks', () => {
  function makePlan(overrides: Partial<DevelopmentPlan> = {}): DevelopmentPlan {
    return {
      generatedAt: '2026-05-26',
      apps: ['CRM'],
      totalModules: 2,
      totalCrossModuleDeps: 1,
      waves: [
        {
          level: 1,
          modules: [
            {
              appCode: 'CRM',
              moduleCode: 'CONTACTS',
              key: 'CRM/CONTACTS',
              entityCount: 3,
              dependsOn: [],
              readiness: { verdict: 'GO', score: 88, errCount: 0, warnCount: 2 },
              isExternal: false,
              warnings: [],
            },
          ],
        },
        {
          level: 2,
          modules: [
            {
              appCode: 'CRM',
              moduleCode: 'PIPELINE',
              key: 'CRM/PIPELINE',
              entityCount: 4,
              dependsOn: ['CRM/CONTACTS'],
              readiness: { verdict: 'GO', score: 85, errCount: 0, warnCount: 1 },
              isExternal: false,
              warnings: [],
            },
          ],
        },
      ],
      cycles: [],
      warnings: [],
      ...overrides,
    };
  }

  it('valid plan has waves', () => {
    const plan = makePlan();
    expect(plan.waves.length).toBeGreaterThan(0);
  });

  it('empty waves should be rejected', () => {
    const plan = makePlan({ waves: [] });
    expect(plan.waves.length).toBe(0);
  });

  it('cycles are flagged', () => {
    const plan = makePlan({
      cycles: [['CRM/A', 'CRM/B']],
    });
    expect(plan.cycles.length).toBeGreaterThan(0);
  });

  it('external deps are in wave 0', () => {
    const plan = makePlan({
      waves: [
        {
          level: 0,
          modules: [
            {
              appCode: 'HR',
              moduleCode: 'EMPLOYEES',
              key: 'HR/EMPLOYEES',
              entityCount: 0,
              dependsOn: [],
              isExternal: true,
              referencedBy: ['CRM/CONTACTS'],
              warnings: [],
            },
          ],
        },
        ...makePlan().waves,
      ],
    });
    const wave0 = plan.waves.find((w) => w.level === 0);
    expect(wave0).toBeDefined();
    expect(wave0!.modules[0].isExternal).toBe(true);
  });

  it('modules in the same wave have no mutual dependencies', () => {
    const plan = makePlan({
      waves: [
        {
          level: 1,
          modules: [
            {
              appCode: 'CRM',
              moduleCode: 'CONTACTS',
              key: 'CRM/CONTACTS',
              entityCount: 3,
              dependsOn: [],
              isExternal: false,
              warnings: [],
            },
            {
              appCode: 'BILLING',
              moduleCode: 'PRODUCTS',
              key: 'BILLING/PRODUCTS',
              entityCount: 2,
              dependsOn: [],
              isExternal: false,
              warnings: [],
            },
          ],
        },
      ],
    });

    const wave1 = plan.waves[0];
    const wave1Keys = new Set(wave1.modules.map((m) => m.key));

    // No module in wave 1 depends on another wave 1 module
    for (const mod of wave1.modules) {
      for (const dep of mod.dependsOn) {
        expect(wave1Keys.has(dep)).toBe(false);
      }
    }
  });
});
