/**
 * NLParserEvalAgent — A2A pipeline for ChronoMind natural-language timer parsing evaluation.
 *
 * Agent roster (3 steps):
 *   1. PhraseSamplerAgent   — assemble canonical NL phrase test suite
 *   2. ParseEvalAgent       — run each phrase through extraction service 'timer-parse' task
 *   3. RegressionReportAgent — validate parsed fields, flag failures, produce pass/fail scorecard
 *
 * MCP tools:
 *   chronomind.nlParser.eval(customPhrases?) — run eval pipeline; returns pass/fail scorecard
 */

import { randomUUID } from 'node:crypto';
import { z } from 'zod';
import { registerTool } from '../tools/registry.js';
import type { McpToolRequest } from '../tools/types.js';
import { extractionRun } from '../../lib/extraction-client.js';
import {
  trackRunCompleted,
  trackRunFailed,
  trackRunStarted,
  trackStepCompleted,
  trackStepFailed,
  trackStepStarted,
} from './run-tracker.js';
import { createSupportCaseForRun, recordBudgetSpend } from './governance.js';

// ── Canonical test suite ──────────────────────────────────────────────────────

interface NLTestCase {
  phrase: string;
  expectedFields: string[];
  description: string;
}

const CANONICAL_TEST_CASES: NLTestCase[] = [
  {
    phrase: 'Remind me in 30 minutes',
    expectedFields: ['duration'],
    description: 'Simple countdown',
  },
  {
    phrase: 'Wake me up at 7am tomorrow',
    expectedFields: ['targetTime'],
    description: 'Alarm at specific time',
  },
  {
    phrase: 'Meeting in 2 hours and 15 minutes',
    expectedFields: ['duration'],
    description: 'Compound duration',
  },
  {
    phrase: 'Call mom every day at 6pm',
    expectedFields: ['targetTime', 'recurrence'],
    description: 'Recurring alarm',
  },
  {
    phrase: 'Study session for 45 minutes then break for 10',
    expectedFields: ['duration'],
    description: 'Linked timers',
  },
  {
    phrase: 'Pomodoro 25 minutes focus',
    expectedFields: ['duration', 'timerType'],
    description: 'Pomodoro type',
  },
  {
    phrase: 'Take meds at 8am, noon, and 8pm',
    expectedFields: ['targetTime', 'recurrence'],
    description: 'Multi-time alarm',
  },
  {
    phrase: 'Boil eggs for 7 minutes',
    expectedFields: ['duration', 'label'],
    description: 'Labeled timer',
  },
  {
    phrase: 'Set a 1 hour countdown',
    expectedFields: ['duration'],
    description: 'Explicit countdown',
  },
  {
    phrase: 'Dentist appointment in 3 days',
    expectedFields: ['targetTime'],
    description: 'Days-ahead event',
  },
];

// ── Types ──────────────────────────────────────────────────────────────────────

type EvalOutcome = 'pass' | 'fail' | 'partial' | 'error';

interface ParseEvalResult {
  phrase: string;
  description: string;
  expectedFields: string[];
  parsedFields: string[];
  missingFields: string[];
  outcome: EvalOutcome;
  errorMessage?: string;
  rawExtraction: unknown;
}

export interface NLParserEvalReport {
  runId: string;
  productId: 'chronomind';
  totalCases: number;
  passed: number;
  partial: number;
  failed: number;
  errored: number;
  passRate: number;
  results: ParseEvalResult[];
  regressions: string[];
  summary: string;
  generatedAt: string;
}

// ── Step 1: PhraseSamplerAgent ────────────────────────────────────────────────

function assemblePhrases(customPhrases: string[]): NLTestCase[] {
  const custom: NLTestCase[] = customPhrases.map(phrase => ({
    phrase,
    expectedFields: ['duration'],
    description: 'Custom phrase',
  }));
  return [...CANONICAL_TEST_CASES, ...custom];
}

// ── Step 2: ParseEvalAgent ────────────────────────────────────────────────────

