import fs from 'node:fs';
import path from 'node:path';
import { getDb, nowIso, newId } from './db.js';
import { AGENTS_DIR } from './paths.js';
import { createAgent, setAgentToolPermission } from './runtime.js';
import { PROCESS_AGENT_TEMPLATES } from './process/seeds/agent-templates.js';

export const AGENT_TEMPLATES = [
  ['project-watcher','Project Watcher','project_watcher','claude-code',['gh_repo_read','gh_issue_list','gh_pr_list','web_fetch','file_read'],['investigate','retro'],'Watches repos, issues, PRs, deploys, and produces weekly project pulses.'],
  ['personal-assistant','Personal Assistant','personal_assistant','anthropic',['gmail_messages_list','gmail_message_read','calendar_events_list','whatsapp_send'],[], 'Reads inbox and calendar context, drafts replies, and keeps the operator organized.'],
  ['engineering-lead','Engineering Lead','engineering_lead','claude-code',['gh_repo_read','gh_issue_list','gh_pr_list','terminal_run','file_read','file_write'],['investigate','review','ship','qa'],'Leads implementation, review, QA, and preview deploys with explicit approval gates.'],
  ['marketing-lead','Marketing Lead','marketing_lead','anthropic',['web_search','web_fetch','file_write','create_artifact'],['design-shotgun','impeccable'],'Creates market research, launch copy, positioning, and design briefs.'],
  ['meeting-notes','Meeting Notes Taker','meeting_notes','anthropic',['transcribe_audio','file_write','create_artifact'],[], 'Turns recordings into transcripts, decisions, action items, and follow-ups.'],
  ['bug-triage','Bug Triage Specialist','bug_triage','claude-code',['gh_issue_list','gh_pr_list','file_read'],['investigate'],'Classifies incoming bugs and proposes severity and next actions.'],
  ['phase-runner','Phase Runner','phase_runner','claude-code',['file_read','file_write','terminal_run'],['ship'],'Executes approved implementation phases and reports blockers.'],
  ['code-reviewer','Code Reviewer','code_reviewer','claude-code',['file_read','gh_pr_list'],['review'],'Provides independent diff review before merge.'],
  ['competitor-scout','Competitor Scout','competitor_scout','anthropic',['web_search','web_fetch','create_artifact'],[], 'Tracks competitor movement and summarizes weekly signals.'],
  ['finance-ops','Finance and Ops','finance_ops','anthropic',['file_read','create_artifact'],[], 'Reads finance docs and drafts advisory runway and ops summaries.'],
  ['legal-compliance','Legal and Compliance','legal_compliance','anthropic',['file_read','web_fetch','create_artifact'],[], 'Reviews policy and compliance documents as advisory output only.'],
  ['customer-support','Customer Support','customer_support','anthropic',['gmail_message_read','file_write'],[], 'Drafts customer replies and escalates approval-required sends.'],
  // ── Business Agent Suite roles (P4) ───────────────────────────────────────
  // Each role's external-write capabilities are unlocked by connecting the
  // matching service and granting through the connection grant flow; the
  // template ships only the safe built-in tools so a fresh install is runnable
  // and every spend/send stays approval-gated.
  ['chief-of-staff','Chief of Staff','chief_of_staff','anthropic',['web_search','file_read','file_write','create_artifact','create_workflow_definition'],['report-builder','market-research'],'Translates founder intent into delegated work across the company, runs the weekly cadence, and surfaces decisions. No direct external writes.'],
  ['ads-media-buyer','Ads / Media Buyer','ads_media_buyer','anthropic',['web_search','file_write','create_artifact'],['design-shotgun'],'Plans, builds, and reports on paid campaigns across Meta/Google/TikTok within budget caps. Every campaign publish and budget set is approval-gated.'],
  ['content-social','Content / Social Manager','content_social','anthropic',['web_search','file_write','create_artifact','voice_speak'],['design-shotgun'],'Produces and schedules organic posts, reels, and videos and maintains brand voice. Every publish is approval-gated.'],
  ['sales-bd','Sales / BD','sales_bd','anthropic',['web_search','web_fetch','gmail_messages_list','email_send','create_artifact'],['inbox-triage'],'Builds target lists, enriches leads, runs outreach, and logs CRM activity. Bulk sends and CRM writes are approval-gated.'],
  ['ops-manager','Operations','ops_manager','anthropic',['calendar_events_list','calendar_event_read','file_read','file_write','create_artifact'],['meeting-scheduler','report-builder'],'Keeps the business running — scheduling, internal docs, recurring checklists, vendor coordination. Calendar invites and webhooks are approval-gated.'],
  ['product-pm','Product / Project Manager','product_pm','claude-code',['gh_repo_read','gh_issue_list','gh_issue_create','gh_pr_list','file_read','file_write','create_workflow_definition'],['review'],'Maintains the roadmap, writes specs, tracks delivery, and runs standups. Issue/PR creates respect the GitHub allowlist.'],
  ['web-ecommerce','Web / Ecommerce','web_ecommerce','claude-code',['web_fetch','supabase_rest_select','browser_open','file_read','file_write','create_artifact'],['web-automation'],'Manages site/store content, products, SEO, and deploys. Deploys, store writes, and DB writes are approval-gated.'],
].map(([slug,title,role,engine_default,tools_required,skills,description_long]) => ({
  slug, title, role, engine_default, tools_required, tools_optional: [], skills,
  memory_scopes: ['agent','project'], approval_profile: role === 'personal_assistant' ? 'strict' : 'standard', description_long,
}));

