// ── Outbound mail (Gmail over OAuth2 / SMTP XOAUTH2) ───────────────────────
//
// Email is configured in Admin → Settings → Email, not via env vars. The
// connection (enabled flag, connected Gmail address, encrypted refresh token)
// lives in the email_config singleton; the OAuth client id/secret are reused
// from the `google` auth_providers row. nodemailer takes the refresh token and
// auto-mints short-lived access tokens for each send.
//
// When email is not configured, sendContactMessage does NOT throw — it signals
// the caller to fall back to a mailto: link (the contact form relies on this).

const nodemailer = require('nodemailer')

const emailConfig = require('../model/emailConfig/emailConfig.model')
const authProviders = require('../model/authProviders/authProviders.model')
const settings = require('../model/settings/settings.model')
const brand = require('../config/brand')
const log = require('./logger')('mailer')

// Ready to send only when enabled, connected (has a refresh token), and we know
// which address to send as.
async function isConfigured() {
  const c = await emailConfig.getSafe()
  return Boolean(c.enabled && c.hasRefreshToken && c.senderEmail)
}

// Recipient for the contact form: the admin-editable contact_email setting, or
// the connected sending address as a last resort.
async function contactRecipient(senderEmail) {
  const to = await settings.get('contact_email')
  return to || senderEmail || null
}

// Build a nodemailer OAuth2 transport from the stored config + reused Google
// client credentials. Returns { transport, config } or null when unconfigured.
async function buildTransport() {
  const config = await emailConfig.getWithSecret()
  if (!config || !config.refreshToken || !config.senderEmail) return null
  const google = await authProviders.getWithSecret('google')
  if (!google || !google.client_id || !google.client_secret) {
    log.warn('email send skipped: Google OAuth client is not configured')
    return null
  }
  const transport = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {
      type: 'OAuth2',
      user: config.senderEmail,
      clientId: google.client_id,
      clientSecret: google.client_secret,
      refreshToken: config.refreshToken,
    },
  })
  return { transport, config }
}

function fromHeader(config) {
  return config.senderName ? `"${config.senderName}" <${config.senderEmail}>` : config.senderEmail
}

/**
 * Send a contact message. If email is not configured/enabled, signals the caller
 * to fall back to a mailto: link instead of throwing.
 */
async function sendContactMessage({ name, email, message }) {
  const built = await buildTransport()
  if (!built) {
    const c = await emailConfig.getSafe()
    return { sent: false, fallback: 'mailto', email: await contactRecipient(c.senderEmail) }
  }
  const { transport, config } = built
  const to = await contactRecipient(config.senderEmail)
  try {
    await transport.sendMail({
      from: fromHeader(config),
      to,
      replyTo: email,
      subject: `${brand.name} contact from ${name || 'a visitor'}`,
      text: `From: ${name || 'unknown'} <${email || 'no email'}>\n\n${message}`,
    })
    await emailConfig.recordStatus({ status: 'connected', statusDetail: 'Last send OK', lastVerifiedAt: new Date() })
    return { sent: true }
  } catch (err) {
    log.error('contact send failed', err)
    await emailConfig.recordStatus({ status: 'error', statusDetail: err.message })
    throw err
  }
}

/**
 * Send a test email to `to`, used by the admin "Send test" button. Throws on
 * failure; records the outcome either way. Returns { sent: true } on success.
 */
async function sendTest(to) {
  const built = await buildTransport()
  if (!built) {
    const err = new Error('Email is not configured. Connect Gmail first.')
    err.code = 'NOT_CONFIGURED'
    throw err
  }
  const { transport, config } = built
  const recipient = to || (await contactRecipient(config.senderEmail))
  if (!recipient) {
    const err = new Error('No recipient available for the test email.')
    err.code = 'NO_RECIPIENT'
    throw err
  }
  try {
    await transport.sendMail({
      from: fromHeader(config),
      to: recipient,
      subject: `${brand.name} email test`,
      text: 'This is a test message confirming Gmail OAuth2 email delivery is working.',
    })
    await emailConfig.recordStatus({ status: 'connected', statusDetail: 'Test send OK', lastVerifiedAt: new Date() })
    return { sent: true, to: recipient }
  } catch (err) {
    log.error('test send failed', err)
    await emailConfig.recordStatus({ status: 'error', statusDetail: err.message })
    throw err
  }
}

/**
 * Send an account invite. `to` is the invitee's email, `acceptUrl` the tokened
 * accept link, `role` their assigned access level, `invitedByName` optional. If
 * email is not configured, returns { sent: false, reason: 'NOT_CONFIGURED' } so
 * the caller can surface the accept link for the admin to share manually rather
 * than throwing. Throws only on an actual send failure.
 */
async function sendInvite({ to, acceptUrl, role, invitedByName }) {
  const built = await buildTransport()
  if (!built) return { sent: false, reason: 'NOT_CONFIGURED' }
  const { transport, config } = built
  const roleLabel = role && role !== 'player' ? ` as ${role}` : ''
  const by = invitedByName ? ` by ${invitedByName}` : ''
  try {
    await transport.sendMail({
      from: fromHeader(config),
      to,
      subject: `Your ${brand.name} invitation`,
      text:
        `You have been invited${by} to join ${brand.name}${roleLabel}.\n\n` +
        `Accept your invitation and set up your account here:\n${acceptUrl}\n\n` +
        `This link is single-use and will expire. If you weren't expecting this, you can ignore it.`,
    })
    await emailConfig.recordStatus({ status: 'connected', statusDetail: 'Invite send OK', lastVerifiedAt: new Date() })
    return { sent: true }
  } catch (err) {
    log.error('invite send failed', err)
    await emailConfig.recordStatus({ status: 'error', statusDetail: err.message })
    throw err
  }
}

/**
 * Send a password-reset link. `to` is the account's email, `resetUrl` the tokened
 * reset link, `username` names which account it's for (email is non-unique, so one
 * address may receive a link per account). If email is not configured, returns
 * { sent: false, reason: 'NOT_CONFIGURED' } — the caller still returns a generic
 * success to avoid leaking whether the address exists. Throws only on a send failure.
 */
async function sendPasswordReset({ to, resetUrl, username }) {
  const built = await buildTransport()
  if (!built) return { sent: false, reason: 'NOT_CONFIGURED' }
  const { transport, config } = built
  const forWhom = username ? ` for the account “${username}”` : ''
  try {
    await transport.sendMail({
      from: fromHeader(config),
      to,
      subject: `Reset your ${brand.name} password`,
      text:
        `We received a request to reset the password${forWhom} at ${brand.name}.\n\n` +
        `Choose a new password here:\n${resetUrl}\n\n` +
        `This link is single-use and expires in about an hour. If you didn't request ` +
        `this, you can safely ignore this email — your password won't change.`,
    })
    await emailConfig.recordStatus({ status: 'connected', statusDetail: 'Password reset send OK', lastVerifiedAt: new Date() })
    return { sent: true }
  } catch (err) {
    log.error('password reset send failed', err)
    await emailConfig.recordStatus({ status: 'error', statusDetail: err.message })
    throw err
  }
}

module.exports = { isConfigured, sendContactMessage, sendTest, sendInvite, sendPasswordReset }
