import { hostname } from 'node:os';
import { isValidPhrase, normalizePhrase, wordCount } from '@athsra/crypto';
import type { UserAuthContext } from '../lib/auth-context.ts';
import { deriveLegacyMasterProofs, deriveMasterProof } from '../lib/auth-proof.ts';
import { resolveProject } from '../lib/auto-project.ts';
import { AthsraClient } from '../lib/client.ts';
import { type Config, loadConfig, saveConfig } from '../lib/config.ts';
import {
  completeIdentityLogin,
  DEFAULT_WORKER_URL,
  type IdentityFlow,
  runDevicePollLoop,
  startIdentityFlow,
} from '../lib/device-login.ts';
import { readPlain } from '../lib/envelope.ts';
import { errMessage, isRecord } from '../lib/err.ts';
import { ensureKeypair } from '../lib/identity-key.ts';
import { probeKeyring, setDeviceToken, setMasterPw, setToken } from '../lib/keyring.ts';
import { consumeLegacySession } from '../lib/legacy-session.ts';
import {
  openBrowser,
  resolveOidcEndpoints,
  runOidcPkceFlow,
  SSO_DEFAULTS,
} from '../lib/oidc-flow.ts';
import { promptConfirm, promptPassword, promptText } from '../lib/prompt.ts';

/**
 * Phase 4 Slice 3 — login 후 X25519 identity 키쌍 보장 (best-effort). 키쌍은 org 공유 시크릿
 * 복호용. 실패해도 login 자체는 성공 유지(다음 login 에 재시도) — provisioning 은 substrate 이고
 * 실제 공유는 Slice 6. master pw 로 wrap 된 privkey 만 server 보관(평문 미노출).
 */
async function provisionIdentityKey(client: AthsraClient, masterPw: string): Promise<void> {
  // identity key = "어디서든 브라우저 device-login" 의 substrate (다른 머신이 sealed key 를 받는 전제).
  // transient 실패에 1회 재시도, 영구 실패면 silent 가 아니라 결과(다른 머신 device-login 불가)를
  // 명시 + 멱등 복구 경로 안내. master-pw 사용 자체는 정상이므로 login 을 hard-fail 하진 않는다.
  for (let attempt = 1; attempt <= 2; attempt++) {
    try {
      const created = await ensureKeypair(client, masterPw);
      console.log(created ? '  identity key: provisioned ✓' : '  identity key: present ✓');
      return;
    } catch (err) {
      if (attempt < 2) continue;
      const msg = err instanceof Error ? err.message : String(err);
      console.warn(`  ⚠ identity key 미provisioning (${msg})`);
      console.warn(
        '     → 다른 머신·위치에서 브라우저 device-login 이 불가합니다 (이 머신의 master-pw 접근은 정상).',
      );
      console.warn(
        '     → 복구: 네트워크 확인 후 `athsra login` 재실행(멱등). 상태는 `athsra doctor` 로 확인.',
      );
    }
  }
}

/**
 * Phase 2.8 (2026-05-26) — Connect SSO 로그인 (OIDC PKCE).
 *
 * 흐름:
 *   1) PKCE code_verifier + challenge 생성
 *   2) localhost callback HTTP server start (ephemeral port)
 *   3) browser open → Connect /authorize
 *   4) callback ?code= → POST Connect /token (code + verifier) → access_token
 *   5) POST worker /auth/sso (access_token + label) → athsra Bearer token
 *   6) keyring 저장 + master pw prompt (envelope decrypt 용)
 *
 * E2EE 정합: SSO 는 worker 인증 layer 만. master pw 는 envelope decrypt 전용.
 * PKCE 플로우 기계 부분(step 1-4)은 lib/oidc-flow.ts 로 분리 (2026-06-02 리팩토링).
 */

