import { execFileSync, spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { ROOT } from './paths.js';
import { seedMarketplaceItems } from './marketplace-seeds.js';
import { ensureDefaultProject } from './projects.js';
import { seedAgentTemplates, installAgentTemplate } from './agent-templates.js';
import { ensureOpsAgent, createArtifact, migrateDefaultAgentSkills } from './runtime.js';
import { renderChangelogHtml } from './artifact-format.js';
import { migrateArtifactsToProjects } from './artifact-migration.js';
import { runAuthV1Migration } from '../scripts/migrate-auth-v1.mjs';
import { ensureSeed as ensureDashboardSpacesSeed } from './dashboard-spaces.js';
import { seedProcessDefinitions } from './process/seeds/adgm-entity-setup.js';
import { seedProcessAgentTemplates } from './process/seeds/agent-templates.js';
import { seedHailAgentTemplates } from './process/seeds/hail-agent-templates.js';
import { seedHailEngagementLifecycle } from './process/seeds/hail-engagement-lifecycle.js';
import { seedHailQuarterlyReview } from './process/seeds/hail-quarterly-review.js';
import { registerHailGateListener } from './hail/review-gates.js';
import { seedMarketplaceWorkflows } from './process/seeds/marketplace-workflows.js';
import { seedMinesAgents } from './mines/seeds.js';
import { seedMinesWorkflows } from './process/seeds/mines-workflows.js';
import { seedBrandProfile } from './brand.js';
import { currentVersion } from './update-check.js';

export const DEFAULT_AGENTS = [];
export const DEFAULT_CONNECTORS = [];

/**
 * Fresh installs ship with an empty /workflows page. The module workflow
 * definitions (ADGM, HAIL lifecycle + quarterly, the Mines workflows) are
 * opt-in — set MADARX_SEED_DEFAULT_WORKFLOWS=1 to pre-seed them. Mirrors the
 * MADARX_SEED_DEFAULT_AGENTS gate for the Mines live agents.
 *
 * The agent templates and the kind:'workflow' marketplace catalog still seed
 * regardless, so a user can install any of these workflows on demand from the
 * marketplace even on a default install.
 */
const SEED_DEFAULT_WORKFLOWS = process.env.MADARX_SEED_DEFAULT_WORKFLOWS === '1';

/**
 * Ensure at least one queued tool_rate_limit_gc heartbeat task exists.
 * Idempotent: skips insert when an active (queued/leased/running) GC task
 * already exists, so this is safe to call on every getDb() open.
 *
 * The heartbeat worker re-schedules the next GC run after each completion,
 * so this seed only fires once on a fresh install.
 */
function seedRateLimitGcTask(db) {
  const existing = db.prepare(
    "SELECT id FROM heartbeat_tasks WHERE type = 'tool_rate_limit_gc' AND status IN ('queued','leased','running') LIMIT 1"
  ).get();
  if (existing) return;

  const id = `hbtask-rl-gc-${Date.now().toString(36)}`;
  const payload = JSON.stringify({ type: 'tool_rate_limit_gc' });
  const now = new Date().toISOString();
  // First run: 24 h from install so it does not fire immediately.
  const runAfter = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
  db.prepare(`
    INSERT INTO heartbeat_tasks
      (id, agent_id, schedule_id, type, status, priority, payload, attempts, max_attempts, run_after, created_at, updated_at)
    VALUES (?, NULL, NULL, ?, 'queued', 0, ?, 0, 99, ?, ?, ?)
  `).run(id, 'tool_rate_limit_gc', payload, runAfter, now, now);
}

/**
 * Ensure a queued weekly update_check heartbeat task exists (idempotent). The
 * worker re-schedules the next run +7d after each completion, so this seed only
 * fires on a fresh install. The check writes $MADARX_HOME/.update_check (the
 * dashboard banner reads it) and auto-applies only if the operator opted into
 * hands-off updates at `madarx setup`.
 */
function seedUpdateCheckTask(db) {
  const existing = db.prepare(
    "SELECT id FROM heartbeat_tasks WHERE type = 'update_check' AND status IN ('queued','leased','running') LIMIT 1"
  ).get();
  if (existing) return;

  const id = `hbtask-upd-${Date.now().toString(36)}`;
  const payload = JSON.stringify({ type: 'update_check' });
  const now = new Date().toISOString();
  // First check: 1 h after install (don't hammer the registry on every fresh boot).
  const runAfter = new Date(Date.now() + 60 * 60 * 1000).toISOString();
  db.prepare(`
    INSERT INTO heartbeat_tasks
      (id, agent_id, schedule_id, type, status, priority, payload, attempts, max_attempts, run_after, created_at, updated_at)
    VALUES (?, NULL, NULL, ?, 'queued', 0, ?, 0, 99, ?, ?, ?)
  `).run(id, 'update_check', payload, runAfter, now, now);
}

/**
 * Seed a default email allowlist entry for the email_send tool.
 *
 * Reads the install user's email domain from `git config user.email` (using
 * execFileSync with a fixed argument list — no shell, no injection risk) and
 * inserts an allowlist entry for that domain so the first send can succeed
 * without manual configuration.
 *
 * Idempotent: skips insert if any email_send allowlist entry already exists.
 * Silent: logs a warning and returns on any failure (git unavailable, no email
 * configured, DB write failed).
 */
function seedDefaultEmailAllowlist(db) {
  try {
    // Idempotency: only seed if no email_send allowlist entries exist.
    const existing = db.prepare(
      "SELECT id FROM tool_allowlists WHERE tool_name = 'email_send' LIMIT 1"
    ).get();
    if (existing) return;

    let domain = null;
    try {
      // execFileSync with explicit args array — no shell expansion, no injection risk.
      const gitEmail = execFileSync('git', ['config', 'user.email'], {
        stdio: ['ignore', 'pipe', 'ignore'],
        timeout: 3000,
      }).toString().trim();

      if (gitEmail.includes('@')) {
        domain = gitEmail.split('@').pop().toLowerCase().trim();
      }
    } catch {
      // git not available or no user.email configured — skip silently.
    }

    if (!domain) {
      console.warn('[seeds] Could not determine email domain from git config user.email. ' +
        'Add a recipient_domain allowlist entry for email_send manually via the dashboard.');
      return;
    }

    const id = `tal-seed-email-${Date.now().toString(36)}`;
    const now = new Date().toISOString();
    db.prepare(`
      INSERT INTO tool_allowlists (id, tool_name, scope_key, scope_value, label, created_at)
      VALUES (?, 'email_send', 'recipient_domain', ?, ?, ?)
    `).run(id, domain, 'Seeded from git config on first install', now);

    console.info(`[seeds] Email allowlist seeded: email_send / recipient_domain = ${domain}`);
  } catch (err) {
    console.warn('[seeds] seedDefaultEmailAllowlist failed:', err?.message || err);
  }
}

/**
 * Seed the madarX CTO brief HTML artifact into the main dashboard space on a
 * fresh install.
 *
 * Reads the vendored doc at seed-assets/cto-brief.html and creates an HTML
 * artifact bound to the default space ('default', shown as "main"). Gives every
 * fresh install a starting document on the main space.
 *
 * Idempotency uses a `config` marker (`cto_brief_seeded`) rather than an
 * artifact-existence check: if the user deletes the brief it stays deleted and
 * never reappears on the next boot — same delete-stays-deleted contract as the
 * Caretaker agent. Silent + non-fatal: a missing source file or a failed write
 * leaves the marker unset so it can retry on a later boot, never blocks boot.
 */
function seedCtoBriefArtifact(db) {
  try {
    const marker = db.prepare("SELECT value FROM config WHERE key = 'cto_brief_seeded'").get();
    if (marker) return;

    const src = path.join(ROOT, 'seed-assets', 'cto-brief.html');
    if (!fs.existsSync(src)) {
      // Source not shipped / not present — skip without marking so a later boot
      // (e.g. after an upgrade that includes it) can still seed it.
      return;
    }
    const html = fs.readFileSync(src, 'utf8');

    createArtifact({
      title: 'madarX OS — Vision, Plan & Open Questions (CTO Brief)',
      kind: 'html',
      content: html,
      space_id: 'default',
      agent_id: 'main-orchestrator',
      // Opt out of the per-day title dedupe — our config marker is the sole
      // idempotency guard for this one-time seed.
      dedupe_key: '',
    });

    db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES ('cto_brief_seeded', '1')").run();
  } catch (err) {
    console.warn('[seeds] seedCtoBriefArtifact failed:', err?.message || err);
  }
}

/**
 * Locate CHANGELOG.md across the dev tree and the standalone web bundle.
 * Mirrors update-check.js's defensive candidate list because `ROOT` is baked at
 * Next build time and misses inside the bundled server.
 */
function readChangelog() {
  const candidates = [
    path.join(ROOT, 'CHANGELOG.md'),
    path.join(process.cwd(), 'CHANGELOG.md'),
    path.join(process.cwd(), '.next', 'standalone', 'CHANGELOG.md'),
  ];
  for (const p of candidates) {
    try {
      if (fs.existsSync(p)) return fs.readFileSync(p, 'utf8');
    } catch {
      // unreadable candidate — try the next one
    }
  }
  return null;
}

/**
 * Slice the `## [version] - date` … (until the next `## [`) block out of the
 * changelog markdown. Returns the section markdown, or null if not found.
 */
function extractChangelogSection(md, version) {
  if (!md || !version) return null;
  const lines = md.split('\n');
  const esc = version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const startRe = new RegExp(`^##\\s*\\[${esc}\\]`);
  let start = -1;
  for (let i = 0; i < lines.length; i++) {
    if (startRe.test(lines[i])) { start = i; break; }
  }
  if (start === -1) return null;
  let end = lines.length;
  for (let i = start + 1; i < lines.length; i++) {
    if (/^##\s*\[/.test(lines[i])) { end = i; break; }
  }
  return lines.slice(start, end).join('\n').trim() || null;
}

/**
 * On a version bump (a shipped bugfix or feature), drop a "What's new" release-
 * notes artifact onto the main dashboard space so the user sees what changed.
 *
 * Trigger is boot-time version detection, so it fires regardless of how the
 * upgrade landed (CLI `madarx update`, dashboard update, weekly auto-update,
 * `npm i -g`, or a git pull + restart). The note content is the matching
 * CHANGELOG.md section for the now-running version.
 *
 * Idempotency uses a per-version `config` marker (`last_release_noted`):
 *   - marker == running version  → already noted, skip (also delete-stays-
 *     deleted: removing the artifact won't recreate it on the next boot).
 *   - no marker (fresh install / first boot with this feature) → set the
 *     baseline silently and create NO artifact. A brand-new install hasn't been
 *     "updated"; only subsequent version bumps produce a note.
 *   - marker != running version → a real upgrade: seed the note, advance marker.
 *
 * Silent + non-fatal: any failure leaves boot untouched.
 */
function seedReleaseNotesArtifact(db) {
  try {
    const version = currentVersion();
    if (!version || version === '0.0.0') return;

    const markerRow = db.prepare("SELECT value FROM config WHERE key = 'last_release_noted'").get();
    const lastNoted = markerRow?.value || null;
    if (lastNoted === version) return;

    const setMarker = () =>
      db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES ('last_release_noted', ?)").run(version);

    // First boot ever (or first boot after this feature shipped): establish the
    // baseline without a note — nothing was "updated", the user just installed.
    if (!lastNoted) {
      setMarker();
      return;
    }

    const section = extractChangelogSection(readChangelog(), version);
    if (!section) {
      // No changelog entry to show — still advance the baseline so we don't
      // retry every boot for a version that has no notes.
      setMarker();
      return;
    }

    // Render the branded "What's new" template (dark editorial, brand palette)
    // instead of the generic document renderer. Falls back to the raw markdown
    // section if the template throws, so a render bug never blocks boot.
    let content = section;
    let kind = 'markdown';
    try {
      const html = renderChangelogHtml({ version, markdown: section });
      if (html && html.includes('<!DOCTYPE')) { content = html; kind = 'html'; }
    } catch (err) {
      console.warn('[seeds] renderChangelogHtml failed, using markdown:', err?.message || err);
    }

    createArtifact({
      title: `madarX ${version} — What's new`,
      kind,
      content,
      space_id: 'default',
      agent_id: 'main-orchestrator',
      // Our per-version config marker is the sole idempotency guard.
      dedupe_key: '',
    });
    setMarker();
  } catch (err) {
    console.warn('[seeds] seedReleaseNotesArtifact failed:', err?.message || err);
  }
}

/**
 * Auto-seed the canonical Ops Agent on first launch.
 *
 * Idempotent: only runs when no row with id='ops-agent' exists in the agents
 * table. If the user explicitly deletes Ops Agent, it stays deleted — this
 * seed only fires on fresh installs (and after a database reset).
 *
 * Delegates to ensureOpsAgent() in runtime.js, which is the canonical setup
 * (toolset, schedule, per-tool permissions, audit row). Same surface that
 * used to back the removed "Ensure Ops Agent" header button.
 */
function seedOpsAgentIfMissing(db) {
  try {
    const existing = db.prepare("SELECT id, name FROM agents WHERE id = 'ops-agent' LIMIT 1").get();
    if (existing) {
      if (existing.name === 'Ops Agent') {
        db.prepare("UPDATE agents SET name = 'Caretaker' WHERE id = 'ops-agent'").run();
        db.prepare("UPDATE schedules SET name = 'Caretaker' WHERE agent_id = 'ops-agent' AND name = 'Ops Agent'").run();
      }
      return;
    }
    ensureOpsAgent({ silent: true });
  } catch (err) {
    console.warn('[seeds] seedOpsAgentIfMissing failed:', err?.message || err);
  }
}

/**
 * Auto-seed the Tool Smith agent (id 'tool-smith-agent') on first launch so the
 * main agent's `request_capability` delegation always has a live target.
 *
 * Idempotent: only runs when no row with id='tool-smith-agent' exists. If the
 * user deletes it, it stays deleted (createAgent inside installAgentTemplate has
 * its own existence guard). The fixed id matches the assignee request_capability
 * delegates to. Silent + non-fatal.
 */
function seedToolSmithAgentIfMissing(db) {
  try {
    const existing = db.prepare("SELECT id FROM agents WHERE id = 'tool-smith-agent' LIMIT 1").get();
    if (existing) return;
    installAgentTemplate('tool-smith', 'personal', { seed: true, agent_id: 'tool-smith-agent' });
  } catch (err) {
    console.warn('[seeds] seedToolSmithAgentIfMissing failed:', err?.message || err);
  }
}

/**
 * Fire-and-forget disk-encryption health check on each boot.
 *
 * Spawns check-disk-encryption.mjs as a detached child process so it never
 * blocks the synchronous seedDefaults call. The child writes its own audit row
 * and exits 0. Errors in the child are observation-only; boot always continues.
 *
 * Two-layer idempotency:
 *   1. Per-process: `globalThis.__madarxDiskEncryptionCheckRan` blocks
 *      re-runs after Next.js HMR module reloads.
 *   2. Cross-process: a DB query for a recent `disk_encryption.health` audit
 *      row blocks fan-out across Next.js dev-mode workers (page-router,
 *      API, RSC), which each have their own globalThis. Without this we
 *      saw ~786 audit rows/minute sustained — 71K rows total in the
 *      May 2026 audit_logs spam incident.
 */
function runDiskEncryptionCheck(db) {
  if (globalThis.__madarxDiskEncryptionCheckRan) return;
  globalThis.__madarxDiskEncryptionCheckRan = true;

  // Cross-process / cross-restart debounce: skip if the disk encryption
  // health was checked in the last hour. The check is informational
  // (does the host have FileVault/LUKS/BitLocker on?), so hourly is more
  // than enough — it does not change second-to-second.
  if (db) {
    try {
      const recent = db.prepare(
        "SELECT id FROM audit_logs WHERE type = 'disk_encryption.health' AND at > datetime('now', '-1 hour') LIMIT 1"
      ).get();
      if (recent) return;
    } catch {
      // If the audit_logs table isn't ready yet (very first schema bootstrap),
      // fall through and let the child write the first row.
    }
  }

  try {
    const scriptPath = new URL('../scripts/check-disk-encryption.mjs', import.meta.url).pathname;
    spawnSync(process.execPath, ['--no-warnings', scriptPath], {
      stdio: 'ignore',
      detached: false,
      timeout: 10000,
      env: { ...process.env },
    });
    // spawnSync is synchronous but fast — fdesetup/lsblk complete in <2s.
    // We don't check the exit code; the check always exits 0.
  } catch {
    // Non-fatal: never block boot on a missing or broken health check script.
  }
}

export function seedDefaults(db) {
  // Fresh-start product mode: do not seed demo agents, schedules, connectors, runs, or artifacts.
  // The Main Agent creates real data only after the user asks for it.

  // Suppress dashboard state.*.changed emits during boot. Seeds call appendAudit
  // (agent.create, schedule.upsert, etc.) which would otherwise fan out to any
  // already-subscribed live-events handler. The dashboard reads its first state
  // snapshot from /api/state on mount, so the seed audits are not needed on the
  // wire. Restored in `finally` below.
  globalThis.__madarxSuppressStateEmits = true;

  try {

  // Infrastructure: ensure the daily rate-limit GC task is queued (idempotent).
  try {
    seedRateLimitGcTask(db);
  } catch {
    // Ignore — heartbeat_tasks table may not be ready yet during schema bootstrap.
  }

  // Infrastructure: ensure the weekly update-check task is queued (idempotent).
  try {
    seedUpdateCheckTask(db);
  } catch {
    // Ignore — heartbeat_tasks table may not be ready yet during schema bootstrap.
  }

  // Email allowlist: seed install user's domain from git config on first install.
  try {
    seedDefaultEmailAllowlist(db);
  } catch {
    // Ignore — tool_allowlists table may not be ready yet during schema bootstrap.
  }

  // V1 personal workforce: seed the default Personal project and its Disk root.
  try {
    ensureDefaultProject(db);
  } catch {
    // Ignore — project tables may not be ready yet during schema bootstrap.
  }

  // Artifact path migration: move legacy workspace/artifacts/* under each
  // project's folder. Idempotent — writes data/.artifacts-migrated-v1 marker.
  try {
    migrateArtifactsToProjects({});
  } catch {
    // Non-fatal — boot continues without migration on failure.
  }

  // Marketplace catalog: idempotent skill + bundle seeds for the marketplace UI.
  try {
    seedMarketplaceItems(db);
  } catch {
    // Ignore — marketplace_items table may not be ready yet during schema bootstrap.
  }

  // Agent marketplace catalog + pre-installed Personal team templates.
  try {
    seedAgentTemplates(db);
  } catch {
    // Ignore — agent template tables may not be ready yet during schema bootstrap.
  }

  // Process engine: ADGM entity-setup agent templates always seed (catalog deps
  // for the marketplace install path). The ADGM workflow *definition* is opt-in
  // (MADARX_SEED_DEFAULT_WORKFLOWS=1) so a fresh /workflows page is empty.
  try {
    seedProcessAgentTemplates(db);
    if (SEED_DEFAULT_WORKFLOWS) seedProcessDefinitions();
  } catch {
    // Ignore — process tables or marketplace tables may not be ready yet.
  }

  // HAIL operating-model layer: agent templates always seed (validator + market-
  // place install path depend on them). The two HAIL workflow definitions are
  // opt-in. The gate listener is harmless when no HAIL definition exists, so it
  // always registers.
  try {
    seedHailAgentTemplates(db);
    if (SEED_DEFAULT_WORKFLOWS) {
      seedHailEngagementLifecycle();
      seedHailQuarterlyReview();
    }
    // Register the in-process listener that mirrors engine approval decisions
    // into the hail_review_gates ledger. globalThis-guarded — safe to call on
    // every boot / re-seed. Without this the gate ledger stays empty at runtime.
    registerHailGateListener();
  } catch {
    // Non-fatal: the HAIL lifecycle workflow will be absent if this seed fails.
  }

  // Marketplace starter workflows (kind:'workflow'). Idempotent. Depends on
  // seedProcessAgentTemplates above for the validator to resolve agent slugs
  // at install time.
  try {
    seedMarketplaceWorkflows(db);
  } catch {
    // Non-fatal: workflow templates appear empty in the marketplace if the seed fails.
  }

  // Mines module: 5 live agents. Suppressed on a Caretaker-only fresh install
  // (default). Opt in with MADARX_SEED_DEFAULT_AGENTS=1 to pre-seed them. The
  // Mines workflow/process definitions below still seed regardless so the
  // module is functional once the user installs its agents on demand.
  if (process.env.MADARX_SEED_DEFAULT_AGENTS === '1') {
    try {
      seedMinesAgents();
    } catch {
      // Non-fatal: Mines agents will not appear in /agents if this seed fails.
    }
  }

  // Mines module: 4 skeleton workflow definitions written directly to
  // process_definitions via the sole writer (upsertDefinition). Opt-in
  // (MADARX_SEED_DEFAULT_WORKFLOWS=1) so a fresh /workflows page is empty.
  if (SEED_DEFAULT_WORKFLOWS) {
    try {
      seedMinesWorkflows();
    } catch {
      // Non-fatal: Mines workflows will not appear in process_definitions if this fails.
    }
  }

  // Brand: seed the default madarX brand profile into config on first install
  // (idempotent). Ships every fresh install with the madarX brand template
  // active; the user can edit it later via /brand, PUT /api/brand, or the
  // import_brand_guidelines tool. Never overwrites a user-customized profile.
  try {
    seedBrandProfile(db);
  } catch {
    // Ignore — config table may not be ready yet during schema bootstrap.
  }

  // Auto-seed the canonical Ops Agent on first launch (idempotent).
  // Runs after agent templates so the agents table is guaranteed to exist.
  seedOpsAgentIfMissing(db);

  // Auto-seed the Tool Smith agent so request_capability delegation has a target.
  seedToolSmithAgentIfMissing(db);

  // Refresh the business-default skills in existing default-shaped skills.md
  // files (no-op for fresh installs and user-customized agents). Idempotent.
  try { migrateDefaultAgentSkills(); } catch (err) { console.warn('[seeds] migrateDefaultAgentSkills failed:', err?.message || err); }

  // Dashboard spaces: seed the 'default' space and backfill any NULL-space
  // artifacts so the dashboard never has to reconcile orphans client-side.
  // Idempotent. Must run after schema migration adds artifacts.space_id.
  try {
    ensureDashboardSpacesSeed();
  } catch {
    // Ignore — table may not be ready on very first schema bootstrap.
  }

  // Seed the CTO brief HTML artifact into the main space on a fresh install.
  // Runs after the dashboard-spaces seed so the 'default' space row exists.
  try {
    seedCtoBriefArtifact(db);
  } catch {
    // Non-fatal — boot continues if the brief seed fails.
  }

  // Release notes: on a version bump, drop a "What's new" artifact onto the main
  // space. No-op on a fresh install (baseline only). Runs after the dashboard-
  // spaces seed so the 'default' space exists.
  try {
    seedReleaseNotesArtifact(db);
  } catch {
    // Non-fatal — boot continues if the release-notes seed fails.
  }

  // Auth v1 migration: seed personal-org, local-user-1, owner membership, bind
  // orphaned projects. Idempotent — marker file skips the log noise on re-runs,
  // but the INSERT OR IGNORE / UPDATE logic is always safe to re-execute.
  try {
    runAuthV1Migration(db);
  } catch {
    // Non-fatal: auth tables may not be ready yet on very first schema bootstrap.
    // The CLI script (scripts/migrate-auth-v1.mjs) is the manual re-run path.
  }

  // Disk-encryption health check: informational only, never blocks boot.
  // Skipped in test environments (an explicit data-dir override — MADARX_DATA_DIR
  // or the legacy VILLA515_DATA_DIR — is set by tests to isolate their DB from the
  // real data dir) to avoid SQLite lock contention between the spawned check child
  // and the test's own DB handle.
  if (!process.env.MADARX_DATA_DIR && !process.env.VILLA515_DATA_DIR && process.env.NODE_ENV !== 'test') {
    try {
      runDiskEncryptionCheck(db);
    } catch {
      // Silently ignore — health check must never block startup.
    }
  }

  } finally {
    globalThis.__madarxSuppressStateEmits = false;
  }
}
