import 'dotenv/config';
import { and, eq } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import { appState, users } from '../src/database/schema';

/**
 * One-time ownership migration.
 *
 * Before authentication was wired into the frontend, `/state` wrote every blob
 * under a fixed sentinel user. This script hands those rows to a real account
 * so the terminals keep their data once `/state` requires a session.
 *
 * Idempotent: a key the target user already owns is never overwritten — it is
 * reported and the sentinel row is left untouched for manual inspection.
 *
 *   CLAIM_EMAIL=you@example.com npm run db:claim-state:prod
 */
const SENTINEL_OWNER_ID = '00000000-0000-0000-0000-000000000001';

function requiredEnvironmentValue(name: string): string {
  const value = process.env[name]?.trim();
  if (!value) throw new Error(`${name} is required.`);
  return value;
}

const databaseUrl = requiredEnvironmentValue('DATABASE_URL');
const email = requiredEnvironmentValue('CLAIM_EMAIL').toLowerCase();

function describeSize(value: unknown): string {
  const bytes = Buffer.byteLength(JSON.stringify(value ?? null), 'utf8');
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

async function main(): Promise<void> {
  const pool = new Pool({
    connectionString: databaseUrl,
    ssl:
      process.env.DATABASE_SSL === 'true'
        ? {
            rejectUnauthorized: process.env.DATABASE_SSL_REJECT_UNAUTHORIZED !== 'false',
          }
        : false,
    application_name: 'ck-terminal-claim-state',
  });

  try {
    const db = drizzle(pool);

    const [target] = await db
      .select({ id: users.id, email: users.email })
      .from(users)
      .where(eq(users.email, email))
      .limit(1);

    if (!target) {
      throw new Error(`No user found for ${email}. Create the account first.`);
    }
    if (target.id === SENTINEL_OWNER_ID) {
      throw new Error('Refusing to claim the sentinel user onto itself.');
    }

    const sentinelRows = await db
      .select({ key: appState.key, value: appState.value })
      .from(appState)
      .where(eq(appState.ownerId, SENTINEL_OWNER_ID));

    if (!sentinelRows.length) {
      console.log('No sentinel-owned state rows found. Nothing to claim.');
      return;
    }

    const ownedRows = await db
      .select({ key: appState.key })
      .from(appState)
      .where(eq(appState.ownerId, target.id));
    const ownedKeys = new Set(ownedRows.map((row) => row.key));

    const moved: string[] = [];
    const skipped: string[] = [];

    await db.transaction(async (transaction) => {
      for (const row of sentinelRows) {
        if (ownedKeys.has(row.key)) {
          skipped.push(row.key);
          continue;
        }
        await transaction
          .update(appState)
          .set({ ownerId: target.id, updatedAt: new Date() })
          .where(and(eq(appState.ownerId, SENTINEL_OWNER_ID), eq(appState.key, row.key)));
        moved.push(row.key);
        console.log(`  moved  ${row.key}  (${describeSize(row.value)})`);
      }
    });

    console.log(`\nClaimed ${moved.length} state row(s) for ${target.email}.`);
    if (skipped.length) {
      console.log(
        `Skipped ${skipped.length} key(s) already owned by ${target.email}: ${skipped.join(', ')}`,
      );
      console.log('The sentinel copies were left in place — inspect them before deleting.');
    }
  } finally {
    await pool.end();
  }
}

void main();
