import crypto from 'node:crypto'
import type { RowDataPacket, ResultSetHeader } from 'mysql2'
import { db, DB } from './db'
import { authenticate } from './auth'
import { bnetMakeRegistration, normalizeEmail, EMAIL_RE } from './bnet'
import { checkSecurityToken } from './security-token'
import { sendMail } from './mail'
import { confirmNewEmailHtml, confirmOldEmailHtml, oldEmailNotificationHtml } from './emails'
import type { SessionData } from './session'


export interface Result {
  success: boolean
  error?: string
}

export async function requestEmailChange(
  session: SessionData,
  curPassword: string,
  curEmail: string,
  newEmail: string,
  confEmail: string,
  token: string,
): Promise<Result> {
  const current = session.bnetEmail ?? '' // ya normalizado (MAYÚS)
  const userId = session.accountId ?? 0
  if (!curPassword || !curEmail || !newEmail || !confEmail || !token) return { success: false, error: 'missingFields' }
  if (normalizeEmail(curEmail) !== current) return { success: false, error: 'wrongCurrentEmail' }
  if (newEmail !== confEmail || !EMAIL_RE.test(newEmail)) return { success: false, error: 'invalidEmail' }
  if (!(await checkSecurityToken(userId, token))) return { success: false, error: 'invalidToken' }
  if (!(await authenticate(current, curPassword))) return { success: false, error: 'wrongCurrentPassword' }

  const oldHash = crypto.randomBytes(16).toString('hex')
  const newHash = crypto.randomBytes(16).toString('hex')
  await db(DB.default).query(
    'INSERT INTO accountactivation (username, email, old_email, password, recruiter_id, hash, old_email_hash, is_used, is_new_email_used, created_at) ' +
      'VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, NOW())',
    [session.username ?? null, newEmail, current, curPassword, userId, newHash, oldHash],
  )

  const site = process.env.SITE_URL || ''
  const link = `${site}/confirm-old-email?hash=${oldHash}`
  await sendMail(
    current,
    `Confirma el cambio de correo - NightSpire`,
    confirmOldEmailHtml(current, session.username ?? null, newEmail, link),
  )
  return { success: true }
}

export async function confirmOldEmail(hash: string): Promise<Result> {
  // `old_email` es el correo con el que la cuenta se identifica AHORA: el cambio
  // aún no ha terminado, así que es con ese con el que hay que saludar.
  const [rows] = await db(DB.default).query<RowDataPacket[]>(
    'SELECT id, email, old_email, hash, username FROM accountactivation WHERE old_email_hash = ? AND is_used = 0',
    [hash],
  )
  const act = rows[0]
  if (!act) return { success: false, error: 'invalidLink' }
  await db(DB.default).query('UPDATE accountactivation SET is_used = 1 WHERE id = ?', [act.id])

  const site = process.env.SITE_URL || ''
  const link = `${site}/confirm-new-email?hash=${act.hash}`
  await sendMail(
    act.email,
    `Confirma tu nuevo correo - NightSpire`,
    confirmNewEmailHtml(act.old_email || act.email, act.username ?? null, link),
  )
  return { success: true }
}

export async function confirmNewEmail(hash: string): Promise<Result> {
  const [rows] = await db(DB.default).query<RowDataPacket[]>(
    'SELECT id, email, old_email, password, username FROM accountactivation WHERE hash = ? AND is_new_email_used = 0 AND old_email IS NOT NULL',
    [hash],
  )
  const act = rows[0]
  if (!act) return { success: false, error: 'invalidLink' }
  await db(DB.default).query('UPDATE accountactivation SET is_new_email_used = 1 WHERE id = ?', [act.id])

  const newNorm = normalizeEmail(act.email)
  const oldNorm = normalizeEmail(act.old_email || '')
  try {
    const reg = bnetMakeRegistration(newNorm, act.password)
    const [bnetRows] = await db(DB.auth).query<RowDataPacket[]>(
      'SELECT id FROM battlenet_accounts WHERE email = ?',
      [oldNorm],
    )
    if (!bnetRows[0]) return { success: false, error: 'accountNotFound' }
    const bnetId = bnetRows[0].id
    await db(DB.auth).query<ResultSetHeader>(
      'UPDATE battlenet_accounts SET email = ?, srp_version = ?, salt = ?, verifier = ? WHERE id = ?',
      [newNorm, reg.srpVersion, reg.salt, reg.verifier, bnetId],
    )
    await db(DB.auth).query('UPDATE account SET email = ?, reg_mail = ? WHERE battlenet_account = ?', [
      newNorm,
      newNorm,
      bnetId,
    ])
  } catch {
    return { success: false, error: 'genericError' }
  }

  // Avisar al correo ANTIGUO de que ya no manda en la cuenta. Va después de que el
  // cambio haya cuajado (si falla arriba, no hay nada que avisar) y sin enlaces:
  // solo informa. Si alguien secuestra una sesión y cambia el correo, esta es la
  // única forma que tiene el dueño de enterarse, porque al entrar se usa el correo.
  // `sendMail` nunca lanza (devuelve false), así que no puede tumbar un cambio ya
  // hecho: como mucho el aviso no sale.
  await sendMail(
    oldNorm,
    `Cambio de correo realizado - NightSpire`,
    oldEmailNotificationHtml(oldNorm, act.username ?? null, newNorm),
  )

  await db(DB.default).query('DELETE FROM accountactivation WHERE id = ?', [act.id])
  return { success: true }
}