async function ssoLoginCmd(): Promise<number> {
  console.log('athsra login --sso (modfolio-connect OIDC PKCE)\n');

  // 1. keyring backend
  const probe = probeKeyring();
  if (!probe.ok) {
    console.error(`✗ keyring backend unavailable: ${probe.error ?? 'unknown'}`);
    return 1;
  }

  // 2. config — workerUrl + machineId
  const existing = loadConfig();
  const envUrl = process.env.ATHSRA_WORKER_URL;
  const workerUrl =
    existing?.workerUrl ??
    envUrl ??
    (await promptText('Worker URL', 'https://athsra-worker.winterermod.workers.dev'));
  const machineId = existing?.machineId ?? `${hostname()}-${Date.now().toString(36)}`;

  // 3. worker reachability
  const tempClient = new AthsraClient(workerUrl);
  const reachable = await tempClient.health();
  if (!reachable) {
    console.error(`✗ worker unreachable: ${workerUrl}`);
    return 1;
  }

  // 4. OIDC PKCE 플로우 (browser → Connect /authorize → callback → /token) → access_token.
  //    env override 해석 후 lib/oidc-flow.ts 에 위임. 실패 시 throw → `✗ ...` + exit 1.
  let accessToken: string;
  try {
    // endpoint 는 OIDC discovery 로 해석 (하드코딩 /authorize 금지 — connect 는 /sso/* 로 광고).
    const endpoints = await resolveOidcEndpoints(
      { discoveryUrl: process.env.ATHSRA_SSO_DISCOVERY_URL ?? SSO_DEFAULTS.discoveryUrl },
      {
        authorizeUrl: process.env.ATHSRA_SSO_AUTHORIZE_URL,
        tokenUrl: process.env.ATHSRA_SSO_TOKEN_URL,
      },
    );
    console.log(`  authorize: ${endpoints.authorizeUrl}`);
    const flow = await runOidcPkceFlow({
      authorizeUrl: endpoints.authorizeUrl,
      tokenUrl: endpoints.tokenUrl,
      clientId: process.env.ATHSRA_SSO_CLIENT_ID ?? SSO_DEFAULTS.clientId,
      scope: SSO_DEFAULTS.scope,
      callbackTimeoutMs: SSO_DEFAULTS.callbackTimeoutMs,
    });
    accessToken = flow.accessToken;
  } catch (err) {
    console.error(`✗ ${errMessage(err)}`);
    return 1;
  }

  // 5. worker /auth/sso (Connect token → athsra Bearer)
  const ssoRes = await fetch(`${workerUrl}/auth/sso`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ access_token: accessToken, label: machineId }),
  });
  if (!ssoRes.ok) {
    console.error(`✗ athsra worker SSO failed: ${ssoRes.status} ${await ssoRes.text()}`);
    return 1;
  }
  const ssoBodyRaw: unknown = await ssoRes.json();
  if (!isRecord(ssoBodyRaw)) {
    console.error('✗ athsra worker SSO: 예상치 못한 응답 형식');
    return 1;
  }
  const token = ssoBodyRaw.token;
  const userId = ssoBodyRaw.user_id;
  const identifier = ssoBodyRaw.identifier;
  const createdAt = ssoBodyRaw.createdAt;
  const expiresAt = ssoBodyRaw.expires_at;
  if (
    typeof token !== 'string' ||
    typeof userId !== 'number' ||
    typeof identifier !== 'string' ||
    typeof createdAt !== 'string' ||
    (expiresAt !== undefined && typeof expiresAt !== 'string')
  ) {
    console.error('✗ athsra worker SSO: 예상치 못한 응답 형식');
    return 1;
  }
  const ssoBody = { token, user_id: userId, identifier, createdAt, expires_at: expiresAt };

  // 6. master pw prompt (envelope decrypt 전용 — worker 에 송신 X)
  console.log('\n● Master password — envelope decrypt (E2EE, worker 노출 X)');
  const envPw = process.env.ATHSRA_MASTER_PW;
  let masterPw: string;
  if (envPw && envPw.length >= 8) {
    masterPw = envPw;
    console.log('• Master password from $ATHSRA_MASTER_PW (non-interactive).');
  } else {
    masterPw = await promptPassword('Master password (8+ chars)');
    if (masterPw.length < 8) {
      console.error('Master password must be at least 8 characters');
      return 1;
    }
  }
  if (isValidPhrase(masterPw)) {
    const wc = wordCount(masterPw);
    console.log(`• Master password is a valid BIP-39 ${wc}-word phrase ✓`);
    masterPw = normalizePhrase(masterPw);
  }

  // 6.5. master pw proof bootstrap-or-verify (POST /auth/proof) — Phase 3a.
  //   proof = Argon2id(masterPw, perUserSalt(user_id, GLOBAL_SALT)) 단방향 해시 — 평문 송신 X (E2EE).
  //   G-1: per-user salt 로 같은 pw 두 user 도 proof 가 유일. 첫 SSO = bootstrap, 재로그인 = 검증.
  const info = await tempClient.info();
  const proof = deriveMasterProof(masterPw, ssoBody.user_id, info.global_salt);
  // 버전 업그레이드 무중단: 저장 proof 가 옛 스킴(gen-0)이면 worker 가 이 후보로 검증 후 G-1 자동
  // 재작성. legacy proof 는 per-user salt 없는 gen-0 이라 userId 불필요 — 그대로 사용.
  const legacyProofs = deriveLegacyMasterProofs(masterPw, info.global_salt);
  try {
    const pr = await new AthsraClient(workerUrl, ssoBody.token).setProof(
      proof,
      info.global_salt_version,
      legacyProofs,
    );
    if (pr.userId !== ssoBody.user_id) {
      console.error(`✗ proof user mismatch (${pr.userId} ≠ ${ssoBody.user_id}) — 재로그인 필요.`);
      return 1;
    }
    if (pr.version_reset) {
      console.log('• Master password re-registered (GLOBAL_SALT changed).');
    } else if (pr.migrated) {
      console.log(
        '• Master password 옛 스킴 → 현 스킴 자동 마이그레이션 ✓ (버전 업그레이드 무중단).',
      );
    } else if (pr.bootstrap) {
      console.log('• Master password set for this account ✓ (worker stores one-way proof only).');
    } else {
      console.log('• Master password verified ✓.');
    }
  } catch (err) {
    const msg = errMessage(err);
    if (msg.includes('409')) {
      console.error('✗ master password mismatch — 이 계정에 설정된 master pw 와 다릅니다.');
      console.error('  올바른 master pw 로 다시 로그인하세요 (틀린 pw 는 저장하지 않습니다).');
      return 1;
    }
    console.error(`✗ master password proof failed: ${msg}`);
    return 1;
  }

  // 7. config + keyring 저장
  const config: Config = {
    workerUrl,
    machineId,
    createdAt: existing?.createdAt ?? ssoBody.createdAt,
    userId: ssoBody.user_id,
  };
  saveConfig(config);
  setMasterPw(machineId, masterPw);
  setToken(machineId, ssoBody.token);
  await provisionIdentityKey(new AthsraClient(workerUrl, ssoBody.token), masterPw);

  console.log(`\n✓ SSO logged in (machine: ${machineId})`);
  console.log(`  identifier: ${ssoBody.identifier}`);
  console.log(`  worker:     ${workerUrl}`);
  if (ssoBody.expires_at) {
    console.log(`  expires:    ${ssoBody.expires_at}`);
  }
  console.log('  keyring:    master-pw + token saved (OS keyring)');
  return 0;
}

