/**
 * Workspace-scoped audit log of mutating operations.
 *
 * Closes BETA_LAUNCH_PLAN P4.7: "Per-workspace audit log of mutating MCP calls".
 *
 * Schema:
 *   audit_log
 *     id           BIGSERIAL PK
 *     ts           TIMESTAMPTZ DEFAULT NOW()
 *     workspace    TEXT NOT NULL              -- workspace slug
 *     user_email   TEXT                       -- caller; null for runner/system
 *     source       TEXT NOT NULL              -- 'session' | 'api_key' | 'system' | 'webhook'
 *     action       TEXT NOT NULL              -- 'apps.create' | 'databases.delete' | …
 *     resource_type TEXT                      -- 'application' | 'database' | 'project' | …
 *     resource_id  TEXT                       -- coolify uuid / project id / etc.
 *     ok           BOOLEAN NOT NULL
 *     params       JSONB                      -- redacted call params
 *     error        TEXT
 *     turn_id      TEXT                       -- correlation id (chat turn etc.)
 *
 * Read via `SELECT … WHERE workspace = $1 ORDER BY ts DESC LIMIT N`.
 *
 * SECRETS: never write raw credentials into `params`. The helper redacts
 * the standard secret-shaped keys (`api_key`, `password`, `token`, `secret`,
 * `private_key`, `credential`). Callers are still responsible for not
 * passing sensitive blobs through unfiltered.
 */
import { getPool, query } from "@/lib/db-postgres";
import { log } from "@/lib/server/logger";

let tableReady = false;

async function ensureTable() {
  if (tableReady) return;
  await query(`
    CREATE TABLE IF NOT EXISTS audit_log (
      id           BIGSERIAL PRIMARY KEY,
      ts           TIMESTAMPTZ NOT NULL DEFAULT NOW(),
      workspace    TEXT NOT NULL,
      user_email   TEXT,
      source       TEXT NOT NULL,
      action       TEXT NOT NULL,
      resource_type TEXT,
      resource_id  TEXT,
      ok           BOOLEAN NOT NULL DEFAULT TRUE,
      params       JSONB,
      error        TEXT,
      turn_id      TEXT
    )
  `);
  await query(`CREATE INDEX IF NOT EXISTS audit_log_workspace_ts_idx ON audit_log (workspace, ts DESC)`);
  await query(`CREATE INDEX IF NOT EXISTS audit_log_action_ts_idx ON audit_log (action, ts DESC)`);
  tableReady = true;
}

const SECRET_KEYS = new Set([
  "api_key", "apiKey",
  "password",
  "token", "access_token", "refresh_token",
  "secret",
  "private_key", "privateKey",
  "credential", "credentials",
  "authorization",
]);

function redact(obj: unknown, depth = 0): unknown {
  if (depth > 4) return "[deep]";
  if (obj == null) return obj;
  if (Array.isArray(obj)) return obj.map((x) => redact(x, depth + 1));
  if (typeof obj === "object") {
    const out: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
      out[k] = SECRET_KEYS.has(k) ? "[redacted]" : redact(v, depth + 1);
    }
    return out;
  }
  if (typeof obj === "string" && obj.length > 2048) return obj.slice(0, 2048) + "…";
  return obj;
}

export interface AuditLogEntry {
  workspace: string;
  userEmail?: string | null;
  source: "session" | "api_key" | "system" | "webhook";
  action: string;
  resourceType?: string;
  resourceId?: string;
  ok: boolean;
  params?: Record<string, unknown>;
  error?: string;
  turnId?: string;
}

/** Best-effort: never throw out of the audit path. */
export async function writeAuditLog(entry: AuditLogEntry): Promise<void> {
  try {
    await ensureTable();
    await query(
      `INSERT INTO audit_log
        (workspace, user_email, source, action, resource_type, resource_id, ok, params, error, turn_id)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, $10)`,
      [
        entry.workspace,
        entry.userEmail ?? null,
        entry.source,
        entry.action,
        entry.resourceType ?? null,
        entry.resourceId ?? null,
        entry.ok,
        entry.params ? JSON.stringify(redact(entry.params)) : null,
        entry.error ?? null,
        entry.turnId ?? null,
      ],
    );
  } catch (err) {
    log.warn("audit-log write failed (non-fatal)", {
      action: entry.action,
      err: err instanceof Error ? err.message : String(err),
    });
  }
}

export async function listAuditLog(opts: {
  workspace: string;
  limit?: number;
  action?: string;
}): Promise<unknown[]> {
  await ensureTable();
  const limit = Math.min(500, Math.max(1, opts.limit ?? 100));
  if (opts.action) {
    return query(
      `SELECT id, ts, user_email, source, action, resource_type, resource_id, ok, params, error, turn_id
       FROM audit_log WHERE workspace = $1 AND action = $2 ORDER BY ts DESC LIMIT $3`,
      [opts.workspace, opts.action, limit],
    );
  }
  return query(
    `SELECT id, ts, user_email, source, action, resource_type, resource_id, ok, params, error, turn_id
     FROM audit_log WHERE workspace = $1 ORDER BY ts DESC LIMIT $2`,
    [opts.workspace, limit],
  );
}

// Re-export for tests / migration scripts
export { getPool };
