// lib/process/stages/agent-run.js — handler for agent_run stages.
//
// Runs the configured agent (looked up from the agent template registered in
// `lib/process/seeds/agent-templates.js`) against the case's intake payload +
// accumulated context. The agent is expected to return strict JSON matching
// the stage's `output_schema`. Result outcome flows back to the engine.
//
// Demo mode: when env MADARX_PROCESS_FAKE_AGENT=1, the handler returns a
// canned response so the demo path runs end-to-end without an LLM round-trip.
// The flag must be set explicitly — implicit "test env" detection (NODE_ENV,
// VILLA515_DATA_DIR) was removed in Phase 5 because it silently hid real
// integration regressions in tests that thought they were exercising the
// real engine.

import { runAgentEngine } from '../../agent-engines.js';
import { getDb, nowIso, newId } from '../../db.js';
import { minesValuationRun } from '../../tools/adapters/mines/valuation-run.js';
import { createAnalysisArtifact } from '../artifacts.js';
import { safeJson } from '../util.js';

function extractJsonFromText(text) {
  if (!text || typeof text !== 'string') return null;
  // Look for the first balanced JSON object.
  const start = text.indexOf('{');
  if (start === -1) return null;
  let depth = 0;
  for (let i = start; i < text.length; i++) {
    const ch = text[i];
    if (ch === '{') depth++;
    else if (ch === '}') {
      depth--;
      if (depth === 0) {
        const candidate = text.slice(start, i + 1);
        try { return JSON.parse(candidate); } catch { return null; }
      }
    }
  }
  return null;
}

// Synthetic outputs used in demo / test mode. Keyed by the agent template slug
// configured on the stage. Some workflow smoke tests deliberately exercise
// real deterministic adapters from this path so the process runner validates
// artifact wiring without making an LLM round-trip.
async function syntheticOutputFor(slug, intake, priorContext) {
  if (slug === 'completeness-agent') {
    const missing = [];
    if (!intake?.applicant_full_name) missing.push('applicant_full_name');
    if (!intake?.passport_number) missing.push('passport_number');
    if (!intake?.email) missing.push('email');
    if (!intake?.proposed_company_name) missing.push('proposed_company_name');
    if (!intake?.business_activity) missing.push('business_activity');
    if (!intake?.shareholders || !Array.isArray(intake.shareholders) || intake.shareholders.length === 0) {
      missing.push('shareholders');
    }
    const outcome = missing.length === 0 ? 'pass' : 'needs_info';
    return {
      outcome,
      missing_fields: missing,
      warnings: [],
      summary: outcome === 'pass'
        ? 'Intake is complete. All required fields present.'
        : `Intake is missing required fields: ${missing.join(', ')}.`,
    };
  }
  if (slug === 'kyc-risk-verifier') {
    // Demo-mode risk score: small deterministic function of the proposed
    // company name length so the same intake always produces the same score.
    const name = String(intake?.proposed_company_name || '').toLowerCase();
    const score = Math.min(0.7, Math.max(0.05, (name.length % 7) * 0.05 + 0.1));
    const action = score < 0.3 ? 'auto_pass' : score < 0.6 ? 'manual_review' : 'reject';
    return {
      risk_score: Number(score.toFixed(2)),
      flags: score >= 0.3 ? ['demo:moderate-risk-name-length'] : [],
      sanctioned_list_hits: [],
      pep_check: 'clear',
      rationale:
        'Demo mode: risk score is a deterministic function of the intake payload — no external KYC services were contacted.',
      recommended_action: action,
    };
  }
  if (slug === 'regulatory-drafter') {
    return {
      draft_kind: 'memorandum_of_association',
      summary: `Draft MOA for ${intake?.proposed_company_name || 'the entity'} (demo).`,
      sections: ['name', 'objects', 'share-capital', 'shareholders', 'directors'],
    };
  }
  if (slug === 'mines-valuation-analyst') {
    const prices = safeJson(intake?.commodity_price_override, intake?.commodity_price_override || {});
    const result = await minesValuationRun({
      project_id: intake?.project_id,
      force_tier: intake?.force_tier === 'auto' ? null : intake?.force_tier,
      commodity_prices_json: prices,
    });
    const artifact = result?.artifact_id
      ? getDb().prepare('SELECT kind, title FROM artifacts WHERE id = ?').get(result.artifact_id)
      : null;
    return {
      ...result,
      outcome: result?.status === 'ok' ? 'pass' : 'fail',
      artifact_kind: artifact?.kind || null,
      artifact_title: artifact?.title || null,
    };
  }
  return { outcome: 'pass', summary: `No synthetic output configured for ${slug} — defaulting to pass.` };
}

