import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createHash, randomBytes } from 'node:crypto'
import { getSessionUser } from '@/lib/auth'
import { findEnvUserById, listEnvUsers } from '@/lib/auth/env-users'
import { findSelfAccountById, selfAccountAsEnvUser } from '@/lib/auth/self-accounts'
import { setAccountContact } from '@/lib/auth/accounts'
import { createAdminClient } from '@/lib/supabase/admin'
import { publicOrigin } from '@/lib/app-url'
import { sendPlatformEmail } from '@/lib/email/send'
import { logSecurityEvent } from '@/lib/security/events'
import { clientIp, rateLimit } from '@/lib/security/rate-limit'
import { emailPattern } from '@/lib/like'
import { emailTaken, normalizeEmailAddress, tidyPhone } from '@/lib/domain/account'

/**
 * Profile → Account (Nash, 2026-09-23): change my sign-in email (verified by
 * a link sent to the NEW address, same approach as the delivery address) and
 * my phone (no verification). Both are the person's own; admins keep their
 * console for everything else.
 */
const schema = z.discriminatedUnion('op', [
  z.object({ op: z.literal('phone'), phone: z.string().max(40) }),
  z.object({ op: z.literal('change_email'), email: z.string().trim().email().max(200) }),
  z.object({ op: z.literal('cancel_email_change') }),
])

async function envOrSelf(userId: string) {
  const env = findEnvUserById(userId)
  if (env) return env
  const self = await findSelfAccountById(userId)
  return self ? selfAccountAsEnvUser(self) : null
}

export async function POST(req: NextRequest) {
  const user = await getSessionUser()
  if (!user) return NextResponse.json({ error: 'Sign in first.' }, { status: 401 })
  if (user.originId !== user.id) return NextResponse.json({ error: 'Switch back to your own account to change its details.' }, { status: 403 })
  const parsed = schema.safeParse(await req.json().catch(() => ({})))
  if (!parsed.success) return NextResponse.json({ error: 'Check the details and try again.' }, { status: 400 })
  const input = parsed.data
  const admin = createAdminClient()
  const ip = clientIp(req.headers)
  const actorLabel = user.name || user.email
  const env = await envOrSelf(user.id)
  if (!env) return NextResponse.json({ error: 'Account not found.' }, { status: 404 })

  if (input.op === 'phone') {
    const { phone } = tidyPhone(input.phone)
    const r = await setAccountContact(env, { phone }, actorLabel)
    if (!r.ok) return NextResponse.json({ error: r.error }, { status: 400 })
    await admin.from('profiles').update({ phone }).eq('id', user.id)
    await logSecurityEvent({ event: 'account_phone_changed', userId: user.id, actorUserId: user.id, actorLabel, ip, detail: { phone_set: !!phone } })
    return NextResponse.json({ ok: true, phone })
  }

  if (input.op === 'cancel_email_change') {
    const { error } = await admin.from('profiles').update({ pending_login_email: null, pending_login_email_token_hash: null, pending_login_email_sent_at: null }).eq('id', user.id)
    if (error && !/column|schema cache/i.test(error.message)) return NextResponse.json({ error: error.message }, { status: 400 })
    return NextResponse.json({ ok: true })
  }

  // change_email
  const gate = rateLimit(`account-email:${user.id}`, 5, 60 * 60_000)
  if (!gate.allowed) return NextResponse.json({ error: 'Too many attempts. Try again in an hour.' }, { status: 429 })
  const email = normalizeEmailAddress(input.email)
  if (email === normalizeEmailAddress(user.email)) return NextResponse.json({ error: 'That is already your sign-in email.' }, { status: 400 })
  if (/@(users\.local|[a-z0-9.-]+\.(test|example|invalid))$/i.test(email)) return NextResponse.json({ error: 'Enter a real email address you can receive mail at.' }, { status: 400 })
  // Nobody else may own it: env logins, account rows, profiles.
  const known: Array<{ email: string; userId: string }> = listEnvUsers().map((u) => ({ email: u.email, userId: u.id }))
  const { data: acc } = await admin.from('auth_accounts').select('user_id, email').ilike('email', emailPattern(email))
  for (const a of (acc ?? []) as Array<{ user_id: string; email: string | null }>) if (a.email) known.push({ email: a.email, userId: a.user_id })
  const { data: prof } = await admin.from('profiles').select('id, email').ilike('email', emailPattern(email))
  for (const p of (prof ?? []) as Array<{ id: string; email: string }>) known.push({ email: p.email, userId: p.id })
  if (emailTaken(known, email, user.id)) return NextResponse.json({ error: 'That email is already used by another HeavyHaul Agent account.' }, { status: 409 })

  const token = randomBytes(24).toString('base64url')
  const { error } = await admin.from('profiles').update({ pending_login_email: email, pending_login_email_token_hash: createHash('sha256').update(token).digest('hex'), pending_login_email_sent_at: new Date().toISOString() }).eq('id', user.id)
  if (error) return NextResponse.json({ error: /column|schema cache/i.test(error.message) ? 'Changing the sign-in email needs database migration 0031.' : error.message }, { status: 400 })
  const r = await sendPlatformEmail({
    templateKey: 'login_email_verification', to: email, userId: null, actorUserId: user.id, clean: true,
    data: { user_name: user.name || user.email, old_email: user.email, verification_link: `${publicOrigin(req)}/account/verify-email/${token}` },
  })
  await logSecurityEvent({ event: 'account_email_change_requested', userId: user.id, actorUserId: user.id, actorLabel, ip, detail: { from: user.email, to: email, email_status: r.status } })
  if (r.status === 'suppressed') return NextResponse.json({ error: `That address cannot receive email (${r.reason?.replace(/_/g, ' ')}). Try a different one.` }, { status: 409 })
  if (r.status === 'failed') return NextResponse.json({ error: `Could not send the verification email: ${r.reason}` }, { status: 502 })
  return NextResponse.json({ ok: true, status: r.status, pending: email })
}