// Tool-Smith: the capability-authoring agent. Defined as a full object (not the
// tuple shorthand) so it can carry a bespoke system_prompt. It authors DECLARATIVE
// capabilities — skills, composite recipes over existing tools, and REST connectors
// — and for genuinely new non-REST integrations it PROPOSES code (propose_tool),
// never executing self-authored code. Every capability it authors is still subject
// to all runtime gates when later used.
AGENT_TEMPLATES.push({
  slug: 'tool-smith',
  title: 'Tool Smith',
  role: 'tool_smith',
  engine_default: 'claude-code',
  tools_required: [
    'create_skill', 'update_skill',
    'create_composite_tool', 'update_composite_tool',
    'create_connector', 'update_connector',
    'propose_tool',
    'web_search', 'web_fetch', 'file_read',
  ],
  tools_optional: [],
  skills: ['plan', 'review'],
  memory_scopes: ['agent', 'project'],
  approval_profile: 'standard',
  description_long: 'Authors reusable capabilities for the marketplace — declarative skills, composite tools chaining existing primitives, and REST API connectors. For non-REST integrations it proposes reviewable code; it never executes self-authored code.',
  system_prompt: [
    'You are Tool Smith, the capability-authoring agent. Your only job is to author',
    'reusable capabilities other agents can use, and publish them to the marketplace.',
    '',
    'Pick the RIGHT authoring path for each request:',
    '  • A recipe chaining tools that ALREADY exist -> author a COMPOSITE TOOL',
    '    (create_composite_tool). Reference only existing primitive tools; the',
    '    composite inherits the MAX risk of its steps and every gate re-fires per',
    '    step. Use this for "do X then Y then summarize" style capabilities.',
    '  • A new REST API (Notion, Linear, Stripe, Airtable, ...) -> author a CONNECTOR',
    '    (create_connector) with a declarative manifest: https base_url, allowed_hosts,',
    '    secret-by-reference auth (NEVER paste a raw key — reference it by name, e.g.',
    '    NOTION_TOKEN), and per-endpoint method/path/risk. Each endpoint becomes a',
    '    callable <slug>.<endpoint> tool the instant the owner supplies the key once.',
    '  • A reusable instruction/prompt recipe an LLM follows with tools it already',
    '    has -> author a SKILL (create_skill).',
    '  • A genuinely new integration that is NOT REST (binary, SDK-only, websockets,',
    '    complex OAuth) -> propose_tool. This stages reviewable code; it is INERT and',
    '    a human must merge + restart. You never execute self-authored code.',
    '',
    'Hard rules: never put a raw secret in a connector manifest. Never declare a',
    'destructive endpoint as low risk — the validator clamps writes to medium and',
    'high/irreversible endpoints always require human approval at run time. When asked',
    'for a capability that already exists, say so instead of duplicating it.',
  ].join('\n'),
});