async function evalPhrase(
  testCase: NLTestCase,
  opts: { token?: string; requestId?: string }
): Promise<ParseEvalResult> {
  try {
    const result = await extractionRun({ text: testCase.phrase, taskId: 'timer-parse' }, opts);

    // Extract field names from the extraction result
    const parsedFields: string[] = [];
    for (const item of result.extractions) {
      const itemRecord = item as unknown as Record<string, unknown>;
      const type = String(itemRecord['type'] ?? itemRecord['entity'] ?? '').toLowerCase();
      if (type) parsedFields.push(type);
      // Also check properties for known timer fields
      const props = itemRecord['properties'] as Record<string, unknown> | undefined;
      if (props) {
        for (const key of ['duration', 'targetTime', 'timerType', 'recurrence', 'label']) {
          if (key in props && !parsedFields.includes(key)) parsedFields.push(key);
        }
      }
    }

    const missingFields = testCase.expectedFields.filter(
      f => !parsedFields.some(p => p.includes(f.toLowerCase()))
    );
    const outcome: EvalOutcome =
      missingFields.length === 0
        ? 'pass'
        : missingFields.length < testCase.expectedFields.length
          ? 'partial'
          : 'fail';

    return {
      phrase: testCase.phrase,
      description: testCase.description,
      expectedFields: testCase.expectedFields,
      parsedFields,
      missingFields,
      outcome,
      rawExtraction: result.extractions,
    };
  } catch (err) {
    return {
      phrase: testCase.phrase,
      description: testCase.description,
      expectedFields: testCase.expectedFields,
      parsedFields: [],
      missingFields: testCase.expectedFields,
      outcome: 'error',
      errorMessage: err instanceof Error ? err.message : String(err),
      rawExtraction: null,
    };
  }
}

// ── Step 3: RegressionReportAgent ─────────────────────────────────────────────

function buildEvalReport(runId: string, results: ParseEvalResult[]): NLParserEvalReport {
  const passed = results.filter(r => r.outcome === 'pass').length;
  const partial = results.filter(r => r.outcome === 'partial').length;
  const failed = results.filter(r => r.outcome === 'fail').length;
  const errored = results.filter(r => r.outcome === 'error').length;
  const passRate = results.length > 0 ? passed / results.length : 0;

  const regressions = results
    .filter(r => r.outcome === 'fail' || r.outcome === 'error')
    .map(r => `"${r.phrase}" — missing: ${r.missingFields.join(', ') || r.errorMessage}`);

  const summary =
    results.length === 0
      ? 'No test cases evaluated.'
      : `${passed}/${results.length} passed (${(passRate * 100).toFixed(0)}%). ${partial} partial, ${failed} failed, ${errored} errored.${regressions.length > 0 ? ` Regressions: ${regressions.length}.` : ' No regressions detected.'}`;

  return {
    runId,
    productId: 'chronomind',
    totalCases: results.length,
    passed,
    partial,
    failed,
    errored,
    passRate,
    results,
    regressions,
    summary,
    generatedAt: new Date().toISOString(),
  };
}

// ── Pipeline runner ────────────────────────────────────────────────────────────

