/**
 * In-process registry of installed printing-press CLIs.
 *
 * Source of truth: SQLite `printing_press_clis` table. The companion JSON
 * manifest at data/printing-press-clis.json is a debug mirror — recreated
 * from the table on demand, never the canonical store.
 */

import path from 'node:path';
import fs from 'node:fs';
import { getDb, nowIso } from './db.js';
import { WORKSPACE, DATA_DIR } from './paths.js';

export const GENERATED_CLIS_DIR = path.join(WORKSPACE, 'generated-clis');
export const SKILLS_INSTALL_DIR = path.join(WORKSPACE, 'skills', 'printing-press');
export const MIRROR_JSON_PATH = path.join(DATA_DIR, 'printing-press-clis.json');

const SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
const DEFAULT_QUOTA_BYTES = 100 * 1024 * 1024;  // 100 MB

export function isValidSlug(slug) {
  return typeof slug === 'string' && SLUG_RE.test(slug);
}

export function cliInstallPath(slug) {
  if (!isValidSlug(slug)) throw new Error(`Invalid CLI slug: ${slug}`);
  return path.join(GENERATED_CLIS_DIR, slug);
}

/**
 * Validate that a path lives under the writable generated-CLIs root. Used by
 * the adapter before any spawn/read to refuse escapes.
 */
export function assertUnderGeneratedClis(absPath) {
  const resolved = path.resolve(absPath);
  const root = path.resolve(GENERATED_CLIS_DIR);
  if (!resolved.startsWith(root + path.sep) && resolved !== root) {
    throw new Error(`Path is not under workspace/generated-clis/: ${absPath}`);
  }
  return resolved;
}

function rowToCli(row) {
  if (!row) return null;
  let manifest = null;
  let declared_scopes = [];
  let required_secrets = [];
  try {
    manifest = row.manifest_json ? JSON.parse(row.manifest_json) : null;
  } catch {
    manifest = null;
  }
  try {
    declared_scopes = row.declared_scopes ? JSON.parse(row.declared_scopes) : [];
  } catch {
    declared_scopes = [];
  }
  try {
    required_secrets = row.required_secrets ? JSON.parse(row.required_secrets) : [];
  } catch {
    required_secrets = [];
  }
  return {
    slug: row.slug,
    source_kind: row.source_kind,
    source_url: row.source_url || null,
    installed_at: row.installed_at,
    updated_at: row.updated_at,
    path: row.path,
    declared_scopes,
    required_secrets,
    quota_bytes: row.quota_bytes || DEFAULT_QUOTA_BYTES,
    manifest,
    status: row.status,
  };
}

export function listInstalledClis({ includeRemoved = false } = {}) {
  const db = getDb();
  const rows = includeRemoved
    ? db.prepare('SELECT * FROM printing_press_clis ORDER BY installed_at DESC').all()
    : db.prepare("SELECT * FROM printing_press_clis WHERE status != 'removed' ORDER BY installed_at DESC").all();
  return rows.map(rowToCli);
}

export function getCli(slug) {
  if (!isValidSlug(slug)) return null;
  const db = getDb();
  const row = db.prepare('SELECT * FROM printing_press_clis WHERE slug = ?').get(slug);
  return rowToCli(row);
}

export function getCliPath(slug) {
  const cli = getCli(slug);
  return cli ? cli.path : null;
}

/**
 * Idempotent register. Used by scripts/printing-press/install-cli.mjs after
 * a successful generation. Does NOT spawn anything — pure DB write.
 */
export function recordInstall({
  slug,
  source_kind,
  source_url = null,
  declared_scopes = [],
  required_secrets = [],
  quota_bytes = DEFAULT_QUOTA_BYTES,
  manifest = null,
  path: cliPath,
}) {
  if (!isValidSlug(slug)) throw new Error(`Invalid CLI slug: ${slug}`);
  if (!source_kind) throw new Error('source_kind is required');
  if (!cliPath) throw new Error('path is required');
  assertUnderGeneratedClis(cliPath);

  const db = getDb();
  const now = nowIso();
  const existing = db.prepare('SELECT slug FROM printing_press_clis WHERE slug = ?').get(slug);
  if (existing) {
    db.prepare(`
      UPDATE printing_press_clis
      SET source_kind = ?, source_url = ?, updated_at = ?, path = ?,
          declared_scopes = ?, required_secrets = ?, quota_bytes = ?,
          manifest_json = ?, status = 'active'
      WHERE slug = ?
    `).run(
      source_kind,
      source_url,
      now,
      cliPath,
      JSON.stringify(declared_scopes),
      JSON.stringify(required_secrets),
      quota_bytes,
      manifest ? JSON.stringify(manifest) : null,
      slug
    );
  } else {
    db.prepare(`
      INSERT INTO printing_press_clis
        (slug, source_kind, source_url, installed_at, updated_at, path,
         declared_scopes, required_secrets, quota_bytes, manifest_json, status)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active')
    `).run(
      slug,
      source_kind,
      source_url,
      now,
      now,
      cliPath,
      JSON.stringify(declared_scopes),
      JSON.stringify(required_secrets),
      quota_bytes,
      manifest ? JSON.stringify(manifest) : null
    );
  }
  return getCli(slug);
}

export function markCliRemoved(slug) {
  if (!isValidSlug(slug)) return null;
  const db = getDb();
  const now = nowIso();
  db.prepare(`UPDATE printing_press_clis SET status = 'removed', updated_at = ? WHERE slug = ?`).run(now, slug);
  return getCli(slug);
}

export function setCliStatus(slug, status) {
  const allowed = new Set(['active', 'disabled', 'removed']);
  if (!allowed.has(status)) throw new Error(`Invalid status: ${status}`);
  if (!isValidSlug(slug)) throw new Error(`Invalid CLI slug: ${slug}`);
  const db = getDb();
  db.prepare(`UPDATE printing_press_clis SET status = ?, updated_at = ? WHERE slug = ?`).run(status, nowIso(), slug);
  return getCli(slug);
}

/**
 * Best-effort disk-usage report. Used by the daily quota GC heartbeat task.
 * Skips silently if the directory does not exist.
 */
export function measureCliDiskUsage(slug) {
  const cli = getCli(slug);
  if (!cli) return null;
  try {
    return { slug, bytes: directorySize(cli.path), quota_bytes: cli.quota_bytes };
  } catch {
    return { slug, bytes: 0, quota_bytes: cli.quota_bytes, error: 'measure_failed' };
  }
}

function directorySize(dir) {
  if (!fs.existsSync(dir)) return 0;
  let total = 0;
  const entries = fs.readdirSync(dir, { withFileTypes: true });
  for (const entry of entries) {
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      total += directorySize(full);
    } else if (entry.isFile()) {
      try {
        total += fs.statSync(full).size;
      } catch {
        // ignore
      }
    }
  }
  return total;
}

/**
 * Rebuild the JSON debug mirror from the table. Cheap; called at end of
 * install scripts so an operator can `cat data/printing-press-clis.json`.
 */
export function refreshMirrorJson() {
  try {
    if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
    const clis = listInstalledClis({ includeRemoved: true });
    fs.writeFileSync(MIRROR_JSON_PATH, JSON.stringify({ generated_at: nowIso(), clis }, null, 2));
  } catch (err) {
    console.warn('[printing-press] mirror JSON refresh failed:', err.message);
  }
}
