'use strict';

const { randomUUID } = require('node:crypto');
const codingReview = require('../coding-review');
const { recordReview } = require('../runtime/execution-trace');

const SCORE_KEYS = ['correctness', 'completeness', 'tests', 'maintainability', 'security'];
const VERDICTS = new Set(['pass', 'revise', 'block']);
const SEVERITY_RANK = { minor: 1, major: 2, blocker: 3 };

function buildCodingReviewPrompt(input = {}) {
  const changedFiles = Array.isArray(input.changedFiles) ? input.changedFiles : [];
  const requiredContext = [
    input.planSummary ? `## Plan Summary\n${input.planSummary}` : '',
    input.transcriptSummary ? `## Worker Transcript Summary\n${input.transcriptSummary}` : '',
    input.testOutput ? `## Test Output\n\`\`\`\n${input.testOutput}\n\`\`\`` : '',
  ].filter(Boolean).join('\n\n');

  return `You are a senior coding reviewer. Evaluate this work for correctness, completeness, tests, maintainability, and security.

## Original Request
${input.request || ''}

## Task Intent
${input.taskIntent || input.intent || ''}

## Changed Files
${changedFiles.length > 0 ? changedFiles.map(file => `- ${file}`).join('\n') : '- none reported'}

${requiredContext ? `${requiredContext}\n\n` : ''}## Diff
\`\`\`diff
${input.diff || ''}
\`\`\`

Return ONLY valid JSON:
{
  "verdict": "pass|revise|block",
  "severity": "minor|major|blocker",
  "issues": [{"severity": "minor|major|blocker", "file": "optional path", "description": "specific issue"}],
  "requiredTests": ["specific tests still needed"],
  "riskAreas": ["specific risk areas"],
  "confidence": 0.0,
  "scores": {
    "correctness": 1,
    "completeness": 1,
    "tests": 1,
    "maintainability": 1,
    "security": 1
  },
  "summary": "one-line summary"
}`;
}

function extractJsonObject(text) {
  if (!text || typeof text !== 'string') return null;
  const trimmed = text.trim();
  try {
    return JSON.parse(trimmed);
  } catch {}

  const fence = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
  if (fence) {
    try {
      return JSON.parse(fence[1].trim());
    } catch {}
  }

  let start = -1;
  let depth = 0;
  let inString = false;
  let escaped = false;
  for (let i = 0; i < trimmed.length; i += 1) {
    const ch = trimmed[i];
    if (escaped) {
      escaped = false;
      continue;
    }
    if (ch === '\\') {
      escaped = true;
      continue;
    }
    if (ch === '"') {
      inString = !inString;
      continue;
    }
    if (inString) continue;
    if (ch === '{') {
      if (depth === 0) start = i;
      depth += 1;
    } else if (ch === '}') {
      depth -= 1;
      if (depth === 0 && start >= 0) {
        try {
          return JSON.parse(trimmed.slice(start, i + 1));
        } catch {
          start = -1;
        }
      }
    }
  }
  return null;
}

function normalizeSeverity(value) {
  const raw = String(value || '').toLowerCase();
  if (raw === 'block' || raw === 'critical' || raw === 'blocker') return 'blocker';
  if (raw === 'major' || raw === 'high') return 'major';
  return 'minor';
}

function normalizeVerdict(value, severity, pass) {
  const raw = String(value || '').toLowerCase();
  if (VERDICTS.has(raw)) return raw;
  if (pass === true) return 'pass';
  if (severity === 'blocker') return 'block';
  return 'revise';
}

function clampConfidence(value, fallback) {
  const n = Number(value);
  if (!Number.isFinite(n)) return fallback;
  return Math.max(0, Math.min(1, n));
}

function normalizeScore(value) {
  const n = Number(value);
  if (!Number.isFinite(n)) return null;
  return Math.max(1, Math.min(5, n));
}

function normalizeIssues(issues, fallbackSeverity) {
  if (!Array.isArray(issues)) return [];
  return issues.map(issue => {
    if (typeof issue === 'string') {
      return { severity: fallbackSeverity, file: '', description: issue };
    }
    if (issue && typeof issue === 'object') {
      return {
        severity: normalizeSeverity(issue.severity || fallbackSeverity),
        file: issue.file || issue.path || '',
        description: issue.description || issue.issue || issue.message || '',
      };
    }
    return null;
  }).filter(issue => issue && issue.description);
}