interface DeviceArgs {
  project?: string;
  perms: 'read' | 'write';
  noBrowser: boolean;
}

function parseDeviceArgs(args: string[]): DeviceArgs {
  let project: string | undefined;
  let perms: 'read' | 'write' = 'read';
  let noBrowser = false;
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    if (a === '--project' || a === '-p') {
      const v = args[i + 1];
      if (v && !v.startsWith('-')) {
        project = v;
        i++;
      }
    } else if (a === '--write') {
      perms = 'write';
    } else if (a === '--perms') {
      const v = args[i + 1];
      if (v) {
        perms = v === 'write' ? 'write' : 'read';
        i++;
      }
    } else if (a === '--no-browser') {
      noBrowser = true;
    } else if (a && a !== '--device' && !a.startsWith('-') && !project) {
      project = a;
    }
  }
  return { project, perms, noBrowser };
}

/**
 * Phase 3 P3 (2026-05-31) — device-login (RFC 8628 적응).
 *
 * 비-TTY agent 가 master pw 인터랙티브 프롬프트 없이 project-scoped 인증.
 * `athsra login --device [--project <p>] [--write]` → user_code + URL 출력 → 사용자가
 * athsra.com/device 에서 1-click 승인 → poll 로 ats_* 수령 → keyring 저장.
 * 이후 `athsra run <p>` 가 master pw 없이 동작.
 */
