/**
 * MemoryCurationAgent — A2A pipeline for JarvisJr agent memory hygiene.
 *
 * Agent roster (3 steps):
 *   1. AgentInventoryAgent   — list all agents owned by a user
 *   2. MemoryScanAgent       — per agent: list memories below importance threshold
 *   3. CurationReportAgent   — prune stale entries + return per-agent curation report
 *
 * MCP tools:
 *   jarvis.memory.curate(importanceThreshold?, dryRun?) — run pipeline across all agents
 */

import { randomUUID } from 'node:crypto';
import { z } from 'zod';
import { registerTool } from '../tools/registry.js';
import type { McpToolRequest } from '../tools/types.js';
import {
  jarvisAgentsList,
  jarvisMemoryList,
  jarvisMemoryPrune,
  type JarvisMemoryDoc,
} from '../../lib/jarvis-client.js';
import { config } from '../../lib/config.js';

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

interface AgentMemoryScan {
  agentId: string;
  agentName: string;
  totalMemories: number;
  staleMemories: JarvisMemoryDoc[];
  expiredMemories: JarvisMemoryDoc[];
}

interface CurationAction {
  agentId: string;
  agentName: string;
  staleCount: number;
  expiredCount: number;
  pruned: number;
  skipped: boolean;
}

export interface MemoryCurationReport {
  runId: string;
  productId: 'jarvisjr';
  importanceThreshold: number;
  dryRun: boolean;
  agentsScanned: number;
  totalStaleFound: number;
  totalExpiredFound: number;
  totalPruned: number;
  perAgent: CurationAction[];
  summary: string;
  generatedAt: string;
}

// ── Step 1: AgentInventoryAgent ────────────────────────────────────────────────

async function listAgents(opts: {
  token?: string;
  requestId?: string;
}): Promise<Array<{ id: string; name: string }>> {
  try {
    const result = await jarvisAgentsList({ limit: config.QUERY_MAX_LIMIT }, opts);
    return result.agents.map(a => ({ id: a.id, name: a.name }));
  } catch {
    return [];
  }
}

// ── Step 2: MemoryScanAgent ────────────────────────────────────────────────────

async function scanAgentMemory(
  agentId: string,
  agentName: string,
  importanceThreshold: number,
  opts: { token?: string; requestId?: string }
): Promise<AgentMemoryScan> {
  try {
    const result = await jarvisMemoryList(agentId, { limit: config.QUERY_MAX_LIMIT }, opts);
    const now = new Date();

    const staleMemories = result.memories.filter(m => m.importance < importanceThreshold);
    const expiredMemories = result.memories.filter(m => m.expiresAt && new Date(m.expiresAt) < now);

    return {
      agentId,
      agentName,
      totalMemories: result.total,
      staleMemories,
      expiredMemories,
    };
  } catch {
    return { agentId, agentName, totalMemories: 0, staleMemories: [], expiredMemories: [] };
  }
}

// ── Step 3: CurationReportAgent ────────────────────────────────────────────────

async function curateAgentMemory(
  scan: AgentMemoryScan,
  dryRun: boolean,
  opts: { token?: string; requestId?: string }
): Promise<CurationAction> {
  const staleCount = scan.staleMemories.length;
  const expiredCount = scan.expiredMemories.length;

  if (dryRun || (staleCount === 0 && expiredCount === 0)) {
    return {
      agentId: scan.agentId,
      agentName: scan.agentName,
      staleCount,
      expiredCount,
      pruned: 0,
      skipped: dryRun || (staleCount === 0 && expiredCount === 0),
    };
  }

  let pruned = 0;
  try {
    const result = await jarvisMemoryPrune(scan.agentId, opts);
    pruned = result.pruned;
  } catch {
    // best-effort
  }

  return {
    agentId: scan.agentId,
    agentName: scan.agentName,
    staleCount,
    expiredCount,
    pruned,
    skipped: false,
  };
}

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

async function runMemoryCurationPipeline(
  importanceThreshold: number,
  dryRun: boolean,
  req: McpToolRequest
): Promise<MemoryCurationReport> {
  const runId = randomUUID();
  const opts = { token: req.headers.authorization?.slice(7), requestId: req.id };

  req.log.info(
    { runId, stepId: 'inventory', dryRun, importanceThreshold },
    'AgentInventoryAgent start'
  );
  const agents = await listAgents(opts);
  req.log.info(
    { runId, stepId: 'inventory', agentCount: agents.length },
    'AgentInventoryAgent done'
  );

  req.log.info({ runId, stepId: 'scan', agentCount: agents.length }, 'MemoryScanAgent start');
  const scans: AgentMemoryScan[] = [];
  for (const agent of agents) {
    const scan = await scanAgentMemory(agent.id, agent.name, importanceThreshold, opts);
    scans.push(scan);
  }
  req.log.info(
    { runId, stepId: 'scan', totalStale: scans.reduce((s, x) => s + x.staleMemories.length, 0) },
    'MemoryScanAgent done'
  );

  req.log.info({ runId, stepId: 'curate', dryRun }, 'CurationReportAgent start');
  const actions: CurationAction[] = [];
  for (const scan of scans) {
    const action = await curateAgentMemory(scan, dryRun, opts);
    actions.push(action);
  }

  const totalStaleFound = actions.reduce((s, a) => s + a.staleCount, 0);
  const totalExpiredFound = actions.reduce((s, a) => s + a.expiredCount, 0);
  const totalPruned = actions.reduce((s, a) => s + a.pruned, 0);

  const summary = dryRun
    ? `DRY RUN: ${totalStaleFound} stale + ${totalExpiredFound} expired memories found across ${agents.length} agents. No pruning performed.`
    : totalPruned > 0
      ? `Pruned ${totalPruned} entries across ${actions.filter(a => !a.skipped).length} agents (${totalStaleFound} stale, ${totalExpiredFound} expired found).`
      : `No memories required pruning across ${agents.length} agents.`;

  req.log.info({ runId, stepId: 'curate', totalPruned, summary }, 'CurationReportAgent done');

  return {
    runId,
    productId: 'jarvisjr',
    importanceThreshold,
    dryRun,
    agentsScanned: agents.length,
    totalStaleFound,
    totalExpiredFound,
    totalPruned,
    perAgent: actions,
    summary,
    generatedAt: new Date().toISOString(),
  };
}

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

registerTool({
  name: 'jarvis.memory.curate',
  description:
    'A2A pipeline: scans all JarvisJr coaching agents, identifies memories below an importance threshold or past their expiry date, prunes stale entries, and returns a per-agent hygiene report. Use dryRun=true to preview without pruning. Requires admin role.',
  requiredRole: 'admin',
  inputSchema: z.object({
    importanceThreshold: z.coerce
      .number()
      .min(0)
      .max(1)
      .default(0.3)
      .describe('Memories with importance below this value are considered stale (default 0.3)'),
    dryRun: z
      .boolean()
      .default(false)
      .describe('If true, scan and report without pruning any memories'),
  }),
  async execute(args, req) {
    return runMemoryCurationPipeline(args.importanceThreshold, args.dryRun, req);
  },
});