function normalizeStringArray(values) {
  if (!Array.isArray(values)) return [];
  return [...new Set(values.map(value => String(value || '').trim()).filter(Boolean))];
}

function parseCodingReview(input, meta = {}) {
  const parsed = typeof input === 'string' ? extractJsonObject(input) : input;
  if (!parsed || typeof parsed !== 'object') {
    return {
      verdict: 'block',
      pass: false,
      severity: 'blocker',
      issues: [{ severity: 'blocker', file: '', description: 'Failed to parse coding review verdict' }],
      requiredTests: [],
      riskAreas: [],
      confidence: 0,
      scores: Object.fromEntries(SCORE_KEYS.map(key => [key, null])),
      summary: '',
      parseError: true,
      reviewer: meta.reviewer || null,
    };
  }

  const severity = normalizeSeverity(parsed.severity);
  const verdict = normalizeVerdict(parsed.verdict, severity, parsed.pass);
  const scores = {};
  for (const key of SCORE_KEYS) scores[key] = normalizeScore(parsed.scores?.[key]);

  return {
    verdict,
    pass: verdict === 'pass',
    severity,
    issues: normalizeIssues(parsed.issues, severity),
    requiredTests: normalizeStringArray(parsed.requiredTests || parsed.required_tests),
    riskAreas: normalizeStringArray(parsed.riskAreas || parsed.risk_areas),
    confidence: clampConfidence(parsed.confidence, verdict === 'pass' ? 0.7 : 0.55),
    scores,
    summary: parsed.summary || '',
    parseError: false,
    reviewer: meta.reviewer || parsed.reviewer || null,
  };
}

function strongestSeverity(reviews) {
  return reviews.reduce((max, review) => (
    SEVERITY_RANK[review.severity] > SEVERITY_RANK[max] ? review.severity : max
  ), 'minor');
}

function average(values) {
  const nums = values.filter(value => Number.isFinite(value));
  if (nums.length === 0) return null;
  return nums.reduce((sum, value) => sum + value, 0) / nums.length;
}

function aggregateCodingReviews(reviews = []) {
  const normalized = reviews.map(review => parseCodingReview(review, { reviewer: review.reviewer }));
  if (normalized.length === 0) {
    const fallback = parseCodingReview('');
    return {
      verdict: 'block',
      pass: false,
      severity: 'blocker',
      issues: fallback.issues,
      requiredTests: [],
      riskAreas: [],
      confidence: 0,
      scores: Object.fromEntries(SCORE_KEYS.map(key => [key, null])),
      agreement: 0,
      reviewerCounts: { pass: 0, revise: 0, block: 0 },
    };
  }

  const reviewerCounts = { pass: 0, revise: 0, block: 0 };
  for (const review of normalized) reviewerCounts[review.verdict] += 1;

  const confidence = average(normalized.map(review => review.confidence)) || 0;
  let verdict = 'pass';
  if (normalized.some(review => review.verdict === 'block' || review.severity === 'blocker')) {
    verdict = 'block';
  } else if (normalized.some(review => review.verdict === 'revise') || confidence < 0.6) {
    verdict = 'revise';
  }

  const scores = {};
  for (const key of SCORE_KEYS) {
    scores[key] = average(normalized.map(review => review.scores?.[key]));
  }

  const issues = normalized.flatMap(review => review.issues);
  const requiredTests = normalizeStringArray(normalized.flatMap(review => review.requiredTests));
  const riskAreas = normalizeStringArray(normalized.flatMap(review => review.riskAreas));
  const agreeing = normalized.filter(review => review.verdict === verdict).length;

  return {
    verdict,
    pass: verdict === 'pass',
    severity: verdict === 'block' ? 'blocker' : strongestSeverity(normalized),
    issues,
    requiredTests,
    riskAreas,
    confidence,
    scores,
    agreement: agreeing / normalized.length,
    reviewerCounts,
  };
}

function qualityFromCodingAggregate(aggregate = {}) {
  const scoreValues = Object.values(aggregate.scores || {}).filter(Number.isFinite);
  const scoreAverage = scoreValues.length > 0
    ? scoreValues.reduce((sum, value) => sum + value, 0) / scoreValues.length / 5
    : 0.7;
  const verdictPenalty = aggregate.verdict === 'block' ? 0.55 : aggregate.verdict === 'revise' ? 0.20 : 0;
  const confidence = Number.isFinite(aggregate.confidence) ? aggregate.confidence : 0.7;
  const blended = scoreAverage * 0.75 + confidence * 0.25 - verdictPenalty;
  return Math.max(0, Math.min(1, Math.round(blended * 1000) / 1000));
}

