// lib/marketplace-modules.js
//
// Module-kind marketplace provisioning + teardown guard.
//
// A `kind:'module'` marketplace item bundles a whole vertical (agents, tools,
// skill packs, workflow templates, nav surfaces). Installing provisions the
// module's agents/tools and flips the marketplace_installations row; the
// sidebar then surfaces the module's nav via getInstalledModuleNav().
//
// Uninstall is GUARDED, mirroring the workflow uninstall guard in
// lib/marketplace.js::setWorkflowItemInstalled(): if any active project still
// references the module, the install row is NOT flipped and a structured
// `{ status: 'blocked' }` result is returned. Only when no active projects
// remain does the row flip to installed=0.
//
// Audit rows are written on every transition (install / uninstall /
// uninstall_blocked) via the canonical INSERT-only appendComplianceAudit writer.
//
// Provisioning is idempotent: seedMinesAgents() checks by id before creating,
// and boot tool seeding already enables module tools — we never duplicate.
//
// getItem is passed in by the caller (lib/marketplace.js::setItemInstalled) to
// avoid a circular import: marketplace.js imports this module at top level, and
// these functions need its getItem() to return the post-write item shape. This
// keeps the functions synchronous so setItemInstalled stays synchronous.

import { getDb, nowIso } from './db.js';
import { appendComplianceAudit } from './compliance-audit.js';
import { seedMinesAgents, removeMinesAgents } from './mines/seeds.js';
import { installAgentTemplate, uninstallAgent } from './agent-templates.js';

// Generic module provisioning: provision each agent TEMPLATE the module declares
// (manifest.agents are template slugs). Idempotent — installAgentTemplate returns
// the existing install without re-creating. Returns the count provisioned.
function provisionTemplateAgents(item) {
  const slugs = Array.isArray(item?.module?.agents) ? item.module.agents : [];
  let count = 0;
  for (const slug of slugs) {
    try {
      installAgentTemplate(slug, 'personal');
      count += 1;
    } catch {
      // A missing template slug must not abort the whole install.
    }
  }
  return count;
}

// Remove the agents this module provisioned, resolving each template slug back to
// its installed agent_id via the installation table. Returns the count removed.
function deprovisionTemplateAgents(db, item) {
  const slugs = Array.isArray(item?.module?.agents) ? item.module.agents : [];
  let count = 0;
  for (const slug of slugs) {
    try {
      const row = db.prepare(
        "SELECT agent_id FROM agent_template_installations WHERE template_slug = ? AND project_id = 'personal'"
      ).get(slug);
      if (row?.agent_id) {
        uninstallAgent(row.agent_id);
        count += 1;
      }
    } catch {
      // best-effort teardown
    }
  }
  return count;
}

const AUDIT_ORG_ID = 'personal-org';
const AUDIT_USER_ID = 'system';

// ── install row helpers ──────────────────────────────────────────────────────

function writeInstallRow(db, itemId, installed, now) {
  const existing = db
    .prepare('SELECT item_id FROM marketplace_installations WHERE item_id=?')
    .get(itemId);
  if (existing) {
    db.prepare(`
      UPDATE marketplace_installations
         SET installed=?, installed_at=?
       WHERE item_id=?
    `).run(installed ? 1 : 0, installed ? now : null, itemId);
  } else {
    db.prepare(`
      INSERT INTO marketplace_installations (item_id, installed, installed_at)
      VALUES (?, ?, ?)
    `).run(itemId, installed ? 1 : 0, installed ? now : null);
  }
}

// ── cascade teardown helpers ─────────────────────────────────────────────────
//
// Uninstall is a CASCADE: removing the module removes the project data it owns.
// mines_projects children (source_documents, drill_holes, assay_samples,
// extracted_facts, valuation_scenarios, spatial_assets, …) are all declared
// `ON DELETE CASCADE`, and PRAGMA foreign_keys is ON, so deleting the project
// rows tears down the whole dependency subtree in one statement.
function deleteMinesProjects(db) {
  try {
    const row = db.prepare('SELECT COUNT(*) AS n FROM mines_projects').get();
    const n = Number(row?.n) || 0;
    if (n > 0) db.prepare('DELETE FROM mines_projects').run();
    return n;
  } catch {
    // Table may not exist in a partial schema — nothing to remove.
    return 0;
  }
}

