import { randomUUID, timingSafeEqual } from 'node:crypto'
import rateLimit from 'express-rate-limit'
import { Document, Packer, Paragraph, HeadingLevel, TextRun } from 'docx'
import { mkdir, unlink, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { inferImageExtensionFromDataUrl, normalizeAssetBaseName, escapeHtml, buildAbsoluteUrl } from '../helpers.js'
import { UPLOADS_DIR, EMAIL_CHANGE_TOKEN_TTL_MS } from '../config.js'
import { state } from '../state.js'
import {
  queueStudyUsersWrite,
  queueStudyCommunityWrite,
  queueStudyCommentsWrite,
  queueStudyCertificatesWrite,
  readUploadsMetadata,
  writeUploadsMetadata,
  loadUserNotes,
  loadUserProgress,
  getUserNotesFilePath,
  getUserProgressFilePath,
} from '../data.js'
import {
  requireStudyAuth,
  hashStudyPassword,
  hashEmailChangeToken,
  normalizeStudyUsername,
  isValidStudyUsername,
  findStudyUserByUsername,
  getStudyAvatarUrl,
  isStudyUserEnrolled,
  normalizeStudySlug,
  getStudyCatalog,
  clearStudySessionCookie,
} from '../study-helpers.js'
import {
  sendStudyAccountDeletedEmail,
  syncContactToResend,
  buildBrandedEmailHtml,
  getCanonicalBaseUrl,
  getResendFromAddress,
} from '../email.js'
import { Resend } from 'resend'

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-account/export-notes', requireStudyAuth, async (req, res) => {
    const user = req.studyUser
    const notes = await loadUserNotes(user.id)
    const progress = await loadUserProgress(user.id)

    const sectionMeta = {}
    const content = state.cachedSiteContent
    const studies = content && Array.isArray(content.studies) && content.studies.length > 0
      ? content.studies
      : [{ slug: 'colossians', title: 'Colossians: Rooted in Christ', description: '', sections: content?.colossiansStudySections ?? [] }]

    for (const study of studies) {
      for (const section of (study.sections ?? [])) {
        sectionMeta[`${study.slug}--${section.id}`] = {
          studyTitle: study.title,
          studyDescription: typeof study.description === 'string' ? study.description : '',
          title: section.title,
          reference: section.reference,
          studyQuestions: Array.isArray(section.studyQuestions) ? section.studyQuestions : [],
        }
      }
    }

    const studyEntries = {}

    for (const [noteKey, noteText] of Object.entries(notes)) {
      if (!noteText?.trim()) continue
      const dashIndex = noteKey.indexOf('--')
      const studySlug = dashIndex >= 0 ? noteKey.slice(0, dashIndex) : 'unknown'
      const sectionId = dashIndex >= 0 ? noteKey.slice(dashIndex + 2) : noteKey
      if (!studyEntries[studySlug]) studyEntries[studySlug] = {}
      studyEntries[studySlug][sectionId] = studyEntries[studySlug][sectionId] || {}
      studyEntries[studySlug][sectionId].noteText = noteText.trim()
    }

    for (const [studySlug, studyProgress] of Object.entries(progress.byStudy)) {
      const quizAnswersBySection = studyProgress.quizAnswers || {}
      for (const [sectionId, answers] of Object.entries(quizAnswersBySection)) {
        if (!Array.isArray(answers) || answers.length === 0) continue
        if (!studyEntries[studySlug]) studyEntries[studySlug] = {}
        studyEntries[studySlug][sectionId] = studyEntries[studySlug][sectionId] || {}
        studyEntries[studySlug][sectionId].quizAnswers = answers.filter(answer => typeof answer === 'string' && answer.trim()).map(answer => answer.trim())
      }
    }

    const studySlugs = Array.from(new Set([
      ...Object.keys(studyEntries),
      ...Object.values(studies).map(study => study.slug),
    ]))

    const docChildren = [
      new Paragraph({ text: 'Verse by Verse with Nate', heading: HeadingLevel.TITLE }),
      new Paragraph({ text: 'My Study Export', heading: HeadingLevel.HEADING_1, spacing: { after: 240 } }),
      new Paragraph({ text: `Student: ${user.displayName || user.username}`, spacing: { after: 120 } }),
      new Paragraph({ text: `Exported ${new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}`, italics: true, spacing: { after: 400 } }),
    ]

    for (const studySlug of studySlugs) {
      const study = studies.find(item => normalizeStudySlug(item?.slug) === studySlug)
      const studyTitle = study?.title || studySlug
      const studyDescription = typeof study?.description === 'string' ? study.description : ''
      const sectionIds = studyEntries[studySlug] ? Object.keys(studyEntries[studySlug]) : []

      if (sectionIds.length === 0) continue

      docChildren.push(new Paragraph({ text: studyTitle, heading: HeadingLevel.HEADING_1, spacing: { before: 400 } }))
      if (studyDescription) {
        docChildren.push(new Paragraph({ text: studyDescription, spacing: { after: 240 } }))
      }
      const noteCount = sectionIds.filter(sectionId => studyEntries[studySlug][sectionId].noteText).length
      const quizCount = sectionIds.filter(sectionId => Array.isArray(studyEntries[studySlug][sectionId].quizAnswers) && studyEntries[studySlug][sectionId].quizAnswers.length > 0).length
      docChildren.push(new Paragraph({ text: `Notes: ${noteCount} | Quiz sections: ${quizCount}`, italics: true, spacing: { after: 240 } }))

      const orderedSectionIds = study?.sections?.map(section => section.id).filter(id => sectionIds.includes(id)) ?? sectionIds
      for (const sectionId of orderedSectionIds) {
        const entry = studyEntries[studySlug][sectionId]
        if (!entry) continue
        const meta = sectionMeta[`${studySlug}--${sectionId}`] || { title: sectionId, reference: '' }
        docChildren.push(new Paragraph({ text: meta.title, heading: HeadingLevel.HEADING_2, spacing: { before: 240 } }))
        if (meta.reference) {
          docChildren.push(new Paragraph({ children: [new TextRun({ text: meta.reference, italics: true, color: '555555' })], spacing: { after: 120 } }))
        }
        if (entry.noteText) {
          docChildren.push(new Paragraph({ text: 'Notes', heading: HeadingLevel.HEADING_3, spacing: { before: 120 } }))
          for (const line of entry.noteText.split('\n')) {
            docChildren.push(new Paragraph({ text: line.trim(), spacing: { after: 80 } }))
          }
        }
        if (Array.isArray(meta.studyQuestions) && meta.studyQuestions.length > 0) {
          docChildren.push(new Paragraph({ text: 'Quiz Questions', heading: HeadingLevel.HEADING_3, spacing: { before: 160 } }))
          meta.studyQuestions.forEach((question, index) => {
            docChildren.push(new Paragraph({ children: [new TextRun({ text: `${index + 1}. `, bold: true }), new TextRun({ text: question })], spacing: { after: 80 } }))
          })
        }
        if (Array.isArray(entry.quizAnswers) && entry.quizAnswers.length > 0) {
          docChildren.push(new Paragraph({ text: 'Quiz Answers', heading: HeadingLevel.HEADING_3, spacing: { before: 160 } }))
          entry.quizAnswers.forEach((answer, index) => {
            docChildren.push(new Paragraph({ children: [new TextRun({ text: `Answer ${index + 1}: `, bold: true }), new TextRun({ text: answer })], spacing: { after: 80 } }))
          })
        }
      }
    }

    if (docChildren.length <= 4) {
      docChildren.push(new Paragraph({ text: 'No notes or quiz answers saved yet.', spacing: { before: 200 } }))
    }

    const doc = new Document({ creator: 'Verse by Verse with Nate', title: 'My Study Export', sections: [{ children: docChildren }] })
    const buffer = await Packer.toBuffer(doc)
    const filename = `my-study-export-${new Date().toISOString().slice(0, 10)}.docx`
    res.setHeader('Content-Disposition', `attachment; filename="${filename}"`)
    res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')
    res.send(buffer)
  })

  app.post('/api/study-account/change-password', studyAuthRateLimiter, requireStudyAuth, (req, res) => {
    const user = req.studyUser
    const currentPassword = typeof req.body?.currentPassword === 'string' ? req.body.currentPassword : ''
    const newPassword = typeof req.body?.newPassword === 'string' ? req.body.newPassword : ''

    const currentHash = hashStudyPassword(currentPassword)
    const a = Buffer.from(currentHash, 'utf8')
    const b = Buffer.from(user.passwordHash, 'utf8')
    if (a.length !== b.length || !timingSafeEqual(a, b)) {
      res.status(401).json({ message: 'Current password is incorrect.' })
      return
    }

    if (newPassword.length < 8 || newPassword.length > 200) {
      res.status(400).json({ message: 'New password must be 8–200 characters.' })
      return
    }

    user.passwordHash = hashStudyPassword(newPassword)
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()
    res.json({ ok: true })
  })

  app.get('/api/study-account/overview', requireStudyAuth, async (req, res) => {
    const user = req.studyUser
    const notes = await loadUserNotes(user.id)
    const progress = await loadUserProgress(user.id)
    const noteEntries = Object.entries(notes)

    const studies = getStudyCatalog().map(study => {
      const totalLessons = Array.isArray(state.cachedSiteContent?.studies)
        ? (state.cachedSiteContent.studies.find(item => normalizeStudySlug(item?.slug) === study.slug)?.sections?.length ?? 0)
        : 0
      const noteCount = noteEntries.filter(([key, value]) => key.startsWith(`${study.slug}--`) && typeof value === 'string' && value.trim()).length
      const completedLessons = progress.byStudy[study.slug]?.completedSectionIds?.length ?? 0
      return {
        slug: study.slug,
        title: study.title,
        status: study.status,
        enrolled: isStudyUserEnrolled(user, study.slug),
        totalLessons,
        completedLessons,
        noteCount,
      }
    })

    res.json({
      profile: {
        username: user.username,
        displayName: user.displayName ?? '',
        subscribeNewsletter: user.subscribeNewsletter !== false,
        studyRemindersEnabled: user.studyRemindersEnabled === true,
        avatarUrl: getStudyAvatarUrl(user),
      },
      stats: {
        noteCount: Object.keys(notes).length,
        memberSince: user.createdAt,
        lastLoginAt: user.lastLoginAt,
        currentStreak: user.currentStreak ?? 0,
        longestStreak: user.longestStreak ?? 0,
        lastStudiedDate: user.lastStudiedDate ?? null,
      },
      studies,
    })
  })

  app.post('/api/study-account/profile', requireStudyAuth, (req, res) => {
    const user = req.studyUser
    const displayName = typeof req.body?.displayName === 'string' ? req.body.displayName.trim().slice(0, 80) : ''
    const avatarUrl = typeof req.body?.avatarUrl === 'string' ? req.body.avatarUrl.trim() : ''
    if (avatarUrl && !/^https?:\/\//i.test(avatarUrl) && !avatarUrl.startsWith('/uploads/') && !avatarUrl.startsWith('data:image/')) {
      res.status(400).json({ message: 'Avatar must be a valid uploaded image, data URI, or https URL.' })
      return
    }
    if (avatarUrl.startsWith('data:image/') && avatarUrl.length > 6 * 1024 * 1024) {
      res.status(400).json({ message: 'Avatar data URI is too large. Please use the upload endpoint instead.' })
      return
    }

    user.displayName = displayName
    user.avatarUrl = avatarUrl
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()
    res.json({ ok: true, displayName: user.displayName, avatarUrl: user.avatarUrl || getStudyAvatarUrl(user) })
  })

  app.post('/api/study-account/avatar-upload', requireStudyAuth, async (req, res) => {
    try {
      const filename = typeof req.body?.filename === 'string' ? req.body.filename : ''
      const dataUrl = typeof req.body?.dataUrl === 'string' ? req.body.dataUrl : ''
      const ext = inferImageExtensionFromDataUrl(dataUrl)

      if (!ext) {
        res.status(400).json({ message: 'Upload must be a PNG, JPG, WEBP, or GIF data URL.' })
        return
      }

      const base64 = dataUrl.split(',')[1] ?? ''
      const buffer = Buffer.from(base64, 'base64')
      if (buffer.length === 0 || buffer.length > (4 * 1024 * 1024)) {
        res.status(400).json({ message: 'Upload must be between 1 byte and 4MB.' })
        return
      }

      const isValidImageBytes = (
        (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) || // JPEG
        (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) || // PNG
        (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) || // GIF
        (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46) // WEBP (RIFF)
      )
      if (!isValidImageBytes) {
        res.status(400).json({ message: 'Upload must be a valid PNG, JPG, WEBP, or GIF image.' })
        return
      }

      const baseName = normalizeAssetBaseName(filename.replace(/\.[a-z0-9]+$/i, ''))
      const finalName = `${baseName || 'avatar'}-${Date.now()}${ext}`

      await mkdir(UPLOADS_DIR, { recursive: true })
      await writeFile(path.join(UPLOADS_DIR, finalName), buffer)
      const metadata = await readUploadsMetadata()
      metadata[finalName] = []
      await writeUploadsMetadata(metadata)

      res.json({ ok: true, url: `/uploads/${finalName}` })
    } catch (err) {
      console.error('[study-account-avatar-upload] upload error:', err)
      res.status(500).json({ message: 'Avatar upload failed.' })
    }
  })

  app.patch('/api/study-account/preferences', requireStudyAuth, (req, res) => {
    const user = req.studyUser
    const subscribeNewsletter = req.body?.subscribeNewsletter === true
    const studyRemindersEnabled = req.body?.studyRemindersEnabled === true
    user.subscribeNewsletter = subscribeNewsletter
    user.studyRemindersEnabled = studyRemindersEnabled
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()

    if (subscribeNewsletter) {
      syncContactToResend(user.displayName || user.username, user.username).catch(err => console.error('[study-account] resend sync error:', err))
    }

    res.json({ ok: true, subscribeNewsletter: user.subscribeNewsletter, studyRemindersEnabled: user.studyRemindersEnabled === true })
  })

  app.post('/api/study-account/request-email-change', studyAuthRateLimiter, requireStudyAuth, async (req, res) => {
    const user = req.studyUser
    const newEmail = normalizeStudyUsername(req.body?.newEmail)
    const currentPassword = typeof req.body?.currentPassword === 'string' ? req.body.currentPassword : ''

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

    if (newEmail === user.username) {
      res.status(400).json({ message: 'That is already your current email.' })
      return
    }

    const existing = findStudyUserByUsername(newEmail)
    if (existing && existing.id !== user.id) {
      res.status(409).json({ message: 'An account with that email already exists.' })
      return
    }

    const currentHash = hashStudyPassword(currentPassword)
    const a = Buffer.from(currentHash, 'utf8')
    const b = Buffer.from(user.passwordHash, 'utf8')
    if (a.length !== b.length || !timingSafeEqual(a, b)) {
      res.status(401).json({ message: 'Current password is incorrect.' })
      return
    }

    const rawToken = randomUUID()
    const tokenHash = hashEmailChangeToken(rawToken)
    const expiresAt = Date.now() + EMAIL_CHANGE_TOKEN_TTL_MS

    user.pendingEmailChange = { newEmail, tokenHash, expiresAt, requestedAt: new Date().toISOString() }
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()

    if (process.env.RESEND_API_KEY) {
      try {
        const resend = new Resend(process.env.RESEND_API_KEY)
        const baseUrl = getCanonicalBaseUrl()
        const verifyUrl = buildAbsoluteUrl(baseUrl, `/study/account?verifyEmailToken=${encodeURIComponent(rawToken)}`)
        const cfg = state.cachedSiteContent ?? {}
        const emailChangeSubject = cfg.emailChangeSubject?.trim() || 'Confirm your new email address'
        const emailChangeBody = cfg.emailChangeBody?.trim() || 'Click the link below to confirm your new account email. If you did not request this change, ignore this message.'
        const emailChangeCtaLabel = cfg.emailChangeCtaLabel?.trim() || 'Confirm Email Change'
        const { error } = await resend.emails.send({
          from: getResendFromAddress(),
          to: [newEmail],
          subject: emailChangeSubject,
          text: `${emailChangeBody}\n\n${verifyUrl}`,
          html: buildBrandedEmailHtml({
            title: emailChangeSubject,
            eyebrow: 'Account Security',
            bodyHtml: `<p style="margin:0 0 16px;">${escapeHtml(emailChangeBody)}</p>`,
            ctaLabel: emailChangeCtaLabel,
            ctaUrl: verifyUrl,
            footerHtml: `<p style="margin:0;font-family:Georgia,serif;font-size:12px;color:#7a7060;">Verse by Verse with Nate</p>`,
          }),
        })
        if (error) {
          console.error('[study-account] email change send error:', error)
          res.status(503).json({ message: 'Could not send verification email right now.' })
          return
        }
      } catch (err) {
        console.error('[study-account] email change send exception:', err)
        res.status(503).json({ message: 'Could not send verification email right now.' })
        return
      }
    }

    res.json({ ok: true, verificationSent: true })
  })

  app.post('/api/study-account/verify-email-change', studyAuthRateLimiter, requireStudyAuth, (req, res) => {
    const user = req.studyUser
    const token = typeof req.body?.token === 'string' ? req.body.token.trim() : ''
    const pending = user.pendingEmailChange

    if (!token || !pending || !pending.tokenHash) {
      res.status(400).json({ message: 'No pending email change request found.' })
      return
    }

    if (pending.expiresAt <= Date.now()) {
      user.pendingEmailChange = null
      queueStudyUsersWrite()
      res.status(400).json({ message: 'This verification link has expired. Request a new email change.' })
      return
    }

    const submittedHash = hashEmailChangeToken(token)
    const a = Buffer.from(submittedHash, 'utf8')
    const b = Buffer.from(pending.tokenHash, 'utf8')
    if (a.length !== b.length || !timingSafeEqual(a, b)) {
      res.status(400).json({ message: 'Invalid verification token.' })
      return
    }

    const newEmail = normalizeStudyUsername(pending.newEmail)
    if (!isValidStudyUsername(newEmail)) {
      user.pendingEmailChange = null
      queueStudyUsersWrite()
      res.status(400).json({ message: 'Pending email address is invalid.' })
      return
    }

    const existing = findStudyUserByUsername(newEmail)
    if (existing && existing.id !== user.id) {
      user.pendingEmailChange = null
      queueStudyUsersWrite()
      res.status(409).json({ message: 'An account with that email already exists.' })
      return
    }

    user.username = newEmail
    user.pendingEmailChange = null
    user.updatedAt = new Date().toISOString()
    queueStudyUsersWrite()

    if (user.subscribeNewsletter !== false) {
      syncContactToResend(user.displayName || user.username, user.username).catch(err => console.error('[study-account] resend sync error:', err))
    }

    res.json({ ok: true, username: user.username })
  })

  app.get('/api/study-account/stats', requireStudyAuth, async (req, res) => {
    const user = req.studyUser
    const notes = await loadUserNotes(user.id)
    res.json({
      noteCount: Object.keys(notes).length,
      memberSince: user.createdAt,
      lastLoginAt: user.lastLoginAt,
    })
  })

  app.delete('/api/study-account', requireStudyAuth, async (req, res) => {
    const user = req.studyUser
    const deletedEmail = user.username
    const deletedDisplayName = user.displayName || user.username

    for (const [token, session] of state.studySessions) {
      if (session.userId === user.id) state.studySessions.delete(token)
    }

    state.studyUsers = state.studyUsers.filter(u => u.id !== user.id)
    queueStudyUsersWrite()

    state.studyNotesCache.delete(user.id)
    try { await unlink(getUserNotesFilePath(user.id)) } catch { /* no notes file is fine */ }

    state.studyProgressCache.delete(user.id)
    try { await unlink(getUserProgressFilePath(user.id)) } catch { /* no progress file is fine */ }

    const beforeCerts = state.studyCertificates.length
    state.studyCertificates = state.studyCertificates.filter(c => c.userId !== user.id)
    if (state.studyCertificates.length !== beforeCerts) queueStudyCertificatesWrite()

    const beforePosts = state.studyCommunityPosts.length
    state.studyCommunityPosts = state.studyCommunityPosts.filter(p => p.authorUserId !== user.id)
    if (state.studyCommunityPosts.length !== beforePosts) queueStudyCommunityWrite()

    const beforeComments = state.studyComments.length
    state.studyComments = state.studyComments.filter(c => c.userId !== user.id)
    if (state.studyComments.length !== beforeComments) queueStudyCommentsWrite()

    sendStudyAccountDeletedEmail(deletedEmail, deletedDisplayName).catch(err => {
      console.error('[study-account] delete email error:', err)
    })

    clearStudySessionCookie(res)
    res.json({ ok: true })
  })
}