export async function runNLParserEvalPipeline(
  customPhrases: string[],
  req: McpToolRequest
): Promise<NLParserEvalReport> {
  const runId = randomUUID();
  const opts = { token: req.headers.authorization?.slice(7), requestId: req.id };
  let currentStep: { stepName: 'sample' | 'eval' | 'report'; order: number } | undefined;

  await safeTrack(() =>
    trackRunStarted({
      runId,
      productId: 'chronomind',
      name: 'nl-parser-eval',
      requestId: req.id,
      token: opts.token,
      input: { customPhraseCount: customPhrases.length },
    })
  );

  try {
    currentStep = { stepName: 'sample', order: 1 };
    const sampleStep = currentStep;
    await safeTrack(() =>
      trackStepStarted({
        runId,
        productId: 'chronomind',
        stepName: sampleStep.stepName,
        order: sampleStep.order,
        token: opts.token,
        requestId: req.id,
        input: { customPhraseCount: customPhrases.length },
      })
    );

    req.log.info(
      { runId, stepId: 'sample', customCount: customPhrases.length },
      'PhraseSamplerAgent start'
    );
    const testCases = assemblePhrases(customPhrases);
    await safeTrack(() =>
      trackStepCompleted({
        runId,
        productId: 'chronomind',
        stepName: sampleStep.stepName,
        order: sampleStep.order,
        token: opts.token,
        requestId: req.id,
        output: { totalCases: testCases.length },
      })
    );
    req.log.info(
      { runId, stepId: 'sample', totalCases: testCases.length },
      'PhraseSamplerAgent done'
    );

    currentStep = { stepName: 'eval', order: 2 };
    const evalStep = currentStep;
    await safeTrack(() =>
      trackStepStarted({
        runId,
        productId: 'chronomind',
        stepName: evalStep.stepName,
        order: evalStep.order,
        token: opts.token,
        requestId: req.id,
        input: { totalCases: testCases.length },
      })
    );
    req.log.info({ runId, stepId: 'eval', totalCases: testCases.length }, 'ParseEvalAgent start');
    const results: ParseEvalResult[] = [];
    for (const testCase of testCases) {
      const result = await evalPhrase(testCase, opts);
      results.push(result);
    }
    const passed = results.filter(r => r.outcome === 'pass').length;
    const estimatedTokens = testCases.reduce(
      (sum, item) => sum + Math.ceil(item.phrase.length / 4),
      0
    );
    await safeTrack(() =>
      trackStepCompleted({
        runId,
        productId: 'chronomind',
        stepName: evalStep.stepName,
        order: evalStep.order,
        token: opts.token,
        requestId: req.id,
        output: { passed, total: results.length },
      })
    );
    await recordBudgetSpend(
      {
        productId: 'chronomind',
        runId,
        source: 'a2a.nl_parser_eval',
        costUsd: Number((estimatedTokens * 0.000002).toFixed(6)),
        tokensUsed: estimatedTokens,
      },
      opts
    );
    req.log.info({ runId, stepId: 'eval', passed, total: results.length }, 'ParseEvalAgent done');

    currentStep = { stepName: 'report', order: 3 };
    const reportStep = currentStep;
    await safeTrack(() =>
      trackStepStarted({
        runId,
        productId: 'chronomind',
        stepName: reportStep.stepName,
        order: reportStep.order,
        token: opts.token,
        requestId: req.id,
      })
    );
    req.log.info({ runId, stepId: 'report' }, 'RegressionReportAgent start');
    const report = buildEvalReport(runId, results);
    await safeTrack(() =>
      trackStepCompleted({
        runId,
        productId: 'chronomind',
        stepName: reportStep.stepName,
        order: reportStep.order,
        token: opts.token,
        requestId: req.id,
        output: { passRate: report.passRate, regressions: report.regressions.length },
      })
    );
    await safeTrack(() =>
      trackRunCompleted({
        runId,
        productId: 'chronomind',
        name: 'nl-parser-eval',
        requestId: req.id,
        token: opts.token,
        output: { passRate: report.passRate, regressions: report.regressions.length },
      })
    );
    if (report.regressions.length > 0 || report.passRate < 0.8) {
      await createSupportCaseForRun(
        {
          productId: 'chronomind',
          runId,
          title: 'NL parser regression detected',
          description: report.summary,
          priority: report.passRate < 0.5 ? 'critical' : 'high',
          tags: ['a2a', 'nl-parser', 'evaluation'],
        },
        opts
      );
    }
    req.log.info(
      {
        runId,
        stepId: 'report',
        passRate: report.passRate,
        regressions: report.regressions.length,
      },
      'RegressionReportAgent done'
    );

    return report;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    if (currentStep) {
      const failedStep = currentStep;
      await safeTrack(() =>
        trackStepFailed({
          runId,
          productId: 'chronomind',
          stepName: failedStep.stepName,
          order: failedStep.order,
          token: opts.token,
          requestId: req.id,
          error: message,
        })
      );
    }
    await safeTrack(() =>
      trackRunFailed({
        runId,
        productId: 'chronomind',
        name: 'nl-parser-eval',
        requestId: req.id,
        token: opts.token,
        error: message,
      })
    );
    throw error;
  }
}

async function safeTrack(fn: () => Promise<void>): Promise<void> {
  try {
    await fn();
  } catch {
    // Tracking must never fail the pipeline itself.
  }
}

// ── MCP tool registration ─────────────────────────────────────────────────────

registerTool({
  name: 'chronomind.nlParser.eval',
  description:
    'A2A pipeline: evaluates the ChronoMind natural-language timer parser by submitting a canonical phrase test suite (plus optional custom phrases) to the extraction service timer-parse task, validates parsed fields, and produces a pass/fail regression scorecard. Run after nl-parser changes to detect regressions. Requires admin role.',
  requiredRole: 'admin',
  inputSchema: z.object({
    customPhrases: z
      .array(z.string().min(1))
      .default([])
      .describe('Additional phrases to test beyond the built-in canonical suite'),
  }),
  async execute(args, req) {
    return runNLParserEvalPipeline(args.customPhrases, req);
  },
});
