import { createHash } from "node:crypto";
import { chmod } from "node:fs/promises";

import postgres from "postgres";

interface InstanceRow {
  id: string;
  name: string;
  file_path: string;
  history_path: string;
  created_at: string;
  updated_at: string;
  history_pointer: number;
  todos: unknown;
  source: unknown;
}

interface HistoryRow {
  instance_id: string;
  sequence: number;
  timestamp: string;
  kind: string;
  action: string;
  snapshot: unknown;
  detail: unknown;
}

interface ReminderNotificationRow {
  instance_id: string;
  todo_id: string;
  reminder_at: string;
  target: string;
  title: string;
  message: string;
  response_preview: string;
  sent_at: string;
}

function outputPath(): string {
  const index = Bun.argv.indexOf("--output");
  const value = index >= 0 ? Bun.argv[index + 1] : process.env.TODO_NOTE_EXPORT_PATH;
  if (!value) throw new Error("--output PATH is required");
  return value;
}

async function main(): Promise<void> {
  const databaseUrl = process.env.DATABASE_URL;
  if (!databaseUrl) throw new Error("DATABASE_URL is required");
  const sql = postgres(databaseUrl, {
    max: 1,
    connect_timeout: 10,
    connection: { application_name: "todo-note-cc01-export" },
  });
  try {
    const instances = await sql<InstanceRow[]>`
      SELECT id, name, file_path, history_path, created_at, updated_at, history_pointer, todos, source
      FROM todo_note_instances
      ORDER BY created_at ASC, id ASC
    `;
    const histories = await sql<HistoryRow[]>`
      SELECT instance_id, sequence, timestamp, kind, action, snapshot, detail
      FROM todo_note_history
      ORDER BY instance_id ASC, id ASC
    `;
    const reminderNotifications = await sql<ReminderNotificationRow[]>`
      SELECT instance_id, todo_id, reminder_at, target, title, message, response_preview, sent_at::text AS sent_at
      FROM todo_note_reminder_notifications
      ORDER BY id ASC
    `;
    const historyByInstance = new Map<string, HistoryRow[]>();
    for (const row of histories) {
      const entries = historyByInstance.get(row.instance_id) ?? [];
      entries.push(row);
      historyByInstance.set(row.instance_id, entries);
    }
    const snapshot = {
      schemaVersion: 1,
      generatedAt: new Date().toISOString(),
      instances: instances.map((row) => ({
        instance: {
          id: row.id,
          name: row.name,
          filePath: row.file_path,
          historyPath: row.history_path,
          createdAt: row.created_at,
          updatedAt: row.updated_at,
          historyPointer: Number(row.history_pointer),
          todos: row.todos,
        },
        history: (historyByInstance.get(row.id) ?? []).map((entry) => ({
          sequence: Number(entry.sequence),
          timestamp: entry.timestamp,
          kind: entry.kind,
          action: entry.action,
          ...(entry.snapshot === null ? {} : { snapshot: entry.snapshot }),
          ...(entry.detail === null ? {} : { detail: entry.detail }),
        })),
        source: typeof row.source === "object" && row.source !== null ? row.source : {},
      })),
      reminderNotifications: reminderNotifications.map((row) => ({
        instanceId: row.instance_id,
        todoId: row.todo_id,
        reminderAt: row.reminder_at,
        target: row.target,
        title: row.title,
        message: row.message,
        responsePreview: row.response_preview,
        sentAt: row.sent_at,
      })),
    };
    const serialized = `${JSON.stringify(snapshot, null, 2)}\n`;
    const target = outputPath();
    await Bun.write(target, serialized);
    await chmod(target, 0o600);
    process.stdout.write(`${JSON.stringify({
      ok: true,
      output: target,
      instances: instances.length,
      historyEntries: histories.length,
      reminderNotifications: reminderNotifications.length,
      bytes: Buffer.byteLength(serialized),
      sha256: createHash("sha256").update(serialized).digest("hex"),
    })}\n`);
  } finally {
    await sql.end({ timeout: 5 });
  }
}

if (import.meta.main && !process.execArgv.includes("--check")) {
  main().catch((error) => {
    process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
    process.exitCode = 1;
  });
}
