import type BetterSqlite3 from "better-sqlite3";

import type {
  AdminAuditQuery,
  AdminOperationAuditItem,
  AdminOperationAuditResponse,
  PrivateContentAccessAuditItem,
  PrivateContentAccessAuditResponse,
} from "@dada/shared-contracts";

interface AuditCursor {
  logId: string;
  occurredAt: number;
}

interface AdminOperationRow {
  actor_ref: string;
  actor_type: "system" | "super_admin";
  after_summary: string | null;
  before_summary: string | null;
  expires_at: number;
  log_id: string;
  occurred_at: number;
  operation_type: string;
  result: "failed" | "succeeded";
  target_ref: string;
  target_type: string;
}

interface PrivateContentAccessRow {
  actor_ref: string;
  content_type: "image" | "prompt";
  expires_at: number;
  log_id: string;
  occurred_at: number;
  target_ref: string;
}

export class AdminAuditQueryError extends Error {
  constructor() {
    super("admin_audit_query_invalid");
    this.name = "AdminAuditQueryError";
  }
}

function encodeCursor(row: { log_id: string; occurred_at: number }) {
  return Buffer.from(JSON.stringify([row.occurred_at, row.log_id]), "utf8").toString("base64url");
}

function decodeCursor(cursor: string | undefined): AuditCursor | undefined {
  if (!cursor) return undefined;
  try {
    const parsed: unknown = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
    if (!Array.isArray(parsed) || parsed.length !== 2 || !Number.isSafeInteger(parsed[0])
      || typeof parsed[1] !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/.test(parsed[1])) {
      throw new AdminAuditQueryError();
    }
    return { occurredAt: parsed[0] as number, logId: parsed[1] };
  } catch (error) {
    if (error instanceof AdminAuditQueryError) throw error;
    throw new AdminAuditQueryError();
  }
}

function normalizeLimit(limit: number | undefined) {
  if (limit === undefined) return 50;
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw new AdminAuditQueryError();
  return limit;
}

function pageRows<Row extends { log_id: string; occurred_at: number }>(rows: Row[], limit: number) {
  const hasMore = rows.length > limit;
  const items = hasMore ? rows.slice(0, limit) : rows;
  return { items, nextCursor: hasMore ? encodeCursor(items[items.length - 1]!) : null };
}

function iso(value: number) {
  return new Date(value).toISOString();
}

export function listAdminOperationAudit(
  database: BetterSqlite3.Database,
  query: AdminAuditQuery,
  clock: () => number = Date.now,
): AdminOperationAuditResponse {
  const cursor = decodeCursor(query.cursor);
  const limit = normalizeLimit(query.limit);
  const rows = (cursor
    ? database.prepare(`
        SELECT actor_ref, actor_type, after_summary, before_summary, expires_at, log_id,
          occurred_at, operation_type, result, target_ref, target_type
        FROM admin_operation_logs
        WHERE occurred_at < ? OR (occurred_at = ? AND log_id < ?)
        ORDER BY occurred_at DESC, log_id DESC LIMIT ?
      `).all(cursor.occurredAt, cursor.occurredAt, cursor.logId, limit + 1)
    : database.prepare(`
        SELECT actor_ref, actor_type, after_summary, before_summary, expires_at, log_id,
          occurred_at, operation_type, result, target_ref, target_type
        FROM admin_operation_logs
        ORDER BY occurred_at DESC, log_id DESC LIMIT ?
      `).all(limit + 1)) as AdminOperationRow[];
  const page = pageRows(rows, limit);
  const items: AdminOperationAuditItem[] = page.items.map((row) => ({
    actor_ref: row.actor_ref,
    actor_type: row.actor_type,
    after_summary: row.after_summary,
    before_summary: row.before_summary,
    expires_at: iso(row.expires_at),
    log_id: row.log_id,
    occurred_at: iso(row.occurred_at),
    operation_type: row.operation_type,
    result: row.result,
    target_ref: row.target_ref,
    target_type: row.target_type,
  }));
  return { generated_at: iso(clock()), items, next_cursor: page.nextCursor };
}

export function listPrivateContentAccessAudit(
  database: BetterSqlite3.Database,
  query: AdminAuditQuery,
  clock: () => number = Date.now,
): PrivateContentAccessAuditResponse {
  const cursor = decodeCursor(query.cursor);
  const limit = normalizeLimit(query.limit);
  const rows = (cursor
    ? database.prepare(`
        SELECT actor_ref, content_type, expires_at, log_id, occurred_at, target_ref
        FROM private_content_access_logs
        WHERE occurred_at < ? OR (occurred_at = ? AND log_id < ?)
        ORDER BY occurred_at DESC, log_id DESC LIMIT ?
      `).all(cursor.occurredAt, cursor.occurredAt, cursor.logId, limit + 1)
    : database.prepare(`
        SELECT actor_ref, content_type, expires_at, log_id, occurred_at, target_ref
        FROM private_content_access_logs
        ORDER BY occurred_at DESC, log_id DESC LIMIT ?
      `).all(limit + 1)) as PrivateContentAccessRow[];
  const page = pageRows(rows, limit);
  const items: PrivateContentAccessAuditItem[] = page.items.map((row) => ({
    actor_ref: row.actor_ref,
    content_type: row.content_type,
    expires_at: iso(row.expires_at),
    log_id: row.log_id,
    occurred_at: iso(row.occurred_at),
    target_ref: row.target_ref,
  }));
  return { generated_at: iso(clock()), items, next_cursor: page.nextCursor };
}