// Per-process guard so re-imports (Next.js dev HMR module reloads) don't
// re-run the seed work even when the DB-level idempotency checks would
// catch it anyway. Cheap, defensive.
const SEED_PERSONAL_TEMPLATES = ['project-watcher','personal-assistant','engineering-lead','marketing-lead','meeting-notes'];

export function seedAgentTemplates(db = getDb()) {
  if (globalThis.__madarxAgentTemplatesSeeded) return;

  const now = nowIso();
  for (const t of AGENT_TEMPLATES) {
    db.prepare(`INSERT INTO marketplace_items (id, kind, slug, title, description, category, author, source_kind, version, manifest_json, risk_summary, tags, created_at)
      VALUES (?, 'agent', ?, ?, ?, ?, 'madarx', 'builtin', '1.0', ?, ?, ?, ?)
      ON CONFLICT(kind, slug) DO UPDATE SET title=excluded.title, description=excluded.description, manifest_json=excluded.manifest_json, tags=excluded.tags`)
      .run(`agent:${t.slug}`, t.slug, t.title, t.description_long, t.role, JSON.stringify({ agent: t }), t.approval_profile, JSON.stringify([t.role, t.engine_default]), now);
  }

  // Caretaker-only fresh install by default: the marketplace catalog above is
  // always seeded (so the agent templates are discoverable + installable), but
  // the 5 personal-* live agents are NOT pre-installed unless explicitly opted
  // in via MADARX_SEED_DEFAULT_AGENTS=1. A clean install therefore starts with
  // only the Caretaker (ops-agent); the user installs the rest on demand.
  if (process.env.MADARX_SEED_DEFAULT_AGENTS !== '1') {
    globalThis.__madarxAgentTemplatesSeeded = true;
    return;
  }

  // Skip the per-project install loop when all expected installations already
  // exist. Without this, every dev-server worker re-fires installAgentTemplate
  // for the 5 personal-* agents, which used to spam `agent.create` audit rows
  // even though the rows were already present (see the May 2026 723K-row
  // spam incident; createAgent itself is now also guarded by a pre-INSERT
  // existence check in lib/runtime.js).
  const placeholders = SEED_PERSONAL_TEMPLATES.map(() => '?').join(',');
  const existing = db.prepare(
    `SELECT COUNT(*) AS c FROM agent_template_installations
     WHERE project_id = 'personal' AND template_slug IN (${placeholders})`
  ).get(...SEED_PERSONAL_TEMPLATES);

  if (existing?.c >= SEED_PERSONAL_TEMPLATES.length) {
    globalThis.__madarxAgentTemplatesSeeded = true;
    return;
  }

  for (const slug of SEED_PERSONAL_TEMPLATES) {
    try { installAgentTemplate(slug, 'personal', { seed: true }); } catch {}
  }
  globalThis.__madarxAgentTemplatesSeeded = true;
}

function normalizeProcessTemplate(t) {
  if (!t) return null;
  return {
    slug: t.slug,
    title: t.title,
    role: t.role,
    engine_default: t.engine_default,
    tools_required: Array.isArray(t.tools_required) ? t.tools_required : [],
    tools_optional: Array.isArray(t.tools_optional) ? t.tools_optional : [],
    skills: Array.isArray(t.skills) ? t.skills : [],
    memory_scopes: ['agent', 'project'],
    approval_profile: t.approval_profile || 'standard',
    description_long: t.description_long,
    system_prompt: typeof t.system_prompt === 'string' ? t.system_prompt : null,
  };
}