function recordCodingQuorumLearning(result = {}, options = {}, brain = null) {
  if (!brain?.insertAgentRunnerEvaluation) return null;
  const builder = options.builder || {};
  const runnerId = builder.runnerId || options.workerRunnerId || options.runnerId;
  if (!runnerId) return null;
  const trace = options.trace || null;
  const changedFiles = Array.isArray(options.changedFiles) ? options.changedFiles : [];
  const aggregate = result.aggregate || {};
  return brain.insertAgentRunnerEvaluation({
    runnerId,
    providerType: builder.providerType || options.workerProvider || null,
    modelId: builder.modelId || options.workerModel || null,
    taskType: options.taskType || 'coding:subtask',
    phase: options.phase || 'coding:review-gated',
    qualityScore: qualityFromCodingAggregate(aggregate),
    latencyMs: options.latencyMs ?? trace?.latencyMs ?? null,
    testsPassed: options.testsPassed ?? (result.requiredTests?.length === 0 ? true : null),
    reviewVerdict: result.verdict || aggregate.verdict || null,
    retries: Math.max(0, (trace?.attempts?.length || 1) - 1),
    filesChanged: changedFiles.length,
    costEstimate: options.costEstimate ?? null,
    wasSelected: true,
  });
}

function selectCodingReviewers(options = {}) {
  const quorumSize = Math.max(1, options.quorumSize || options.reviewQuorumSize || 2);
  const reviewers = Array.isArray(options.reviewers) ? options.reviewers.filter(Boolean) : [];
  if (options.reviewer) return [options.reviewer];
  if (reviewers.length === 0) return [codingReview.selectReviewer(options)];

  const workerProvider = options.workerProvider || null;
  const different = reviewers.filter(reviewer => reviewer.providerType && reviewer.providerType !== workerProvider);
  const same = reviewers.filter(reviewer => !different.includes(reviewer));
  return [...different, ...same].slice(0, quorumSize);
}

function issueToString(issue) {
  const file = issue.file ? `${issue.file}: ` : '';
  return `${file}${issue.description}`;
}

async function runCodingQuorum(options = {}, deps = {}) {
  const quorumId = randomUUID();
  const prompt = buildCodingReviewPrompt(options);
  const reviewers = selectCodingReviewers(options);
  const runReview = deps.runReview || codingReview.runReview;

  const runOne = async (reviewer) => {
    const verdict = await runReview(prompt, {
      ...options,
      reviewer,
    });
    return parseCodingReview(verdict, { reviewer: verdict.reviewer || reviewer });
  };

  const settled = await Promise.allSettled(reviewers.map(runOne));
  const reviews = settled.map((result, index) => {
    if (result.status === 'fulfilled') return result.value;
    return parseCodingReview('', { reviewer: reviewers[index] });
  });
  const aggregate = aggregateCodingReviews(reviews);

  const result = {
    quorumId,
    prompt,
    reviewers,
    reviews,
    aggregate,
    verdict: aggregate.verdict,
    pass: aggregate.pass,
    severity: aggregate.severity,
    issues: aggregate.issues.map(issueToString),
    requiredTests: aggregate.requiredTests,
    riskAreas: aggregate.riskAreas,
    confidence: aggregate.confidence,
    agreement: aggregate.agreement,
    learningSignals: {
      quorumId,
      reviewerCount: reviews.length,
      verdict: aggregate.verdict,
      confidence: aggregate.confidence,
      agreement: aggregate.agreement,
      issueCount: aggregate.issues.length,
      requiredTestCount: aggregate.requiredTests.length,
    },
  };
  recordReview(options.trace, result);
  const persistedEvaluationId = recordCodingQuorumLearning(result, options, options.brain || deps.brain);
  if (persistedEvaluationId) {
    result.learningSignals.persistedEvaluationId = persistedEvaluationId;
    result.learningSignals.builderRunnerId = options.builder?.runnerId || options.workerRunnerId || options.runnerId || null;
    result.learningSignals.qualityScore = qualityFromCodingAggregate(aggregate);
  }
  return result;
}

module.exports = {
  buildCodingReviewPrompt,
  parseCodingReview,
  aggregateCodingReviews,
  qualityFromCodingAggregate,
  recordCodingQuorumLearning,
  selectCodingReviewers,
  runCodingQuorum,
};