/**
 * Provision and install a module marketplace item. Idempotent.
 *
 * - Ensures module agents are provisioned (seedMinesAgents() — idempotent,
 *   checks by id). Module tools are seeded at boot, so we do not duplicate.
 * - Flips the marketplace_installations row to installed=1.
 * - Writes a `marketplace.module.installed` audit row (status 'success').
 *
 * @param {object} item — the module MarketplaceItem (must carry id/kind/slug).
 * @param {(id: string) => object} getItem — refresher from the marketplace layer.
 * @returns {object} the refreshed item (result.installed === true).
 */
export function installModule(item, getItem) {
  const db = getDb();
  const now = nowIso();

  // Idempotent provisioning. Today the only module is `mines`; route by
  // module_id so future modules can add their own provisioning here.
  const moduleId = item?.module?.module_id || item?.slug;
  if (moduleId === 'mines') {
    seedMinesAgents();
  } else {
    // Generic module: provision the agent templates it declares.
    provisionTemplateAgents(item);
  }

  writeInstallRow(db, item.id, true, now);

  try {
    appendComplianceAudit({
      user_id: AUDIT_USER_ID,
      org_id: AUDIT_ORG_ID,
      action: 'marketplace.module.installed',
      status: 'success',
      summary: `Installed module ${item.title || item.slug || item.id}`,
      action_target_id: item.id,
      action_target_type: 'marketplace_module',
      meta: { module_id: moduleId, slug: item.slug },
    });
  } catch {
    // Audit failure is non-fatal to the install — the install row is the
    // source of truth and was already written above.
  }

  return getItem(item.id);
}

/**
 * Uninstall a module marketplace item. CASCADE teardown — symmetric with
 * installModule (which provisions agents + flips the row): uninstall removes
 * the module's provisioned agents AND the project data it owns, then flips the
 * install row to installed=0.
 *
 * - Deletes the module's agents (removeMinesAgents() — idempotent).
 * - Deletes the module's projects, cascading to all child rows via FK.
 * - Flips the marketplace_installations row to installed=0.
 * - Writes a `marketplace.module.uninstalled` audit row (status 'success')
 *   recording how many agents/projects were removed.
 *
 * The deletes + row flip run inside a single transaction so a partial failure
 * leaves the module installed rather than half-torn-down.
 *
 * @param {object} item — the module MarketplaceItem.
 * @param {(id: string) => object} getItem — refresher from the marketplace layer.
 * @returns {object} refreshed item (result.installed === false).
 */
export function uninstallModule(item, getItem) {
  const db = getDb();
  const now = nowIso();
  const moduleId = item?.module?.module_id || item?.slug;

  let removedProjects = 0;
  let removedAgents = 0;

  db.exec('BEGIN');
  try {
    if (moduleId === 'mines') {
      removedProjects = deleteMinesProjects(db);
      removedAgents = removeMinesAgents();
    } else {
      // Generic module: tear down the agent templates it provisioned.
      removedAgents = deprovisionTemplateAgents(db, item);
    }
    writeInstallRow(db, item.id, false, now);
    db.exec('COMMIT');
  } catch (err) {
    db.exec('ROLLBACK');
    throw err;
  }

  try {
    appendComplianceAudit({
      user_id: AUDIT_USER_ID,
      org_id: AUDIT_ORG_ID,
      action: 'marketplace.module.uninstalled',
      status: 'success',
      summary: `Uninstalled module ${item.title || item.slug || item.id} (removed ${removedAgents} agent(s), ${removedProjects} project(s))`,
      action_target_id: item.id,
      action_target_type: 'marketplace_module',
      meta: { module_id: moduleId, slug: item.slug, removed_agents: removedAgents, removed_projects: removedProjects },
    });
  } catch {
    // Non-fatal — the install row is the source of truth.
  }

  return getItem(item.id);
}
