/**
 * Worker-side dispatcher — routes login / signup / verify-email writes to AuthWriteDO
 * (30s CPU budget, sticky isolate), mirroring makeDoDeriveBits.
 *
 * Two DISTINCT fallback disciplines, because (unlike PasswordHashDO's pure
 * compute) these flows have side effects:
 *   - signup: idempotent (findUserByEmail short-circuits), so it falls back
 *     INLINE on a missing binding/secret OR a DO runtime failure.
 *   - verify-email: NOT idempotent (completeEmailVerification always inserts a
 *     fresh tenant; the single-use JTI is the at-most-once guard). It falls back
 *     inline ONLY when the binding/secret is absent (the DO never ran = today's
 *     behavior). On a DO RUNTIME failure the DO may already have consumed the JTI
 *     and committed the tenant, so re-running inline is unsafe — it surfaces an
 *     error instead.
 */
import type { Env } from '@zync/types'
import {
  runSessionAuthentication,
  type SessionExecutionContext,
  type SessionAuthenticationResult,
} from './session-authentication'
import {
  runLogin,
  runSignup,
  runVerifyEmail,
  type LoginInput,
  type SignupInput,
  type SignupResult,
  type VerifyEmailInput,
  type VerifyEmailResult,
} from '../routes/auth/_auth-flows'

function shard(): number {
  return crypto.getRandomValues(new Uint32Array(1))[0]! % 16
}

function authWriteDoConfigured(env: Env): boolean {
  return Boolean(env.AUTHWRITE_DO && env.AUTHWRITE_DO_SECRET)
}

async function callAuthWriteDo(
  env: Env,
  path: '/authenticate' | '/login' | '/signup' | '/verify-email',
  body: unknown,
): Promise<Response> {
  const id = env.AUTHWRITE_DO.idFromName(`aw-${shard()}`)
  const stub = env.AUTHWRITE_DO.get(id)
  return stub.fetch(`https://authwrite${path}`, {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-zync-authwrite': env.AUTHWRITE_DO_SECRET,
    },
    body: JSON.stringify(body),
  })
}

export async function dispatchSessionAuthentication(
  env: Env,
  token: string,
  executionCtx?: SessionExecutionContext,
): Promise<SessionAuthenticationResult> {
  if (!authWriteDoConfigured(env)) {
    return runSessionAuthentication(env, token, executionCtx)
  }
  try {
    const response = await callAuthWriteDo(env, '/authenticate', { token })
    if (!response.ok) {
      return { ok: false, status: 503, error: 'Authentication temporarily unavailable' }
    }
    return (await response.json()) as SessionAuthenticationResult
  } catch {
    return { ok: false, status: 503, error: 'Authentication temporarily unavailable' }
  }
}

/**
 * Dispatch login after the edge Worker has validated origin, rate limit, and
 * input shape. A configured DO failure is fail-closed: the flow may already
 * have minted a refresh token, so an inline retry could create a second session.
 */
export async function dispatchLogin(env: Env, input: LoginInput): Promise<Response> {
  if (!authWriteDoConfigured(env)) {
    return runLogin(env, input)
  }
  try {
    return await callAuthWriteDo(env, '/login', input)
  } catch {
    console.warn('AuthWriteDO login transport failure — not inline-retrying session issuance')
    return Response.json(
      { error: 'Login temporarily unavailable. Please retry.' },
      { status: 503 },
    )
  }
}

/**
 * Dispatch signup. Inline fallback on missing config OR any DO failure — safe
 * because runSignup is idempotent on re-entry.
 */
export async function dispatchSignup(env: Env, input: SignupInput): Promise<SignupResult> {
  if (!authWriteDoConfigured(env)) {
    return runSignup(env, input)
  }
  try {
    const res = await callAuthWriteDo(env, '/signup', input)
    if (!res.ok) {
      console.warn(`AuthWriteDO signup failed: HTTP ${res.status} — inline fallback`)
      return runSignup(env, input)
    }
    return (await res.json()) as SignupResult
  } catch {
    console.warn('AuthWriteDO signup failed, falling back to inline')
    return runSignup(env, input)
  }
}

/**
 * Dispatch verify-email. Inline fallback ONLY when the DO is unconfigured (it
 * never ran). On a DO runtime failure the flow may have partially committed, so
 * it surfaces a 500 rather than inline-retrying a non-idempotent side effect.
 */
export async function dispatchVerifyEmail(env: Env, input: VerifyEmailInput): Promise<VerifyEmailResult> {
  if (!authWriteDoConfigured(env)) {
    return runVerifyEmail(env, input)
  }
  let res: Response
  try {
    res = await callAuthWriteDo(env, '/verify-email', input)
  } catch {
    console.warn('AuthWriteDO verify-email transport failure — not inline-retrying side effects')
    return { status: 500, body: { error: 'Verification temporarily unavailable. Please retry the link.' } }
  }
  if (!res.ok) {
    console.warn(`AuthWriteDO verify-email failed: HTTP ${res.status} — not inline-retrying side effects`)
    return { status: 500, body: { error: 'Verification temporarily unavailable. Please retry the link.' } }
  }
  return (await res.json()) as VerifyEmailResult
}
