import type { DatabaseAdapter } from "./types.js";
import { createHash } from "node:crypto";

const SENSITIVE_KEY_PATTERN =
  /(content|summary|query|text|document|source_ref|source_uri|reason|invalidation_reason|token|secret|password|authorization|cookie)/iu;

function isPlainObject(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function hashSha256(value: string): string {
  return createHash("sha256").update(value, "utf8").digest("hex");
}

function redactString(value: string): Record<string, unknown> {
  return {
    redacted: true,
    chars: value.length,
    bytes: Buffer.byteLength(value, "utf8"),
    sha256: hashSha256(value)
  };
}

export function redactAuditDetails(input: unknown): { details: unknown; redacted: boolean } {
  let redacted = false;

  const redactRecursive = (value: unknown, keyHint?: string): unknown => {
    if (typeof value === "string") {
      if (keyHint && SENSITIVE_KEY_PATTERN.test(keyHint)) {
        redacted = true;
        return redactString(value);
      }
      return value;
    }

    if (Array.isArray(value)) {
      return value.map((item) => redactRecursive(item, keyHint));
    }

    if (isPlainObject(value)) {
      const out: Record<string, unknown> = {};
      for (const [key, nested] of Object.entries(value)) {
        if (SENSITIVE_KEY_PATTERN.test(key)) {
          redacted = true;
          if (typeof nested === "string") {
            out[key] = redactString(nested);
          } else if (nested === null || nested === undefined) {
            out[key] = { redacted: true, value: null };
          } else {
            out[key] = { redacted: true, type: Array.isArray(nested) ? "array" : typeof nested };
          }
          continue;
        }
        out[key] = redactRecursive(nested, key);
      }
      return out;
    }

    return value;
  };

  return { details: redactRecursive(input), redacted };
}

/**
 * Ensures the audit_log table exists in the DB.
 */
export async function ensureAuditLogTable(db: DatabaseAdapter): Promise<void> {
  await db.exec(`
    CREATE TABLE IF NOT EXISTS audit_log (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      ts TEXT NOT NULL,
      project_id TEXT,
      user TEXT,
      action TEXT NOT NULL,
      entry_id TEXT,
      details TEXT,
      redacted INTEGER DEFAULT 0
    );
    CREATE INDEX IF NOT EXISTS idx_audit_log_project_id ON audit_log(project_id);
    CREATE INDEX IF NOT EXISTS idx_audit_log_entry_id ON audit_log(entry_id);
  `);
}

/**
 * Writes an audit log entry. Details should be a JSON-serializable object.
 */
export async function writeAuditLog(db: DatabaseAdapter, {
  project_id,
  user,
  action,
  entry_id,
  details
}: {
  project_id?: string;
  user?: string;
  action: string;
  entry_id?: string;
  details?: unknown;
}): Promise<void> {
  const ts = new Date().toISOString();
  const redactedPayload = redactAuditDetails(details);
  await db.run(
    `INSERT INTO audit_log (ts, project_id, user, action, entry_id, details, redacted)
     VALUES (?, ?, ?, ?, ?, ?, ?)`,
    [
      ts,
      project_id ?? null,
      user ?? null,
      action,
      entry_id ?? null,
      redactedPayload.details ? JSON.stringify(redactedPayload.details) : null,
      redactedPayload.redacted ? 1 : 0
    ]
  );
}
