// ── Admin: outbound email configuration (Gmail OAuth2) ─────────────────────
//
// Modern replacement for env-var SMTP. Sending goes through Gmail over OAuth2;
// the admin connects the mailbox with an in-app consent flow that captures a
// refresh token. We reuse the existing `google` SSO OAuth client (its id/secret)
// rather than a second app — so the only per-mailbox secret is the refresh token,
// stored AES-GCM-encrypted and write-only over this API (never returned).
//
// The connect flow mirrors sso.controller.js: a signed httpOnly tx cookie carries
// the CSRF nonce + PKCE verifier across the redirect to Google and back. It differs
// only in scope (https://mail.google.com/ for SMTP XOAUTH2) and access_type=offline
// + prompt=consent, which guarantee a refresh token even on reconnect.

const emailConfig = require('../../../model/emailConfig/emailConfig.model')
const authProviders = require('../../../model/authProviders/authProviders.model')
const activity = require('../../../model/activity/activity.model')
const mailer = require('../../../utils/mailer')
const GoogleProvider = require('../../../auth/providers/google.provider')
const ssoState = require('../../../auth/ssoState')
const token = require('../../../auth/token')

const log = require('../../../utils/logger')('admin')

// Gmail scope grants SMTP (XOAUTH2) access; openid+email let us read back which
// address was connected. The narrower gmail.send scope only works via the Gmail
// API, not SMTP, so we need the full-access scope here.
const EMAIL_SCOPE = 'https://mail.google.com/ openid email'
const TX_COOKIE = 'email_oauth_tx'

// Public base URL for the OAuth redirect_uri — same fallback pattern as
// sso.controller.js. Must be identical between start and callback.
function appBaseUrl(req) {
  const configured = process.env.APP_BASE_URL
  if (configured) return configured.replace(/\/+$/, '')
  const derived = `${req.protocol}://${req.get('host')}`
  log.warn('APP_BASE_URL not set — deriving email redirect_uri from the request', { derived })
  return derived
}
function redirectUri(req) {
  return `${appBaseUrl(req)}/api/v1/admin/email/connect/callback`
}
function txCookieOptions(req) {
  return { ...token.cookieOptions(req), maxAge: 10 * 60 * 1000 }
}

// Front-end redirect targets after the callback resolves.
const CONNECTED_URL = '/admin/settings?email_connected=1'
const errorUrl = (code) => `/admin/settings?email_error=${code}`

// Load the Google OAuth client (id + decrypted secret) reused for email. Returns
// null when the google provider hasn't been configured with credentials yet.
async function googleClient() {
  const row = await authProviders.getWithSecret('google')
  if (!row || !row.client_id || !row.client_secret) return null
  return { clientId: row.client_id, clientSecret: row.client_secret }
}

