// Connection instances — multiple installs of one connection at different scopes.
//
// Each row represents "service X is connected at scope Y" with non-secret
// metadata (account label, key fingerprint). The actual credentials live in
// the secrets table at the matching scope.
//
// Scope shape:
//   { scope: 'global',  project_id: null, agent_id: null }
//   { scope: 'project', project_id: '<id>', agent_id: null }
//   { scope: 'agent',   project_id: null, agent_id: '<id>' }

import { getDb, nowIso, newId } from './db.js';

function safeParse(json, fallback) {
  if (!json) return fallback;
  try {
    const parsed = JSON.parse(json);
    return parsed == null ? fallback : parsed;
  } catch {
    return fallback;
  }
}

function shapeRow(row) {
  if (!row) return null;
  return {
    id: row.id,
    service_key: row.service_key,
    scope: row.scope,
    project_id: row.project_id || null,
    agent_id: row.agent_id || null,
    account_label: row.account_label || null,
    last_4: row.last_4 || null,
    summary: safeParse(row.summary_json, null),
    verified_at: row.verified_at,
    created_at: row.created_at,
  };
}

function normalizeScopeInput(input = {}) {
  // Accept either { scope: 'project', project_id: 'x' } or
  // shorthand { project_id: 'x' } / { agent_id: 'x' }.
  const raw = String(input.scope || '').trim().toLowerCase();
  if (input.agent_id) {
    return { scope: 'agent', project_id: null, agent_id: String(input.agent_id) };
  }
  if (input.project_id) {
    return { scope: 'project', project_id: String(input.project_id), agent_id: null };
  }
  if (raw === 'project' || raw === 'agent') {
    throw new Error(`scope '${raw}' requires ${raw === 'project' ? 'project_id' : 'agent_id'}`);
  }
  return { scope: 'global', project_id: null, agent_id: null };
}

/**
 * All install instances for one service, ordered global → project → agent.
 */
export function listInstances(serviceKey) {
  if (!serviceKey) return [];
  const rows = getDb().prepare(`
    SELECT * FROM connection_instances
     WHERE service_key = ?
     ORDER BY
       CASE scope WHEN 'global' THEN 0 WHEN 'project' THEN 1 WHEN 'agent' THEN 2 ELSE 3 END,
       account_label ASC
  `).all(String(serviceKey));
  return rows.map(shapeRow);
}

/**
 * Look up one install by exact scope tuple.
 */
export function getInstance(serviceKey, scopeInput = {}) {
  if (!serviceKey) return null;
  const { scope, project_id, agent_id } = normalizeScopeInput(scopeInput);
  const row = getDb().prepare(`
    SELECT * FROM connection_instances
     WHERE service_key = ? AND scope = ?
       AND COALESCE(project_id, '') = COALESCE(?, '')
       AND COALESCE(agent_id, '')   = COALESCE(?, '')
     LIMIT 1
  `).get(String(serviceKey), scope, project_id, agent_id);
  return shapeRow(row);
}

/**
 * Upsert an instance after a successful verify. `metadata` carries the
 * non-secret display fields the modal renders (account_label, last_4,
 * summary blob).
 */
export function markInstanceVerified(serviceKey, scopeInput = {}, metadata = {}) {
  if (!serviceKey) throw new Error('service_key is required');
  const { scope, project_id, agent_id } = normalizeScopeInput(scopeInput);
  const db = getDb();
  const now = nowIso();
  const summaryJson = metadata.summary ? JSON.stringify(metadata.summary) : null;

  const existing = db.prepare(`
    SELECT id FROM connection_instances
     WHERE service_key = ? AND scope = ?
       AND COALESCE(project_id, '') = COALESCE(?, '')
       AND COALESCE(agent_id, '')   = COALESCE(?, '')
     LIMIT 1
  `).get(String(serviceKey), scope, project_id, agent_id);

  if (existing) {
    db.prepare(`
      UPDATE connection_instances
         SET account_label = ?, last_4 = ?, summary_json = ?, verified_at = ?
       WHERE id = ?
    `).run(
      metadata.account_label || null,
      metadata.last_4 || null,
      summaryJson,
      now,
      existing.id,
    );
    return getInstance(serviceKey, { scope, project_id, agent_id });
  }

  db.prepare(`
    INSERT INTO connection_instances
      (id, service_key, scope, project_id, agent_id, account_label, last_4, summary_json, verified_at, created_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    newId('conn'),
    String(serviceKey),
    scope,
    project_id,
    agent_id,
    metadata.account_label || null,
    metadata.last_4 || null,
    summaryJson,
    now,
    now,
  );
  return getInstance(serviceKey, { scope, project_id, agent_id });
}

/**
 * Remove one install row. Caller is responsible for revoking the matching
 * secrets at the same scope.
 */
export function deleteInstance(serviceKey, scopeInput = {}) {
  if (!serviceKey) throw new Error('service_key is required');
  const { scope, project_id, agent_id } = normalizeScopeInput(scopeInput);
  const result = getDb().prepare(`
    DELETE FROM connection_instances
     WHERE service_key = ? AND scope = ?
       AND COALESCE(project_id, '') = COALESCE(?, '')
       AND COALESCE(agent_id, '')   = COALESCE(?, '')
  `).run(String(serviceKey), scope, project_id, agent_id);
  return { deleted: result.changes };
}

/**
 * All connections grouped by service, useful for agent-detail views
 * ("which connections does agent X have?"). Returns rows where the scope
 * matches the agent (or its project) directly — does NOT include inherited
 * global rows.
 */
export function listInstancesForAgent(agentId, projectId = null) {
  if (!agentId) return [];
  const params = [String(agentId)];
  let sql = `SELECT * FROM connection_instances WHERE agent_id = ?`;
  if (projectId) {
    sql += ` OR (scope = 'project' AND project_id = ?)`;
    params.push(String(projectId));
  }
  sql += ` ORDER BY service_key ASC`;
  return getDb().prepare(sql).all(...params).map(shapeRow);
}

/**
 * All connection instances for one project (does not include global).
 */
export function listInstancesForProject(projectId) {
  if (!projectId) return [];
  return getDb().prepare(`
    SELECT * FROM connection_instances
     WHERE project_id = ? AND scope = 'project'
     ORDER BY service_key ASC
  `).all(String(projectId)).map(shapeRow);
}

export { normalizeScopeInput };
