/**
 * validate.ts — Validates the development plan and checks per-module readiness.
 *
 * Reads dev-plan.json (produced by /ba-create-plan-development) and verifies that
 * each module has the required PRD files and a passing audit verdict.
 */
import path from 'node:path';
import {
  fileExists,
  directoryExists,
  readJson,
  readText,
} from '../../../../lib/fs.js';
import { PreflightInputSchema } from './types.js';
import type {
  PreflightInput,
  DevelopmentPlan,
  ReadinessInfo,
  ModulePreflight,
  WavePreflight,
  PreflightReport,
} from './types.js';

// ---------------------------------------------------------------------------
// Readiness verdict parsing (same regex as create-plan-development/parse.ts)
// ---------------------------------------------------------------------------

const VERDICT_RE =
  /Verdict\s*:\s*(GO|NO-GO)(?:.*?score\s+(\d+))?(?:.*?(\d+)\s+err)?(?:.*?(\d+)\s+warn)?/;

async function parseReadiness(
  auditPath: string,
): Promise<ReadinessInfo> {
  const exists = await fileExists(auditPath);
  if (!exists) return { verdict: 'UNKNOWN' };

  const content = await readText(auditPath);
  const match = VERDICT_RE.exec(content);
  if (!match) return { verdict: 'UNKNOWN' };

  return {
    verdict: match[1] as 'GO' | 'NO-GO',
    score: match[2] ? Number(match[2]) : undefined,
    errCount: match[3] ? Number(match[3]) : undefined,
    warnCount: match[4] ? Number(match[4]) : undefined,
  };
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

export interface ValidateResult {
  valid: boolean;
  errors: string[];
  warnings: string[];
  spec?: PreflightInput;
  plan?: DevelopmentPlan;
}

/**
 * Stage 1: Zod validation + plan file existence.
 */
export async function validateInput(raw: unknown): Promise<ValidateResult> {
  const errors: string[] = [];
  const warnings: string[] = [];

  const parsed = PreflightInputSchema.safeParse(raw);
  if (!parsed.success) {
    const zodErrors = parsed.error.issues.map(
      (i) => `${i.path.join('.')}: ${i.message}`,
    );
    return { valid: false, errors: zodErrors, warnings };
  }

  const spec = parsed.data;

  // Check baRoot exists
  if (!(await directoryExists(spec.baRoot))) {
    errors.push(`BA root not found: ${spec.baRoot}`);
    return { valid: false, errors, warnings };
  }

  // Check dev-plan.json exists
  const planPath = path.join(spec.baRoot, '_plan', 'dev-plan.json');
  if (!(await fileExists(planPath))) {
    errors.push(
      `Development plan not found: ${planPath}. Run /ba-create-plan-development first.`,
    );
    return { valid: false, errors, warnings };
  }

  // Read and parse the plan
  let plan: DevelopmentPlan;
  try {
    plan = await readJson<DevelopmentPlan>(planPath);
  } catch (err) {
    errors.push(
      `Failed to parse dev-plan.json: ${err instanceof Error ? err.message : String(err)}`,
    );
    return { valid: false, errors, warnings };
  }

  // Basic plan validation
  if (!plan.waves || plan.waves.length === 0) {
    errors.push('Development plan has no waves');
    return { valid: false, errors, warnings };
  }

  // Check projectPath exists
  if (!(await directoryExists(spec.projectPath))) {
    errors.push(`Project path not found: ${spec.projectPath}`);
    return { valid: false, errors, warnings };
  }

  // Warn about cycles
  if (plan.cycles && plan.cycles.length > 0) {
    warnings.push(
      `Plan contains ${plan.cycles.length} circular dependency group(s) — co-develop those modules`,
    );
  }

  return { valid: true, errors, warnings, spec, plan };
}

/**
 * Stage 2: Per-module readiness check.
 * Returns the full preflight report.
 */
export async function checkReadiness(
  spec: PreflightInput,
  plan: DevelopmentPlan,
): Promise<PreflightReport> {
  const wavesToCheck = plan.waves.filter((w) => w.level > 0);
  const wavePreflights: WavePreflight[] = [];
  let readyCount = 0;
  let blockedCount = 0;
  let externalCount = 0;

  // Wave 0: externals (just count them)
  const wave0 = plan.waves.find((w) => w.level === 0);
  if (wave0) {
    externalCount = wave0.modules.length;
  }

  for (const wave of wavesToCheck) {
    // Filter waves if the user specified specific ones
    if (spec.waves && spec.waves.length > 0 && !spec.waves.includes(wave.level)) {
      continue;
    }

    const modulePreflights: ModulePreflight[] = [];
    const waveBlockers: string[] = [];

    for (const mod of wave.modules) {
      const preflight = await checkModule(spec.baRoot, mod.appCode, mod.moduleCode, wave.level);
      modulePreflights.push(preflight);

      if (preflight.status === 'ready') {
        readyCount++;
      } else {
        blockedCount++;
        waveBlockers.push(`${mod.key}: ${preflight.blockers.join('; ')}`);
      }
    }

    wavePreflights.push({
      level: wave.level,
      status: waveBlockers.length === 0 ? 'ready' : 'blocked',
      modules: modulePreflights,
      blockers: waveBlockers,
    });
  }

  // Determine which waves can proceed
  let firstReadyWave: number | null = null;
  let lastReadyWave: number | null = null;

  for (const wp of wavePreflights) {
    if (wp.status === 'ready') {
      if (firstReadyWave === null) firstReadyWave = wp.level;
      lastReadyWave = wp.level;
    } else {
      // A blocked wave stops the chain
      break;
    }
  }

  return {
    planDate: plan.generatedAt,
    apps: plan.apps,
    projectPath: spec.projectPath,
    totalModules: readyCount + blockedCount,
    readyModules: readyCount,
    blockedModules: blockedCount,
    externalDeps: externalCount,
    hasCycles: (plan.cycles?.length ?? 0) > 0,
    waves: wavePreflights,
    canProceed: firstReadyWave !== null,
    firstReadyWave,
    lastReadyWave,
  };
}

// ---------------------------------------------------------------------------
// Per-module check
// ---------------------------------------------------------------------------

async function checkModule(
  baRoot: string,
  appCode: string,
  moduleCode: string,
  waveLevel: number,
): Promise<ModulePreflight> {
  const key = `${appCode}/${moduleCode}`;
  const moduleDir = path.join(baRoot, appCode, moduleCode);
  const blockers: string[] = [];
  const warnings: string[] = [];

  // Check PRD files
  const prdFiles = {
    prd: await fileExists(path.join(moduleDir, 'prd.md')),
    entities: await fileExists(path.join(moduleDir, 'prd.entities.md')),
    api: await fileExists(path.join(moduleDir, 'prd.api.md')),
    frontend: await fileExists(path.join(moduleDir, 'prd.frontend.md')),
    pagespecs: await directoryExists(path.join(moduleDir, 'pagespecs')),
  };

  if (!prdFiles.prd) blockers.push('prd.md missing — run /ba-create-prd');
  if (!prdFiles.entities) blockers.push('prd.entities.md missing');
  if (!prdFiles.api) blockers.push('prd.api.md missing');
  if (!prdFiles.frontend) blockers.push('prd.frontend.md missing');
  if (!prdFiles.pagespecs) warnings.push('pagespecs/ directory missing — frontend phase may have issues');

  // Check audit readiness
  const auditPath = path.join(moduleDir, '_audit', 'prd.md');
  const readiness = await parseReadiness(auditPath);

  if (readiness.verdict === 'UNKNOWN') {
    blockers.push('_audit/prd.md missing — run /ba-audit-prd');
  } else if (readiness.verdict === 'NO-GO') {
    blockers.push(
      `Audit verdict: NO-GO${readiness.score != null ? ` (score ${readiness.score}/100)` : ''}${readiness.errCount != null ? ` — ${readiness.errCount} err` : ''}`,
    );
  } else if (readiness.verdict === 'GO' && readiness.score != null && readiness.score < 80) {
    blockers.push(`Audit score ${readiness.score}/100 < 80 — not dev-ready`);
  }

  return {
    appCode,
    moduleCode,
    key,
    wave: waveLevel,
    status: blockers.length === 0 ? 'ready' : 'not-ready',
    moduleDir,
    readiness,
    prdFiles,
    blockers,
    warnings,
  };
}
