/**
 * `shade migrate-storage` and `shade rotate-storage-key` — at-rest encryption
 * lifecycle commands (V3.2).
 *
 * Both rely on @shade/storage-encrypted; key sources mirror what KeyManager
 * accepts: passphrase | keychain | injected (env-injected).
 */

import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { mkdirSync } from 'node:fs';
import { KeyManager, type KeySource, migrateSqliteToEncrypted, rotateSqliteEncryptionKey } from '@shade/storage-encrypted';

interface CommonStorageOptions {
  dbPath?: string;
  keySource: 'passphrase' | 'keychain' | 'injected';
  passphrase?: string;
  keychainService?: string;
  keychainAccount?: string;
  injectedKeyHex?: string;
  saltFile?: string;
}

export interface MigrateCliOptions extends CommonStorageOptions {
  dryRun?: boolean;
  keepOriginal?: boolean;
  noBackup?: boolean;
}

export interface RotateCliOptions extends CommonStorageOptions {
  newKeySource: 'passphrase' | 'keychain' | 'injected';
  newPassphrase?: string;
  newKeychainService?: string;
  newKeychainAccount?: string;
  newInjectedKeyHex?: string;
  newSaltFile?: string;
}

export async function migrateStorageCommand(opts: MigrateCliOptions): Promise<void> {
  const dbPath = opts.dbPath ?? process.env.SHADE_DB_PATH ?? '/data/shade-client.db';
  const km = await openKeyManager({ ...opts, dbPath, role: 'open-or-create' });

  const log = (m: string) => console.log(`  • ${m}`);
  console.log(`Migrating ${dbPath} → encrypted at-rest schema…`);
  if (opts.dryRun) console.log('  (dry-run: no writes will be made)');

  const dryRun = opts.dryRun ?? false;
  // Respect explicit --no-backup; otherwise inherit migrate's default
  // (which is "no backup during dry-run, backup otherwise").
  const backup = opts.noBackup ? false : !dryRun;
  const report = await migrateSqliteToEncrypted({
    dbPath,
    keyManager: km,
    log,
    dryRun,
    keepOriginal: opts.keepOriginal ?? false,
    backup,
  });

  console.log('\nResult:');
  console.log(`  identity            ${report.identity}`);
  console.log(`  config              ${report.config}`);
  console.log(`  signed_prekeys      ${report.signedPrekeys}`);
  console.log(`  one_time_prekeys    ${report.oneTimePrekeys}`);
  console.log(`  sessions            ${report.sessions}`);
  console.log(`  trusted_identities  ${report.trustedIdentities}`);
  console.log(`  retired_identities  ${report.retiredIdentities}`);
  console.log(`  stream_state        ${report.streamStates}`);
  console.log(`  duration            ${report.durationMs}ms`);
  if (report.backupPath) console.log(`  backup              ${report.backupPath}`);
  if (report.dryRun) console.log('\nDry-run complete; no data was modified.');
  else if (!report.keptOriginal) console.log('\nUnencrypted tables dropped.');
}

export async function rotateStorageKeyCommand(opts: RotateCliOptions): Promise<void> {
  const dbPath = opts.dbPath ?? process.env.SHADE_DB_PATH ?? '/data/shade-client.db';
  if (!existsSync(dbPath)) throw new Error(`rotate-storage-key: DB not found at ${dbPath}`);

  console.log(`Rotating storage key for ${dbPath}…`);

  const oldKm = await openKeyManager({
    dbPath,
    role: 'open-existing',
    keySource: opts.keySource,
    ...(opts.passphrase !== undefined ? { passphrase: opts.passphrase } : {}),
    ...(opts.keychainService !== undefined ? { keychainService: opts.keychainService } : {}),
    ...(opts.keychainAccount !== undefined ? { keychainAccount: opts.keychainAccount } : {}),
    ...(opts.injectedKeyHex !== undefined ? { injectedKeyHex: opts.injectedKeyHex } : {}),
    ...(opts.saltFile !== undefined ? { saltFile: opts.saltFile } : {}),
  });
  const newKm = await openKeyManager({
    dbPath,
    role: 'open-or-create',
    keySource: opts.newKeySource,
    ...(opts.newPassphrase !== undefined ? { passphrase: opts.newPassphrase } : {}),
    ...(opts.newKeychainService !== undefined ? { keychainService: opts.newKeychainService } : {}),
    ...(opts.newKeychainAccount !== undefined ? { keychainAccount: opts.newKeychainAccount } : {}),
    ...(opts.newInjectedKeyHex !== undefined ? { injectedKeyHex: opts.newInjectedKeyHex } : {}),
    ...(opts.newSaltFile !== undefined ? { saltFile: opts.newSaltFile } : {}),
  });

  const log = (m: string) => console.log(`  • ${m}`);
  const result = await rotateSqliteEncryptionKey({ dbPath, oldKeyManager: oldKm, newKeyManager: newKm, log });
  console.log(`\nRotation complete: ${result.rowsRotated} rows re-keyed.`);
}