function isDemoMode() {
  return process.env.MADARX_PROCESS_FAKE_AGENT === '1';
}

// Find an installed agent for this template scoped to the case's project.
// Returns null if no active install exists in this project — never falls
// back to cross-project agents (a Personal-project case must not pick up a
// Work-project agent silently). Callers handle the null by failing the stage
// with an "agent_not_installed" error.
function findAgentForTemplate(slug, project_id) {
  if (!slug || !project_id) return null;
  const db = getDb();
  return db.prepare(`
    SELECT a.* FROM agent_template_installations i
    JOIN agents a ON a.id = i.agent_id
    WHERE i.template_slug = ? AND i.project_id = ? AND i.status = 'active'
    ORDER BY i.installed_at DESC LIMIT 1
  `).get(slug, project_id) || null;
}

function recordAgentRunRow({ agent_id, case_id, stage_slug, output, error = null }) {
  const db = getDb();
  const id = newId('agentrun');
  const now = nowIso();
  const status = error ? 'failed' : 'complete';
  try {
    db.prepare(`
      INSERT INTO agent_runs (id, agent_id, trigger_source, trigger_detail, status, started_at, finished_at, error, result_json, created_at, updated_at)
      VALUES (?, ?, 'process_stage', ?, ?, ?, ?, ?, ?, ?, ?)
    `).run(
      id,
      agent_id || 'process-engine',
      `case:${case_id}/stage:${stage_slug}`,
      status,
      now,
      now,
      error,
      output ? JSON.stringify(output) : null,
      now,
      now,
    );
  } catch {
    return null;
  }
  return id;
}

export async function executeStage({ case: c, stage }) {
  const config = stage.config || {};
  const slug = config.agent_template_slug;
  const intake = c.intake_payload || {};
  const priorContext = c.context || {};

  // Demo / test path — synthesize structured output.
  if (isDemoMode()) {
    const output = await syntheticOutputFor(slug, intake, priorContext);
    const agentRunId = recordAgentRunRow({
      agent_id: findAgentForTemplate(slug, c.project_id)?.id || null,
      case_id: c.id,
      stage_slug: stage.slug,
      output,
    });
    return attachAnalysisArtifact(
      outcomeFromOutput({ slug, stage, output, agent_run_id: agentRunId }),
      { caseRow: c, stage, output, agentSlug: slug }
    );
  }

  // Real path — locate the agent installation and call the LLM engine.
  const agent = findAgentForTemplate(slug, c.project_id);
  if (!agent) {
    return {
      kind: 'fail',
      error: `agent template "${slug}" is not installed in any project`,
      output: { error: 'agent_not_installed', agent_template_slug: slug },
    };
  }

  const systemPrompt = config.prompt ||
    'You are a process-stage verifier. Return strict JSON.';
  const userMessage = JSON.stringify({
    intake,
    prior_stages: priorContext,
  });

  let raw;
  try {
    raw = await runAgentEngine({
      agent,
      systemPrompt,
      userMessage,
      maxTokens: 2048,
    });
  } catch (err) {
    return {
      kind: 'fail',
      error: `agent engine failed: ${err?.message || err}`,
      output: { error: String(err?.message || err) },
    };
  }

  const parsed = extractJsonFromText(raw?.text || raw?.content || raw || '');
  if (!parsed) {
    return {
      kind: 'fail',
      error: 'agent did not return JSON',
      output: { error: 'invalid_agent_output', raw: String(raw).slice(0, 2000) },
    };
  }
  const agentRunId = recordAgentRunRow({
    agent_id: agent.id,
    case_id: c.id,
    stage_slug: stage.slug,
    output: parsed,
  });
  return attachAnalysisArtifact(
    outcomeFromOutput({ slug, stage, output: parsed, agent_run_id: agentRunId }),
    { caseRow: c, stage, output: parsed, agentSlug: slug }
  );
}