async function deviceLoginCmd(args: string[]): Promise<number> {
  const { project: explicitProject, perms, noBrowser } = parseDeviceArgs(args);
  // project: 명시 --project > cwd 자동 감지 (basename > .athsra > package.json)
  const project = explicitProject ?? resolveProject([]).project ?? undefined;
  if (!project) {
    console.error(
      'project 미지정 — `athsra login --device --project <name>` 또는 project repo 안에서 실행.',
    );
    return 1;
  }

  const probe = probeKeyring();
  if (!probe.ok) {
    console.error(`✗ keyring backend unavailable: ${probe.error ?? 'unknown'}`);
    return 1;
  }

  const existing = loadConfig();
  const workerUrl = process.env.ATHSRA_WORKER_URL ?? existing?.workerUrl ?? DEFAULT_WORKER_URL;
  const machineId = existing?.machineId ?? `${hostname()}-${Date.now().toString(36)}`;

  const client = new AthsraClient(workerUrl);
  if (!(await client.health())) {
    console.error(`✗ worker unreachable: ${workerUrl}`);
    return 1;
  }

  let dc: Awaited<ReturnType<typeof client.deviceCode>>;
  try {
    dc = await client.deviceCode({ project, perms, label: machineId });
  } catch (err) {
    console.error(`✗ device code 발급 실패: ${errMessage(err)}`);
    return 1;
  }

  console.log('\n● athsra device-login — 브라우저에서 승인이 필요합니다\n');
  console.log(`  1. 열기:  ${dc.verification_uri_complete}`);
  console.log(`  2. 코드:  ${dc.user_code}   (project: ${project}, perms: ${perms})`);
  console.log('\n  athsra.com 에 SSO 로그인 후 "승인" 클릭하면 자동 진행됩니다.');
  console.log('  (master pw 는 그 브라우저에서 1회 — 이 기기엔 저장되지 않습니다)\n');
  if (!noBrowser) openBrowser(dc.verification_uri_complete);

  // poll (lib/device-login.ts 공용 루프 — Phase 5 B5 추출)
  process.stdout.write('  대기 중');
  const outcome = await runDevicePollLoop(client, dc.device_code, {
    intervalMs: Math.max(1, dc.interval) * 1000,
    expiresAt: Date.now() + dc.expires_in * 1000,
    onTick: () => process.stdout.write('.'),
    onRefresh: ({ userCode, verificationUriComplete }) => {
      // 만료 임박 → 재발급 없이 새 user_code 로 갱신(세션 유지). 구 코드는 무효 → 화면에 새 코드 재출력.
      console.log('\n\n  ↻ 코드 만료 임박 — 새 코드로 갱신됨(로그인 세션 유지):');
      console.log(`  1. 열기:  ${verificationUriComplete}`);
      console.log(`  2. 코드:  ${userCode}   (project: ${project}, perms: ${perms})`);
      process.stdout.write('  대기 중');
    },
  });
  if (outcome.step === 'denied') {
    console.error('\n\n✗ 승인이 거부되었습니다.');
    return 1;
  }
  if (outcome.step === 'expired') {
    console.error('\n\n✗ 요청이 만료되었습니다. `athsra login --device` 재시도.');
    return 1;
  }
  if (outcome.step === 'timeout') {
    console.error('\n\n✗ 승인 대기 시간 초과. `athsra login --device` 재시도.');
    return 1;
  }
  const result = outcome.result;
  if (result.tokenType !== 'service') {
    console.error('\n\n✗ 예상치 못한 user-kind 토큰 (service device-login).');
    return 1;
  }
  setDeviceToken(project, result.token);
  saveConfig({
    workerUrl,
    machineId,
    createdAt: existing?.createdAt ?? new Date().toISOString(),
  });
  console.log('\n\n✓ device-login 완료');
  console.log(`  project: ${result.project} (${result.perms})`);
  console.log('  keyring: device token 저장 (master pw 불필요)');
  console.log(`  사용:    athsra run ${result.project} -- <command>\n`);
  return 0;
}