export function getAgentTemplate(slug) {
  const local = AGENT_TEMPLATES.find(t => t.slug === slug);
  if (local) return local;
  const processT = PROCESS_AGENT_TEMPLATES.find(t => t.slug === slug);
  return normalizeProcessTemplate(processT);
}

export function installAgentTemplate(slug, projectId = 'personal', opts = {}) {
  const template = getAgentTemplate(slug);
  if (!template) throw new Error('agent template not found');
  const db = getDb();
  const existing = db.prepare('SELECT * FROM agent_template_installations WHERE template_slug = ? AND project_id = ?').get(slug, projectId);
  if (existing && !opts.overwrite) return existing;
  const agentId = opts.agent_id || `${projectId}-${slug}`.replace(/[^a-zA-Z0-9_-]/g, '-');
  const agent = createAgent({
    id: agentId,
    name: template.title,
    type: template.role,
    role: template.role,
    title: template.title,
    engine: template.engine_default,
    tools: template.tools_required,
    mission: template.description_long,
    system_prompt: template.system_prompt || undefined,
    approval: template.approval_profile,
    project_id: projectId,
    template_slug: slug,
    suppress_live_event: Boolean(opts.seed),
  });
  for (const tool of template.tools_required) setAgentToolPermission(agentId, tool, tool.includes('write') || tool.includes('send') || tool.includes('terminal') ? 'approval_required' : 'auto');
  const now = nowIso();
  db.prepare(`INSERT INTO agent_template_installations (id, template_slug, agent_id, project_id, installed_at, status)
    VALUES (?, ?, ?, ?, ?, 'active')
    ON CONFLICT(template_slug, project_id) DO UPDATE SET agent_id=excluded.agent_id, installed_at=excluded.installed_at, status='active'`)
    .run(`ati-${projectId}-${slug}`, slug, agentId, projectId, now);
  return { template_slug: slug, agent_id: agentId, project_id: projectId, installed_at: now, agent };
}

export function uninstallAgent(agentId) {
  const db = getDb();
  const row = db.prepare('SELECT * FROM agent_template_installations WHERE agent_id = ?').get(agentId);
  db.prepare('DELETE FROM agent_template_installations WHERE agent_id = ?').run(agentId);
  db.prepare("UPDATE agents SET status = 'archived' WHERE id = ?").run(agentId);
  const src = path.join(AGENTS_DIR, agentId);
  const dst = path.join(AGENTS_DIR, '_archived', agentId);
  try { if (fs.existsSync(src)) { fs.mkdirSync(path.dirname(dst), { recursive: true }); fs.renameSync(src, dst); } } catch {}
  return { archived: true, installation: row || null };
}

export function listInstalledAgents(filters = {}) {
  const db = getDb();
  const where = ['i.status = \'active\'']; const params=[];
  if (filters.project_id) { where.push('i.project_id = ?'); params.push(filters.project_id); }
  if (filters.role) { where.push('a.role = ?'); params.push(filters.role); }
  if (filters.engine) { where.push('a.engine = ?'); params.push(filters.engine); }
  if (filters.status) { where.push('a.status = ?'); params.push(filters.status); }
  return db.prepare(`SELECT i.*, a.name, a.role, a.engine, a.status AS agent_status, a.last_run_at, a.next_run_at, p.name AS project_name, p.slug AS project_slug
    FROM agent_template_installations i JOIN agents a ON a.id = i.agent_id LEFT JOIN projects p ON p.id = i.project_id WHERE ${where.join(' AND ')} ORDER BY p.name ASC, a.name ASC`).all(...params);
}

// Batched count of active installed agents per project. One query instead of
// one-per-project. Returns a plain object keyed by project_id (string).
export function countInstalledAgentsByProject() {
  const rows = getDb().prepare(
    `SELECT project_id, COUNT(*) AS count
       FROM agent_template_installations
      WHERE status = 'active' AND project_id IS NOT NULL
      GROUP BY project_id`
  ).all();
  const map = Object.create(null);
  for (const row of rows) map[row.project_id] = row.count;
  return map;
}