// Side-effect helper that generates a presentation HTML artifact for the
// agent's output and appends its id to the outcome envelope. Runs on both
// pass and fail outcomes so a failed completeness analysis is still surfaced
// as an artifact for the operator to inspect.
function attachAnalysisArtifact(outcome, { caseRow, stage, output, agentSlug }) {
  if (!outcome || !output) return outcome;
  const artifactId = createAnalysisArtifact({ caseRow, stage, output, agentSlug });
  if (!artifactId) return outcome;
  const ids = Array.isArray(outcome.artifact_ids) ? [...outcome.artifact_ids] : [];
  ids.push(artifactId);
  return { ...outcome, artifact_ids: ids };
}

function outcomeFromOutput({ slug, stage, output, agent_run_id }) {
  const config = stage.config || {};
  const outcomeMap = config.on_outcome || {};

  // Completeness agent: outcome literal.
  if (slug === 'completeness-agent') {
    if (output?.outcome === 'pass') return { kind: 'pass', output, agent_run_id };
    return { kind: 'fail', output, agent_run_id, error: 'incomplete_intake' };
  }

  // Regulatory drafter: produces an advisory MOA draft. Any non-null output
  // counts as pass — there is no business-rules pass/fail for a drafter.
  if (slug === 'regulatory-drafter') {
    if (!output || typeof output !== 'object') {
      return { kind: 'fail', output, agent_run_id, error: 'drafter_missing_output' };
    }
    return { kind: 'pass', output, agent_run_id };
  }

  // KYC/risk agent: only explicit auto_pass / manual_review advance. Anything
  // else (reject, missing field, prompt-injected gibberish) fails the stage —
  // the engine cannot trust an LLM's silence here.
  if (slug === 'kyc-risk-verifier') {
    const action = output?.recommended_action;
    if (action === 'auto_pass' || action === 'manual_review') {
      return { kind: 'pass', output, agent_run_id };
    }
    return {
      kind: 'fail',
      output,
      agent_run_id,
      error: action === 'reject' ? 'kyc_reject' : 'kyc_unrecognized_action',
    };
  }

  // Mines valuation analyst: deterministic adapter-backed output. The
  // valuation adapter itself owns DCF gate protection and returns a formal
  // valuation or readiness artifact id; keep that artifact linked to the
  // process execution so package/artifact surfaces can expose it directly.
  if (slug === 'mines-valuation-analyst') {
    if (output?.status === 'ok' && output?.artifact_id) {
      return { kind: 'pass', output, agent_run_id, artifact_ids: [output.artifact_id] };
    }
    return {
      kind: 'fail',
      output,
      agent_run_id,
      error: output?.error || 'mines_valuation_failed',
    };
  }

  // Generic: only explicit "pass" advances. Anything else (missing field,
  // unknown value, prompt-injected output) fails the stage. Don't trust the
  // LLM's silence — default to safe failure.
  if (output?.outcome === 'pass') return { kind: 'pass', output, agent_run_id };
  return {
    kind: 'fail',
    output,
    agent_run_id,
    error: output?.error || output?.outcome ? `agent_returned_${output?.outcome || 'unknown'}` : 'agent_missing_outcome',
  };
}

export const STAGE_KIND = 'agent_run';
