// lib/tools/connectors/registry.js — sole writer + loader for API connectors (Option D).
//
// A connector is a DECLARATIVE manifest (inert data) describing a REST surface:
// https base_url, an allowed_hosts allowlist, secret-by-reference auth, and a set
// of endpoints each with a method/path/risk. This module is the only writer of
// the api_connectors table.
//
// Surfacing: listConnectorTools() expands each active connector's endpoints into
// callable <slug>.<endpoint> tool descriptors (same shape as a static
// MARKETPLACE_TOOLS row, tagged source.kind='connector'). lib/tools/registry.js
// folds these into listMarketplaceTools() so every gate applies, and the generic
// http_api_call adapter is the ONE executor — it interpolates data only and never
// runs anything supplied by the manifest.

import { getDb, nowIso } from '../../db.js';
import { getSecret } from '../../secrets.js';
import { validateConnectorManifest, RISK_ORDER } from './connector-validator.js';

function approvalForRisk(risk) {
  const required = RISK_ORDER.indexOf(risk) >= RISK_ORDER.indexOf('medium');
  return { approval_required: required, approval_mode: required ? 'approval_required' : 'auto' };
}

/** Secret ref names the manifest's auth depends on (for availability/missing_env). */
function authSecretRefs(auth = {}) {
  const refs = [];
  if (auth.type === 'bearer' || auth.type === 'api_key') {
    if (auth.secret_ref) refs.push(auth.secret_ref);
  } else if (auth.type === 'basic') {
    if (auth.username_ref) refs.push(auth.username_ref);
    if (auth.password_ref) refs.push(auth.password_ref);
  }
  return refs;
}

function secretAvailable(name) {
  try {
    return Boolean(getSecret(name));
  } catch {
    return false;
  }
}

/**
 * Validate + write a connector manifest. Throws an Error with code 'spec_invalid'
 * (and .errors) when the manifest is malformed.
 *
 * @param {object} manifest the connector manifest
 * @param {object} ctx      { created_by_user_id, created_by_agent_id }
 */
export function upsertConnector(manifest, ctx = {}) {
  const result = validateConnectorManifest(manifest);
  if (!result.ok) {
    const err = new Error('connector manifest invalid');
    err.code = 'spec_invalid';
    err.errors = result.errors;
    throw err;
  }
  const n = result.normalized;
  const db = getDb();
  const now = nowIso();
  const existing = db.prepare('SELECT slug FROM api_connectors WHERE slug=?').get(n.slug);
  const manifestJson = JSON.stringify(n);
  if (existing) {
    db.prepare(`UPDATE api_connectors
        SET title=?, base_url=?, manifest_json=?, status='active', updated_at=?
      WHERE slug=?`)
      .run(n.title, n.base_url, manifestJson, now, n.slug);
  } else {
    db.prepare(`INSERT INTO api_connectors
        (slug, title, base_url, manifest_json, status, created_by_user_id, created_by_agent_id, created_at, updated_at)
      VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?)`)
      .run(n.slug, n.title, n.base_url, manifestJson,
        ctx.created_by_user_id || null, ctx.created_by_agent_id || null, now, now);
  }
  return {
    slug: n.slug,
    endpoints: Object.keys(n.endpoints),
    tool_slugs: Object.keys(n.endpoints).map((e) => `${n.slug}.${e}`),
    secret_refs: authSecretRefs(n.auth),
    warnings: result.warnings,
  };
}

export function archiveConnector(slug) {
  const db = getDb();
  const row = db.prepare('SELECT slug FROM api_connectors WHERE slug=?').get(slug);
  if (!row) return { ok: false, error: 'not_found', slug };
  db.prepare("UPDATE api_connectors SET status='archived', updated_at=? WHERE slug=?").run(nowIso(), slug);
  return { ok: true, slug };
}

export function getConnector(slug) {
  return getDb().prepare("SELECT * FROM api_connectors WHERE slug=? AND status='active'").get(slug) || null;
}

/** Parsed normalized manifest. null if missing/archived. */
export function getConnectorManifest(slug) {
  const row = getConnector(slug);
  if (!row) return null;
  try {
    return JSON.parse(row.manifest_json);
  } catch {
    return null;
  }
}

/**
 * Resolve a '<slug>.<endpoint>' tool name to its connector manifest + endpoint
 * definition. Returns null if the connector or endpoint does not exist. Used by
 * both the registry dispatch resolver and the http_api_call executor.
 */
export function resolveConnectorEndpoint(toolName) {
  if (typeof toolName !== 'string') return null;
  const dot = toolName.indexOf('.');
  if (dot <= 0) return null;
  const slug = toolName.slice(0, dot);
  const endpoint = toolName.slice(dot + 1);
  const manifest = getConnectorManifest(slug);
  if (!manifest) return null;
  const endpointDef = manifest.endpoints?.[endpoint];
  if (!endpointDef) return null;
  return { slug, endpoint, manifest, endpointDef };
}

/**
 * Dynamic tool descriptors for every endpoint of every ACTIVE connector — same
 * shape as a static MARKETPLACE_TOOLS row. `available` reflects whether the
 * referenced vault secret is present (you can't call Notion without its token);
 * missing_env surfaces the unresolved secret_ref so the UI/connection assistant
 * can prompt for it.
 */
export function listConnectorTools() {
  let rows;
  try {
    rows = getDb().prepare("SELECT * FROM api_connectors WHERE status='active' ORDER BY slug ASC").all();
  } catch {
    return [];
  }
  const tools = [];
  for (const row of rows) {
    let manifest;
    try {
      manifest = JSON.parse(row.manifest_json);
    } catch {
      continue;
    }
    const secretRefs = authSecretRefs(manifest.auth || {});
    const missing = secretRefs.filter((name) => !secretAvailable(name));
    const available = missing.length === 0;
    for (const [endpoint, def] of Object.entries(manifest.endpoints || {})) {
      const { approval_required, approval_mode } = approvalForRisk(def.risk);
      tools.push({
        name: `${row.slug}.${endpoint}`,
        title: `${manifest.title || row.slug}: ${endpoint}`,
        description: def.description || `${def.method} ${manifest.base_url}${def.path}`,
        category: 'connector',
        risk_level: def.risk,
        approval_required,
        approval_mode,
        required_env: secretRefs,
        enabled: true,
        available,
        missing_env: missing,
        allowlist_required: false,
        allowlist_scope_key: null,
        allowlist_default_allow: false,
        input_schema: def.input_schema || { type: 'object', properties: {} },
        source: { kind: 'connector', connector_slug: row.slug, endpoint },
      });
    }
  }
  return tools;
}

/** Slug list of every active connector — used by request_capability dedupe. */
export function listConnectorSlugs() {
  try {
    return getDb().prepare("SELECT slug FROM api_connectors WHERE status='active'").all().map((r) => r.slug);
  } catch {
    return [];
  }
}