// GET /admin/email/config
async function getConfig(req, res) {
  try {
    const config = await emailConfig.getSafe()
    // Surface whether the Google client email can borrow is configured, so the
    // UI can explain why Connect is unavailable.
    config.googleConfigured = Boolean(await googleClient())
    return res.json(config)
  } catch (err) {
    log.error('emailConfig.getConfig', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// PUT /admin/email/config — sender name + enabled toggle. Enabling requires a
// connected mailbox (a stored refresh token).
async function saveConfig(req, res) {
  const { senderName, enabled } = req.body
  try {
    const current = await emailConfig.getSafe()
    if (enabled && !current.hasRefreshToken) {
      return res.status(400).json({ message: 'Connect a Gmail account before enabling email.' })
    }
    const saved = await emailConfig.save({
      senderName: senderName !== undefined ? senderName || null : undefined,
      enabled,
      updatedBy: req.user.id,
    })
    saved.googleConfigured = Boolean(await googleClient())
    await activity.log({ req, action: 'email.config.update', detail: { enabled: saved.enabled } })
    log.info('email config updated', { by: req.user.username, enabled: saved.enabled })
    return res.json(saved)
  } catch (err) {
    log.error('emailConfig.saveConfig', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /admin/email/connect/start — returns { url } for the browser to navigate to.
async function connectStart(req, res) {
  try {
    const client = await googleClient()
    if (!client) {
      return res.status(400).json({
        message: 'Configure the Google authentication provider (client id + secret) before connecting email.',
      })
    }
    const provider = new GoogleProvider({ clientId: client.clientId, clientSecret: client.clientSecret })
    const tx = ssoState.createTx({ flow: 'email' })
    res.cookie(TX_COOKIE, tx.txToken, txCookieOptions(req))

    const params = new URLSearchParams({
      client_id: client.clientId,
      redirect_uri: redirectUri(req),
      response_type: 'code',
      scope: EMAIL_SCOPE,
      access_type: 'offline',
      prompt: 'consent',
      include_granted_scopes: 'true',
      state: tx.nonce,
      code_challenge: tx.codeChallenge,
      code_challenge_method: 'S256',
    })
    const url = `${provider.authEndpoint()}?${params.toString()}`
    return res.json({ url })
  } catch (err) {
    log.error('emailConfig.connectStart', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /admin/email/connect/callback — exchange the code, capture the refresh
// token + connected address, store encrypted, and redirect back to Settings.
async function connectCallback(req, res) {
  const txToken = req.cookies && req.cookies[TX_COOKIE]
  const { code, state, error: oauthError } = req.query
  res.clearCookie(TX_COOKIE, token.cookieOptions(req)) // single-use

  if (oauthError) {
    log.warn('email connect: provider returned error', { error: String(oauthError).slice(0, 60) })
    return res.redirect(errorUrl('denied'))
  }
  const tx = ssoState.verifyTx(txToken, state)
  if (!tx || tx.flow !== 'email' || !code) {
    log.warn('email connect: bad state')
    return res.redirect(errorUrl('bad_state'))
  }
  try {
    const client = await googleClient()
    if (!client) return res.redirect(errorUrl('no_client'))
    const provider = new GoogleProvider({ clientId: client.clientId, clientSecret: client.clientSecret })

    const tokenSet = await provider.exchangeCode({
      code,
      redirectUri: redirectUri(req),
      codeVerifier: tx.verifier,
    })
    if (!tokenSet.refresh_token) {
      // Google only returns a refresh token when it hasn't already granted one
      // for this client+scope. prompt=consent should force it; if it's still
      // missing the admin can revoke the app's access and retry.
      log.warn('email connect: no refresh_token returned')
      return res.redirect(errorUrl('no_refresh_token'))
    }
    const profile = await provider.getUserProfile(tokenSet.access_token)
    const senderEmail = profile.email || null
    if (!senderEmail) return res.redirect(errorUrl('no_email'))

    await emailConfig.save({
      senderEmail,
      refreshToken: tokenSet.refresh_token,
      enabled: true,
      status: 'connected',
      statusDetail: 'Connected',
      updatedBy: req.user.id,
    })
    await emailConfig.recordStatus({ status: 'connected', statusDetail: 'Connected', lastVerifiedAt: new Date() })
    await activity.log({ req, action: 'email.connect', detail: { senderEmail } })
    log.info('email connected', { senderEmail, by: req.user.username })
    return res.redirect(CONNECTED_URL)
  } catch (err) {
    log.error('emailConfig.connectCallback', err)
    return res.redirect(errorUrl('error'))
  }
}

// POST /admin/email/test — send a test message (to the given address, or the
// contact recipient by default).
async function testSend(req, res) {
  try {
    const result = await mailer.sendTest(req.body.to)
    await activity.log({ req, action: 'email.test', detail: { to: result.to } })
    return res.json(result)
  } catch (err) {
    log.warn('email test send failed', { message: err.message })
    return res.status(502).json({ message: err.message || 'Could not send the test email.' })
  }
}

// POST /admin/email/disconnect — clear the stored credential and disable sending.
async function disconnect(req, res) {
  try {
    const config = await emailConfig.disconnect(req.user.id)
    config.googleConfigured = Boolean(await googleClient())
    await activity.log({ req, action: 'email.disconnect' })
    log.info('email disconnected', { by: req.user.username })
    return res.json(config)
  } catch (err) {
    log.error('emailConfig.disconnect', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

module.exports = { getConfig, saveConfig, connectStart, connectCallback, testSend, disconnect }