/** identity login 후 best-effort 검증 — whoami (실패해도 login 성공 유지). */
async function verifyIdentityLogin(client: AthsraClient): Promise<void> {
  try {
    const me = await client.whoami();
    if (me.userId !== undefined) {
      console.log(`  verified:   user #${me.userId} ✓`);
    }
  } catch {
    console.warn('  verify:     보류 (login 자체는 성공 — 다음 명령에서 재확인).');
  }
}

/**
 * Phase 5 (2026-06-12) — identity device-login (기본 `athsra login`).
 *
 * 브라우저 완결 흐름 (TTY 0회, master pw 이 머신 미보관):
 *   1. 디바이스 X25519 키쌍 생성 (privkey 는 이 머신에만 — sealed-box unseal 용)
 *   2. deviceCode(kind:user, device_pub_key) → user_code + URL + fingerprint 출력 + openBrowser
 *   3. 사용자가 athsra.com/device 에서 로그인 + master pw 로 identity priv 봉인 → complete
 *   4. poll → {token(atk_*), sealed_identity_key, user_id, key_version}
 *   5. unseal(user_id 대조) → identity priv → keyring 저장 + token + config.userId
 *   6. whoami best-effort 검증
 */
async function identityLoginCmd(args: string[]): Promise<number> {
  const noBrowser = args.includes('--no-browser');

  // 1. flow 시작 (keyring probe → config/worker 해석 → 키쌍 생성 → deviceCode) —
  //    lib/device-login.ts 공용 코어 (Phase 5 B5 추출, MCP athsra_login_start 와 동일 경로).
  let flow: IdentityFlow;
  try {
    flow = await startIdentityFlow();
  } catch (err) {
    console.error(`✗ ${errMessage(err)}`);
    return 1;
  }

  console.log('\n● athsra login — 브라우저에서 승인이 필요합니다\n');
  console.log(`  1. 열기:    ${flow.verificationUriComplete}`);
  console.log(`  2. 코드:    ${flow.userCode}`);
  console.log(
    `  3. 지문:    ${flow.fingerprint}   ← 브라우저 화면의 지문과 일치하는지 확인 (phishing 가드)`,
  );
  console.log('\n  athsra.com 에 로그인 후 master password 로 승인하면 자동 진행됩니다.');
  console.log('  (master password 는 그 브라우저에서만 — 이 기기엔 저장되지 않습니다)\n');
  if (!noBrowser) openBrowser(flow.verificationUriComplete);

  // 2. poll (공용 루프)
  process.stdout.write('  대기 중');
  const outcome = await runDevicePollLoop(flow.client, flow.deviceCode, {
    intervalMs: flow.intervalMs,
    expiresAt: flow.expiresAt,
    onTick: () => process.stdout.write('.'),
    onRefresh: ({ userCode, verificationUriComplete }) => {
      // 만료 임박 → 재발급 없이 새 user_code 로 갱신(세션·지문 유지). 구 코드 무효 → 새 코드 재출력.
      console.log('\n\n  ↻ 코드 만료 임박 — 새 코드로 갱신됨(로그인 세션·지문 유지):');
      console.log(`  1. 열기:    ${verificationUriComplete}`);
      console.log(`  2. 코드:    ${userCode}`);
      process.stdout.write('  대기 중');
    },
  });
  if (outcome.step === 'denied') {
    console.error('\n\n✗ 승인이 거부되었습니다.');
    return 1;
  }
  if (outcome.step === 'expired') {
    console.error('\n\n✗ 요청이 만료되었습니다. `athsra login` 재시도.');
    return 1;
  }
  if (outcome.step === 'timeout') {
    console.error('\n\n✗ 승인 대기 시간 초과. `athsra login` 재시도.');
    return 1;
  }
  const result = outcome.result;
  if (result.tokenType !== 'user') {
    console.error('\n\n✗ 예상치 못한 service 토큰 (identity login).');
    return 1;
  }

  // 3. unseal(user_id 대조) → keyring identity+token → config.userId (공용 완료 처리)
  try {
    await completeIdentityLogin({
      result,
      devicePrivateKey: flow.devicePrivateKey,
      machineId: flow.machineId,
      workerUrl: flow.workerUrl,
      existingCreatedAt: flow.existingCreatedAt,
    });
  } catch (err) {
    console.error(`\n\n✗ identity key unseal 실패: ${errMessage(err)}`);
    return 1;
  }
  console.log('\n\n✓ logged in (identity 모드)');
  console.log(`  machine:    ${flow.machineId}`);
  console.log(`  worker:     ${flow.workerUrl}`);
  console.log('  keyring:    identity key + token 저장 (master pw 이 기기엔 없음)');
  // 4. best-effort 검증
  await verifyIdentityLogin(new AthsraClient(flow.workerUrl, result.token));
  return 0;
}