// ─── helpers ──────────────────────────────────────────────────────

interface OpenKmArgs extends CommonStorageOptions {
  /** open-existing: salt file MUST already exist for passphrase mode. open-or-create: generate salt if missing. */
  role: 'open-existing' | 'open-or-create';
}

async function openKeyManager(args: OpenKmArgs): Promise<KeyManager> {
  const source = await buildKeySource(args);
  if (source.kind === 'keychain') {
    const { getDefaultKeychain } = await import('@shade/keychain');
    return KeyManager.open(source, { keychain: getDefaultKeychain() });
  }
  return KeyManager.open(source);
}

async function buildKeySource(args: OpenKmArgs): Promise<KeySource> {
  switch (args.keySource) {
    case 'passphrase': {
      const passphrase = args.passphrase ?? process.env.SHADE_STORAGE_PASSPHRASE;
      if (!passphrase) throw new Error('passphrase required: pass --passphrase or set SHADE_STORAGE_PASSPHRASE');
      const dbPath = args.dbPath ?? process.env.SHADE_DB_PATH ?? '/data/shade-client.db';
      const saltPath = args.saltFile ?? `${dbPath}.salt`;
      const salt = await loadOrCreateSalt(saltPath, args.role);
      return { kind: 'passphrase', passphrase, salt };
    }
    case 'keychain': {
      const service = args.keychainService ?? process.env.SHADE_KEYCHAIN_SERVICE ?? 'shade.storage';
      const account = args.keychainAccount ?? process.env.SHADE_KEYCHAIN_ACCOUNT ?? 'default';
      return {
        kind: 'keychain', service, account,
        createIfMissing: args.role === 'open-or-create',
      };
    }
    case 'injected': {
      const hex = args.injectedKeyHex ?? process.env.SHADE_STORAGE_KEY_HEX;
      if (!hex) throw new Error('injected key required: pass --key-hex or set SHADE_STORAGE_KEY_HEX');
      const key = hexToBytes(hex);
      if (key.length !== 32) throw new Error(`injected key must be 32 bytes (got ${key.length})`);
      return { kind: 'injected', key };
    }
  }
}

async function loadOrCreateSalt(saltPath: string, role: OpenKmArgs['role']): Promise<Uint8Array> {
  if (existsSync(saltPath)) {
    const buf = readFileSync(saltPath);
    if (buf.length < 16) throw new Error(`salt file ${saltPath} is too short (need ≥ 16 bytes)`);
    return new Uint8Array(buf);
  }
  if (role === 'open-existing') {
    throw new Error(`salt file not found at ${saltPath} — re-run with the original salt or create new with migrate-storage`);
  }
  const fresh = new Uint8Array(16);
  globalThis.crypto.getRandomValues(fresh);
  mkdirSync(dirname(resolve(saltPath)), { recursive: true });
  writeFileSync(saltPath, fresh, { mode: 0o600 });
  console.log(`  • generated new salt at ${saltPath}`);
  return fresh;
}

function hexToBytes(hex: string): Uint8Array {
  const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
  if (clean.length % 2 !== 0) throw new Error('hex string must have even length');
  const out = new Uint8Array(clean.length / 2);
  for (let i = 0; i < out.length; i++) {
    out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
  }
  return out;
}

export function parseStorageArgs(args: string[]): MigrateCliOptions & RotateCliOptions {
  const out: MigrateCliOptions & RotateCliOptions = {
    keySource: 'passphrase',
    newKeySource: 'passphrase',
  };
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    switch (a) {
      case '--db-path':         out.dbPath = args[++i]; break;
      case '--key-source':      out.keySource = args[++i] as MigrateCliOptions['keySource']; break;
      case '--passphrase':      out.passphrase = args[++i]; break;
      case '--keychain-service': out.keychainService = args[++i]; break;
      case '--keychain-account': out.keychainAccount = args[++i]; break;
      case '--key-hex':         out.injectedKeyHex = args[++i]; break;
      case '--salt-file':       out.saltFile = args[++i]; break;
      case '--dry-run':         out.dryRun = true; break;
      case '--keep-original':   out.keepOriginal = true; break;
      case '--no-backup':       out.noBackup = true; break;
      // rotation
      case '--new-key-source':  out.newKeySource = args[++i] as RotateCliOptions['newKeySource']; break;
      case '--new-passphrase':  out.newPassphrase = args[++i]; break;
      case '--new-keychain-service': out.newKeychainService = args[++i]; break;
      case '--new-keychain-account': out.newKeychainAccount = args[++i]; break;
      case '--new-key-hex':     out.newInjectedKeyHex = args[++i]; break;
      case '--new-salt-file':   out.newSaltFile = args[++i]; break;
      default:
        if (a?.startsWith('--')) throw new Error(`unknown flag: ${a}`);
    }
  }
  return out;
}
