import { createHash, createHmac, randomUUID, timingSafeEqual } from 'node:crypto'
import rateLimit from 'express-rate-limit'
import qrcode from 'qrcode'
import {
  generateTotpSecret,
  verifyTotpCode,
  generateRecoveryCodes,
} from '../auth.js'
import { parseCookies } from '../helpers.js'
import { STUDY_SESSION_COOKIE, MAX_STUDY_USERS, MAX_CONTACT_SUBMISSIONS } from '../config.js'
import { state } from '../state.js'
import {
  queueStudyUsersWrite,
  queueContactSubmissionsWrite,
  normalizeContactEmailStatus,
  normalizeMessageType,
} from '../data.js'
import {
  normalizeStudyUsername,
  isValidStudyUsername,
  hashStudyPassword,
  findStudyUserByUsername,
  getStudyAvatarUrl,
  createStudySession,
  setStudySessionCookie,
  clearStudySessionCookie,
  requireStudyAuth,
  createStudyTotpPendingToken,
  consumeStudyTotpPendingToken,
  generateEmailOtp,
  storeEmailOtp,
  verifyEmailOtp,
  getStudyUserFromRequest,
} from '../study-helpers.js'
import { sendEmailOtp, sendStudyWelcomeEmail, syncContactToResend } from '../email.js'

const studyAuthRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 20,
  standardHeaders: true,
  legacyHeaders: false,
  message: { message: 'Too many attempts. Please wait 15 minutes and try again.' },
  skipSuccessfulRequests: true,
})