const LOGIN_USAGE = [
  'usage: athsra login [--password | --sso | --device]',
  '',
  '기본 (identity device-login, master pw 이 기기 미보관): athsra login',
  'master pw register (founding owner, full 권한): athsra login --password',
  'SSO (modfolio-connect OIDC PKCE — Phase 2.8): athsra login --sso',
  'device (비-TTY agent, project-scoped — Phase 3 P3): athsra login --device [--project <p>] [--write]',
  '',
  'device-login: agent 가 user_code 출력 → 사용자가 athsra.com/device 에서 1-click 승인 →',
  '  project-scoped ats_* 수령 (keyring 저장). master pw 는 승인 브라우저에서만 사용.',
  '',
  '환경변수:',
  '  ATHSRA_WORKER_URL           worker URL (config 우선)',
  '  ATHSRA_MASTER_PW            non-interactive master pw',
  '  ATHSRA_PAPER_BACKUP_CONFIRMED=1   paper-backup prompt 우회',
  '  ATHSRA_SSO_DISCOVERY_URL    OIDC discovery URL (default: login.modfolio.io/.well-known/openid-configuration)',
  '  ATHSRA_SSO_AUTHORIZE_URL    authorize endpoint override (둘 다 설정 시 discovery 생략)',
  '  ATHSRA_SSO_TOKEN_URL        token endpoint override (둘 다 설정 시 discovery 생략)',
  '  ATHSRA_SSO_CLIENT_ID        Connect client_id (default: athsra-cli)',
].join('\n');

export async function loginCmd(args: string[]): Promise<number> {
  if (args.includes('--help') || args.includes('-h')) {
    console.log(LOGIN_USAGE);
    return 0;
  }
  if (args.includes('--device')) {
    return deviceLoginCmd(args);
  }
  if (args.includes('--sso')) {
    return ssoLoginCmd();
  }
  if (args.includes('--password')) {
    return passwordLoginCmd();
  }
  return identityLoginCmd(args);
}

/**
 * 기존 기본 — founding(user 1) master pw register. Phase 5 부터 `--password` 로 명시.
 * identity 모드(신규 기본)와 달리 이 머신 keyring 에 master pw 보관 (full owner 권한).
 */
