// Copyright (c) 2026 defconxt. All rights reserved.
// Licensed under AGPL-3.0 — see LICENSE file for details.

/**
 * NERF Benchmark — Data models.
 */

import { readFileSync } from 'node:fs';
import { basename } from 'node:path';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);

export class BenchmarkConfig {
  constructor({ name, description, level, tags = [], winCondition = 'flag', files = [], path = '.', category = '' }) {
    if (level < 1 || level > 5) throw new Error(`level must be 1-5, got ${level}`);
    if (!['flag', 'question'].includes(winCondition)) throw new Error(`winCondition must be 'flag' or 'question'`);
    this.name = name;
    this.description = description;
    this.level = level;
    this.tags = tags;
    this.winCondition = winCondition;
    this.files = files;
    this.path = path;
    this.category = category;
  }

  get dirName() { return basename(this.path); }

  static fromJsonFile(configPath) {
    const data = JSON.parse(readFileSync(configPath, 'utf8'));
    return new BenchmarkConfig({
      name: data.name,
      description: data.description || '',
      level: parseInt(data.level, 10),
      tags: data.tags || [],
      winCondition: data.win_condition || 'flag',
      files: data.files || [],
      path: configPath.replace(/\/[^/]+$/, ''),
    });
  }

  static fromYamlFile(configPath) {
    let yaml;
    try { yaml = require('yaml'); } catch { throw new Error('yaml package required for benchmark.yaml'); }
    const data = yaml.parse(readFileSync(configPath, 'utf8'));
    let description = '';
    for (const item of data.content || []) {
      if (item?.kind === 'description') { description = item.content || ''; break; }
    }
    if (!description) description = data.description || '';
    return new BenchmarkConfig({
      name: data.name || basename(configPath.replace(/\/[^/]+$/, '')),
      description,
      level: parseInt(data.level || data.difficulty || 1, 10),
      tags: data.tags || [],
      winCondition: data.win_condition || 'flag',
      files: data.files || [],
      path: configPath.replace(/\/[^/]+$/, ''),
    });
  }
}

export class SolverResult {
  constructor(opts = {}) {
    this.flagFound = opts.flagFound ?? false;
    this.flagValue = opts.flagValue ?? '';
    this.answerFound = opts.answerFound ?? false;
    this.answerValue = opts.answerValue ?? '';
    this.durationS = opts.durationS ?? 0;
    this.tokensIn = opts.tokensIn ?? 0;
    this.tokensOut = opts.tokensOut ?? 0;
    this.apiCostUsd = opts.apiCostUsd ?? 0;
    this.toolCalls = opts.toolCalls ?? 0;
    this.steps = opts.steps ?? [];
    this.error = opts.error ?? null;
  }
}

export class BenchmarkResult {
  constructor({ config, solverResult, passed, expectedFlag, actualFlag, targetUrl = '' }) {
    this.config = config;
    this.solverResult = solverResult;
    this.passed = passed;
    this.expectedFlag = expectedFlag;
    this.actualFlag = actualFlag;
    this.targetUrl = targetUrl;
  }
}

export class CompetitorBaseline {
  constructor({ name, overallPct, overallPassed, overallTotal, levelPct = {}, medianCostUsd = 0, medianTimeS = 0, source = '' }) {
    this.name = name;
    this.overallPct = overallPct;
    this.overallPassed = overallPassed;
    this.overallTotal = overallTotal;
    this.levelPct = levelPct;
    this.medianCostUsd = medianCostUsd;
    this.medianTimeS = medianTimeS;
    this.source = source;
  }
}

export class RunReport {
  constructor({ total = 0, passed = 0, failed = 0, skipped = 0, durationS = 0, totalCostUsd = 0, results = [] } = {}) {
    this.total = total;
    this.passed = passed;
    this.failed = failed;
    this.skipped = skipped;
    this.durationS = durationS;
    this.totalCostUsd = totalCostUsd;
    this.results = results;
  }

  get passRate() { return this.total > 0 ? (this.passed / this.total) * 100 : 0; }

  resultsByLevel() {
    const byLevel = {};
    for (const r of this.results) {
      (byLevel[r.config.level] ??= []).push(r);
    }
    return byLevel;
  }

  resultsByTag() {
    const byTag = {};
    for (const r of this.results) {
      for (const tag of r.config.tags) {
        (byTag[tag] ??= []).push(r);
      }
    }
    return byTag;
  }

  passRateByLevel() {
    const rates = {};
    for (const [level, results] of Object.entries(this.resultsByLevel())) {
      const p = results.filter((r) => r.passed).length;
      rates[level] = results.length ? (p / results.length) * 100 : 0;
    }
    return rates;
  }
}