export function register(app) {
  app.get('/api/study-auth/status', (req, res) => {
    const user = getStudyUserFromRequest(req)
    res.json({
      authenticated: Boolean(user),
      username: user?.username ?? '',
      displayName: user?.displayName ?? '',
      subscribeNewsletter: user?.subscribeNewsletter !== false,
      studyRemindersEnabled: user?.studyRemindersEnabled === true,
      avatarUrl: user ? getStudyAvatarUrl(user) : '',
      enrolledStudySlugs: Array.isArray(user?.enrolledStudySlugs) ? user.enrolledStudySlugs : [],
      totpEnabled: Boolean(user && (user.twoFaMethod === 'app' || user.twoFaMethod === 'email') && (user.twoFaMethod === 'email' || (user.totpSecret && user.totpVerified))),
      twoFaMethod: user?.twoFaMethod ?? null,
      totpRecoveryCodesRemaining: user?.twoFaMethod === 'app' ? (user.totpRecoveryCodes?.length ?? 0) : 0,
    })
  })

  app.post('/api/study-auth/signup', studyAuthRateLimiter, async (req, res) => {
    const username = normalizeStudyUsername(req.body?.username)
    const password = typeof req.body?.password === 'string' ? req.body.password : ''
    const subscribe = req.body?.subscribe === true
    const displayName = typeof req.body?.displayName === 'string' ? req.body.displayName.trim().slice(0, 80) : ''

    if (!isValidStudyUsername(username)) {
      res.status(400).json({ message: 'Please enter a valid email address.' })
      return
    }

    if (typeof password !== 'string' || password.length < 8 || password.length > 200) {
      res.status(400).json({ message: 'Password must be 8-200 characters.' })
      return
    }

    if (findStudyUserByUsername(username)) {
      res.status(409).json({ message: 'An account with that email already exists.' })
      return
    }

    if (state.studyUsers.length >= MAX_STUDY_USERS) {
      res.status(503).json({ message: 'Account registration is temporarily unavailable. Please try again later.' })
      return
    }

    const now = new Date().toISOString()
    const user = {
      id: randomUUID(),
      username,
      passwordHash: hashStudyPassword(password),
      displayName,
      subscribeNewsletter: subscribe,
      studyRemindersEnabled: false,
      pendingEmailChange: null,
      enrolledStudySlugs: [],
      createdAt: now,
      updatedAt: now,
      lastLoginAt: now,
    }

    state.studyUsers.push(user)
    queueStudyUsersWrite()

    if (subscribe) {
      const wantsWelcome = true
      const submission = {
        id: randomUUID(),
        submittedAt: now,
        name: displayName || username,
        email: username,
        message: '',
        messageType: 'general',
        subscribe: wantsWelcome,
        archived: false,
        emailStatus: normalizeContactEmailStatus(null, wantsWelcome),
      }
      state.contactSubmissions.unshift(submission)
      state.contactSubmissions = state.contactSubmissions.slice(0, MAX_CONTACT_SUBMISSIONS)
      queueContactSubmissionsWrite()
      syncContactToResend(displayName || username, username).catch(err => console.error('[study-signup] resend sync error:', err))
    }

    sendStudyWelcomeEmail(username, displayName || username).catch(err => console.error('[study-signup] welcome email error:', err))

    const sessionToken = createStudySession(user.id)
    setStudySessionCookie(res, sessionToken)
    res.json({
      ok: true,
      username: user.username,
      displayName: user.displayName,
      subscribeNewsletter: user.subscribeNewsletter,
      studyRemindersEnabled: user.studyRemindersEnabled === true,
      avatarUrl: getStudyAvatarUrl(user.username),
      enrolledStudySlugs: user.enrolledStudySlugs,
    })
  })

  app.post('/api/study-auth/login', studyAuthRateLimiter, (req, res) => {
    const username = normalizeStudyUsername(req.body?.username)
    const password = typeof req.body?.password === 'string' ? req.body.password : ''
    const user = findStudyUserByUsername(username)

    if (!user) {
      res.status(401).json({ message: 'Invalid email or password.' })
      return
    }

    const submittedHash = hashStudyPassword(password)
    const expectedHash = user.passwordHash
    const a = Buffer.from(submittedHash, 'utf8')
    const b = Buffer.from(expectedHash, 'utf8')
    if (a.length !== b.length || !timingSafeEqual(a, b)) {
      res.status(401).json({ message: 'Invalid email or password.' })
      return
    }

    const twoFaMethod = user.twoFaMethod ?? (user.totpSecret && user.totpVerified ? 'app' : null)
    if (twoFaMethod === 'app' && user.totpSecret && user.totpVerified) {
      const pendingToken = createStudyTotpPendingToken(user.id)
      res.json({ totpRequired: true, pendingToken, method: 'app' })
      return
    }
    if (twoFaMethod === 'email') {
      const code = generateEmailOtp()
      storeEmailOtp(user.id, code)
      const pendingToken = createStudyTotpPendingToken(user.id)
      sendEmailOtp(user.username, code).catch(err => console.error('[email-otp] login send error:', err))
      res.json({ totpRequired: true, pendingToken, method: 'email' })
      return
    }

    user.lastLoginAt = new Date().toISOString()
    user.updatedAt = user.lastLoginAt
    user.reengagementSentAt = {}
    queueStudyUsersWrite()

    const sessionToken = createStudySession(user.id)
    setStudySessionCookie(res, sessionToken)
    res.json({
      ok: true,
      username: user.username,
      displayName: user.displayName ?? '',
      subscribeNewsletter: user.subscribeNewsletter !== false,
      studyRemindersEnabled: user.studyRemindersEnabled === true,
      avatarUrl: getStudyAvatarUrl(user.username),
      enrolledStudySlugs: user.enrolledStudySlugs ?? [],
    })
  })

  app.post('/api/study-auth/totp-verify', studyAuthRateLimiter, (req, res) => {
    const { pendingToken, code } = req.body ?? {}
    const userId = consumeStudyTotpPendingToken(pendingToken)
    if (!userId) {
      res.status(401).json({ message: 'Session expired or invalid. Please sign in again.' })
      return
    }
    const user = state.studyUsers.find(u => u.id === userId)
    const twoFaMethodCheck = user?.twoFaMethod ?? (user?.totpSecret && user?.totpVerified ? 'app' : null)
    if (!user || !twoFaMethodCheck) {
      res.status(400).json({ message: '2FA is not configured for this account.' })
      return
    }

    const codeStr = typeof code === 'string' ? code.replace(/\s/g, '') : ''
    const twoFaMethod = twoFaMethodCheck

    function completeLogin(extra = {}) {
      user.lastLoginAt = new Date().toISOString()
      user.updatedAt = user.lastLoginAt
      user.reengagementSentAt = {}
      queueStudyUsersWrite()
      const sessionToken = createStudySession(user.id)
      setStudySessionCookie(res, sessionToken)
      res.json({ ok: true, ...extra, username: user.username, displayName: user.displayName ?? '', subscribeNewsletter: user.subscribeNewsletter !== false, studyRemindersEnabled: user.studyRemindersEnabled === true, avatarUrl: getStudyAvatarUrl(user.username), enrolledStudySlugs: user.enrolledStudySlugs ?? [] })
    }

    if (twoFaMethod === 'email') {
      const result = verifyEmailOtp(user.id, codeStr)
      if (result === 'ok') { completeLogin(); return }
      if (result === 'expired') { res.status(401).json({ message: 'Code expired. Please sign in again to receive a new code.' }); return }
      if (result === 'too-many') { res.status(401).json({ message: 'Too many attempts. Please sign in again.' }); return }
      res.status(401).json({ message: 'Invalid code. Check your email and try again.' })
      return
    }

    if (verifyTotpCode(user.totpSecret, codeStr)) {
      completeLogin()
      return
    }

    if (Array.isArray(user.totpRecoveryCodes) && user.totpRecoveryCodes.length > 0) {
      const normalised = codeStr.replace(/-/g, '').toUpperCase()
      const matchIdx = user.totpRecoveryCodes.findIndex(h => {
        try { return createHash('sha256').update(normalised).digest('hex') === h } catch { return false }
      })
      if (matchIdx !== -1) {
        user.totpRecoveryCodes.splice(matchIdx, 1)
        completeLogin({ usedRecoveryCode: true, remainingRecoveryCodes: user.totpRecoveryCodes.length })
        return
      }
    }

    res.status(401).json({ message: 'Invalid code. Try again or use a recovery code.' })
  })

  app.post('/api/study-auth/totp-setup-init', requireStudyAuth, async (req, res) => {
    const user = req.studyUser
    const secret = generateTotpSecret()
    const label = user.username
    const issuer = 'Verse by Verse with Nate'
    const uri = `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(label)}?secret=${secret}&issuer=${encodeURIComponent(issuer)}&algorithm=SHA1&digits=6&period=30`
    const qrDataUrl = await qrcode.toDataURL(uri)
    user.totpSecretPending = secret
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()
    res.json({ qrDataUrl, secret })
  })

  app.post('/api/study-auth/totp-setup-confirm', requireStudyAuth, (req, res) => {
    const user = req.studyUser
    const { code } = req.body ?? {}
    if (!user.totpSecretPending) {
      res.status(400).json({ message: 'No 2FA setup in progress. Start setup first.' })
      return
    }
    if (!verifyTotpCode(user.totpSecretPending, typeof code === 'string' ? code.replace(/\s/g, '') : '')) {
      res.status(401).json({ message: 'Code incorrect. Scan the QR code again and try once more.' })
      return
    }
    const recoveryCodes = generateRecoveryCodes()
    user.totpSecret = user.totpSecretPending
    user.totpVerified = true
    user.twoFaMethod = 'app'
    user.totpEnabledAt = new Date().toISOString()
    user.totpRecoveryCodes = recoveryCodes.map(c => createHash('sha256').update(c.replace(/-/g, '').toUpperCase()).digest('hex'))
    delete user.totpSecretPending
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()
    res.json({ ok: true, recoveryCodes })
  })

  app.post('/api/study-auth/2fa-setup-email', studyAuthRateLimiter, requireStudyAuth, async (req, res) => {
    const user = req.studyUser
    const code = generateEmailOtp()
    storeEmailOtp(user.id, code)
    await sendEmailOtp(user.username, code)
    res.json({ ok: true })
  })

  app.post('/api/study-auth/2fa-setup-email-confirm', studyAuthRateLimiter, requireStudyAuth, (req, res) => {
    const user = req.studyUser
    const { code } = req.body ?? {}
    const result = verifyEmailOtp(user.id, typeof code === 'string' ? code.trim() : '')
    if (result === 'expired') { res.status(401).json({ message: 'Code expired. Please request a new one.' }); return }
    if (result === 'too-many') { res.status(401).json({ message: 'Too many attempts. Please request a new code.' }); return }
    if (result !== 'ok') { res.status(401).json({ message: 'Invalid code. Check your email and try again.' }); return }
    user.twoFaMethod = 'email'
    user.totpSecret = null
    user.totpVerified = false
    user.totpRecoveryCodes = []
    delete user.totpSecretPending
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()
    res.json({ ok: true })
  })

  app.post('/api/study-auth/email-otp-resend', studyAuthRateLimiter, async (req, res) => {
    const { pendingToken } = req.body ?? {}
    const entry = state.studyTotpPendingTokens.get(pendingToken)
    if (!entry || Date.now() > entry.expiresAt) { res.status(401).json({ message: 'Session expired. Please sign in again.' }); return }
    const user = state.studyUsers.find(u => u.id === entry.userId)
    if (!user) { res.status(404).json({ message: 'User not found.' }); return }
    const code = generateEmailOtp()
    storeEmailOtp(user.id, code)
    await sendEmailOtp(user.username, code)
    res.json({ ok: true })
  })

  app.post('/api/study-auth/totp-disable', studyAuthRateLimiter, requireStudyAuth, (req, res) => {
    const user = req.studyUser
    const { password } = req.body ?? {}
    const submittedHash = hashStudyPassword(typeof password === 'string' ? password : '')
    const a = Buffer.from(submittedHash, 'utf8')
    const b = Buffer.from(user.passwordHash, 'utf8')
    if (a.length !== b.length || !timingSafeEqual(a, b)) {
      res.status(401).json({ message: 'Incorrect password.' })
      return
    }
    user.twoFaMethod = null
    user.totpSecret = null
    user.totpVerified = false
    user.totpRecoveryCodes = []
    delete user.totpSecretPending
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()
    res.json({ ok: true })
  })

  app.post('/api/study-auth/totp-regen-recovery', requireStudyAuth, (req, res) => {
    const user = req.studyUser
    if (!user.totpSecret || !user.totpVerified) {
      res.status(400).json({ message: '2FA is not enabled.' })
      return
    }
    const recoveryCodes = generateRecoveryCodes()
    user.totpRecoveryCodes = recoveryCodes.map(c => createHash('sha256').update(c.replace(/-/g, '').toUpperCase()).digest('hex'))
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()
    res.json({ ok: true, recoveryCodes })
  })

  app.post('/api/study-auth/logout', (req, res) => {
    const cookies = parseCookies(req.headers.cookie)
    const token = cookies[STUDY_SESSION_COOKIE]
    if (token) {
      state.studySessions.delete(token)
    }
    clearStudySessionCookie(res)
    res.json({ ok: true })
  })

  // One-click unsubscribe from re-engagement emails (no login required)
  app.get('/api/study-auth/unsubscribe-reminders', (req, res) => {
    const { uid, token } = req.query ?? {}
    if (typeof uid !== 'string' || typeof token !== 'string') {
      res.status(400).send('Invalid unsubscribe link.')
      return
    }
    const secret = process.env.RESEND_WEBHOOK_TOKEN ?? 'siteforge'
    const expected = createHmac('sha256', secret).update(uid).digest('hex')
    const expectedBuf = Buffer.from(expected, 'hex')
    const actualBuf = Buffer.from(token.length === expected.length ? token : '', 'hex')
    let valid = false
    try { valid = timingSafeEqual(expectedBuf, actualBuf) } catch { valid = false }
    if (!valid) {
      res.status(400).send('Invalid or expired unsubscribe link.')
      return
    }
    const user = state.studyUsers.find(u => u.id === uid)
    if (!user) {
      res.status(404).send('Account not found.')
      return
    }
    user.studyRemindersEnabled = false
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()
    res.send('<!doctype html><html><head><meta charset="utf-8"><title>Unsubscribed</title><style>body{font-family:system-ui,sans-serif;max-width:480px;margin:80px auto;padding:24px;text-align:center;color:#333}h1{font-size:1.4rem;margin-bottom:12px}p{color:#666;line-height:1.6}</style></head><body><h1>You\'ve been unsubscribed</h1><p>You will no longer receive re-engagement reminder emails. You can re-enable them anytime from your account settings.</p></body></html>')
  })
}