async function passwordLoginCmd(): Promise<number> {
  console.log('athsra login --password\n');

  // 1. keyring backend probe (정공법: fallback 없음)
  const probe = probeKeyring();
  if (!probe.ok) {
    console.error(`✗ keyring backend unavailable: ${probe.error ?? 'unknown'}`);
    console.error('  Run `athsra doctor` for setup instructions (apt install + dbus).');
    return 1;
  }

  // 2. legacy ~/.athsra/session 발견 시 1회 migration
  const legacy = consumeLegacySession();
  if (legacy) {
    console.log('• Detected legacy ~/.athsra/session — will migrate to keyring + remove file.\n');
  }

  // 3. config — workerUrl + machineId ($ATHSRA_WORKER_URL 우선)
  const existing = loadConfig();
  const envUrl = process.env.ATHSRA_WORKER_URL;
  const workerUrl =
    existing?.workerUrl ??
    envUrl ??
    (await promptText('Worker URL', 'https://athsra-worker.winterermod.workers.dev'));
  const machineId = existing?.machineId ?? `${hostname()}-${Date.now().toString(36)}`;

  // 4. master pw
  //    우선순위: legacy session > $ATHSRA_MASTER_PW (non-interactive) > prompt
  let masterPw: string;
  const envPw = process.env.ATHSRA_MASTER_PW;
  if (legacy) {
    masterPw = legacy.masterPw;
    console.log('• Using master password from legacy session.');
  } else if (envPw && envPw.length >= 8) {
    masterPw = envPw;
    console.log('• Master password from $ATHSRA_MASTER_PW (non-interactive mode).');
  } else {
    masterPw = await promptPassword('Master password (8+ chars)');
    if (masterPw.length < 8) {
      console.error('Master password must be at least 8 characters');
      return 1;
    }
    const confirm = await promptPassword('Confirm');
    if (masterPw !== confirm) {
      console.error('Passwords do not match');
      return 1;
    }
  }

  // 5. paper-backup confirm + BIP-39 phrase detect (legacy 는 skip)
  //    $ATHSRA_PAPER_BACKUP_CONFIRMED=1 명시 시 prompt 우회 (호출자 명시 ack)
  if (!legacy) {
    if (isValidPhrase(masterPw)) {
      const wc = wordCount(masterPw);
      console.log(
        `\n• Master password is a valid BIP-39 ${wc}-word phrase ✓ (recommended, checksum verified).`,
      );
      // BIP-39 phrase 는 normalize 후 사용 (소문자 + single space)
      masterPw = normalizePhrase(masterPw);
    } else {
      console.log(
        '\n• Tip: `athsra new-phrase` 로 BIP-39 12-word phrase 생성 가능 (분실 risk 완화).',
      );
    }

    console.log(
      '\n⚠ CRITICAL: master password 분실 = 모든 secret 영구 loss (Phase 1 = recovery 없음).',
    );
    console.log('  반드시 종이에 적어 안전한 곳에 보관하세요.');
    if (process.env.ATHSRA_PAPER_BACKUP_CONFIRMED === '1') {
      console.log('• $ATHSRA_PAPER_BACKUP_CONFIRMED=1 — paper-backup ack 자동 적용.');
    } else {
      const acked = await promptConfirm(
        'I have written down my master password on paper and stored it safely',
      );
      if (!acked) {
        console.error('Aborted — please backup your master password before continuing.');
        return 1;
      }
    }
  }

  // 6. worker reachability + global_salt fetch
  const tempClient = new AthsraClient(workerUrl);
  const reachable = await tempClient.health();
  if (!reachable) {
    console.error(`✗ worker unreachable: ${workerUrl}`);
    return 1;
  }
  const info = await tempClient.info();
  if (info.phase < 1) {
    console.error(`✗ worker phase=${info.phase} — Pre-A 인증 미배포. wrangler deploy 후 재시도.`);
    return 1;
  }

  // Phase 1.x.5: GLOBAL_SALT_VERSION change 감지 안내
  if (info.proof_invalidated) {
    console.log('');
    console.log(
      `⚠ GLOBAL_SALT_VERSION 변경 감지 (PROOF: ${info.proof_global_salt_version} → worker: ${info.global_salt_version}).`,
    );
    console.log('  옛 PROOF 가 다음 register 시 자동 invalidate 됩니다.');
    console.log('  R2 envelope 은 영향 없습니다 (per-envelope salt 사용 — secret data 보존).');
    console.log('  동일 master password 로 재 register 진행합니다.\n');
  }

  // 7. master_pw_proof = Argon2id(pw, perUserSalt(user_id, GLOBAL_SALT)) — G-1 per-user salt.
  // password register 는 founding singleton(user 1) 전용(SSO 외 유일 경로) — userId=1 고정.
  // 다중 사용자 self-serve password register 는 out-of-scope(SSO-gated 유지).
  const FOUNDING_USER_ID = 1;
  const proofBase64 = deriveMasterProof(masterPw, FOUNDING_USER_ID, info.global_salt);
  // 버전 업그레이드 무중단: 저장 proof 가 옛 스킴(≤1.0.2)이면 worker 가 이 후보로 검증 후 G-1 자동 재작성.
  const legacyProofs = deriveLegacyMasterProofs(masterPw, info.global_salt);

  // 8. register → token
  let reg: Awaited<ReturnType<typeof tempClient.register>>;
  try {
    reg = await tempClient.register(proofBase64, machineId, legacyProofs);
    if (reg.userId !== undefined && reg.userId !== FOUNDING_USER_ID) {
      console.error(
        `✗ register user mismatch (${reg.userId} ≠ ${FOUNDING_USER_ID}) — worker 확인 필요.`,
      );
      return 1;
    }
  } catch (err) {
    const msg = errMessage(err);
    if (msg.includes('401')) {
      console.error('✗ master password mismatch — 이미 등록된 master pw 와 다릅니다.');
      console.error('  올바른 master pw 입력 또는 기존 머신에서 revoke 후 재시도.');
    } else {
      console.error(`✗ register failed: ${msg}`);
    }
    return 1;
  }

  // 9. config + keyring 저장
  const config: Config = {
    workerUrl,
    machineId,
    createdAt: existing?.createdAt ?? reg.createdAt,
    userId: reg.userId ?? FOUNDING_USER_ID,
  };
  saveConfig(config);
  setMasterPw(machineId, masterPw);
  setToken(machineId, reg.token);
  const userClient = new AthsraClient(workerUrl, reg.token);
  await provisionIdentityKey(userClient, masterPw);

  // bootstrap typo-guard — worker 는 proof 만 저장(평문 pw·키 모름)하므로 오타 pw 도 bootstrap 된다.
  // 기존 envelope 가 있으면 실제 복호를 시도해, 잘못된 pw 로 인한 "조용한 lockout" 을 즉시 경고로 전환.
  // (proof 검증 경로[verify/migrate]는 pw 가 이미 맞으므로 bootstrap 일 때만 검사 = 로그인 지연 최소.)
  if (reg.bootstrap) {
    try {
      const projects = await userClient.listProjects();
      const probe = projects[0];
      if (probe) {
        const probeCtx: UserAuthContext = {
          kind: 'user',
          config,
          masterPw,
          token: reg.token,
          client: userClient,
        };
        await readPlain(probeCtx, probe); // 잘못된 pw 면 decrypt 에서 throw
      }
    } catch (err) {
      const msg = errMessage(err);
      if (msg.includes('decrypt') || msg.includes('auth tag')) {
        console.error(
          '\n⚠ 경고: 방금 설정한 master password 가 기존 secret 을 복호화하지 못합니다 (오타 가능성 높음).',
        );
        console.error(
          '  secret 데이터는 안전합니다 — 올바른 password 로 `athsra login --password` 를 다시 실행하세요.',
        );
        console.error(
          '  (worker 는 proof 만 보관 → password 자체는 검증 불가. 이 복호 검사가 유일한 typo 방어선입니다.)',
        );
      }
      // 그 외(네트워크 등)는 무시 — 로그인 자체는 성공.
    }
  }

  console.log(`\n✓ logged in (machine: ${machineId})`);
  console.log(`  worker:   ${workerUrl}`);
  console.log(`  config:   ~/.athsra/config.json`);
  console.log('  keyring:  master-pw + token saved (OS keyring, 무기한)');
  if (reg.migrated) {
    console.log('  proof:    옛 스킴 → 현 스킴 자동 마이그레이션 ✓ (CLI 버전 정합, 무중단)');
  }
  if (legacy) {
    console.log('  legacy:   ~/.athsra/session migrated and removed.');
  }
  return 0;
}
