import type { ResultSetHeader, RowDataPacket } from 'mysql2'
import { db, DB } from './db'
import { normalizeEmail, bnetMakeRegistration, gameMakeRegistration, makeGameAccountUsername, EMAIL_RE } from './bnet'
import { sendMail } from './mail'
import { activationEmailHtml } from './emails'
import { randomToken } from './random-token'


export interface RegisterInput {
  password: string
  confPassword: string
  email: string
  confEmail: string
  recruiter?: string
}

export interface Result {
  success: boolean
  error?: string
  /** Solo al activar: el correo de la cuenta, para poder decir cuál se activó. */
  email?: string
}

export async function registerAccount(input: RegisterInput): Promise<Result> {
  const password = (input.password || '').trim()
  const confPassword = (input.confPassword || '').trim()
  const email = (input.email || '').trim()
  const confEmail = (input.confEmail || '').trim()
  const recruiter = (input.recruiter || '').trim()

  if (!password || !email) return { success: false, error: 'missingFields' }
  if (password !== confPassword) return { success: false, error: 'passwordMismatch' }
  if (password.length > 16) return { success: false, error: 'passwordTooLong' }
  if (email !== confEmail || !EMAIL_RE.test(email)) return { success: false, error: 'invalidEmail' }

  // ¿ya existe una cuenta Battle.net con ese email?
  try {
    const [rows] = await db(DB.auth).query<RowDataPacket[]>(
      'SELECT COUNT(*) AS n FROM battlenet_accounts WHERE email = ?',
      [normalizeEmail(email)],
    )
    if (Number(rows[0]?.n ?? 0) > 0) return { success: false, error: 'emailExists' }
  } catch {
    // BD de cuentas no disponible en dev: continuamos.
  }

  // Borrar cualquier activación de registro pendiente para ese email
  await db(DB.default).query('DELETE FROM accountactivation WHERE email = ? AND old_email IS NULL', [email])

  // Reclutador opcional
  let recruiterId = 0
  if (recruiter) {
    try {
      const [a] = await db(DB.auth).query<RowDataPacket[]>('SELECT id FROM account WHERE username = ?', [recruiter])
      if (a[0]) {
        recruiterId = a[0].id
      } else {
        const [c] = await db(DB.characters).query<RowDataPacket[]>(
          'SELECT account FROM characters WHERE name = ?',
          [recruiter],
        )
        if (c[0]) recruiterId = c[0].account
        else return { success: false, error: 'recruiterNotFound' }
      }
    } catch {
      return { success: false, error: 'recruiterNotFound' }
    }
  }

  // 32 alfanuméricos, como el `get_random_string(32)` original (col hash = varchar(32)).
  // Se portó como `randomBytes(16).toString('hex')`, que llenaba los 32 caracteres pero
  // solo con `0-9a-f`: cambiaba el formato del enlace de activación sin querer.
  const hash = randomToken(32)
  await db(DB.default).query(
    'INSERT INTO accountactivation (email, password, recruiter_id, hash, created_at, is_used, is_new_email_used) VALUES (?, ?, ?, ?, NOW(), 0, 0)',
    [email, password, recruiterId, hash],
  )

  const link = `${process.env.SITE_URL}/activate-account?act=${hash}`
  // El correo se muestra normalizado (MAYÚSCULAS) en el asunto y en el cuerpo, igual
  // que en el del token de seguridad, que lo saca de `session.bnetEmail` (ya normalizado).
  // El DESTINATARIO va sin normalizar: es la dirección tal cual la escribió el usuario.
  await sendMail(
    email,
    `Activación de la cuenta ${normalizeEmail(email)} - NightSpire`,
    activationEmailHtml(normalizeEmail(email), link),
  )

  return { success: true }
}

export async function activateAccount(hash: string, lastIp = '0.0.0.0'): Promise<Result> {
  const [rows] = await db(DB.default).query<RowDataPacket[]>(
    'SELECT id, email, password, recruiter_id, created_at FROM accountactivation WHERE hash = ? AND old_email IS NULL',
    [hash],
  )
  const act = rows[0]
  if (!act) return { success: false, error: 'invalidLink' }
  if (Date.now() - new Date(act.created_at).getTime() > 3600_000) return { success: false, error: 'expiredLink' }

  const emailNorm = normalizeEmail(act.email)
  const password: string = act.password

  // Volver a comprobar que el correo sigue libre. `registerAccount` ya lo mira, pero
  // entre registrarse y activar puede haberse creado la cuenta (otra activación
  // pendiente del mismo correo, un cambio de correo confirmado...), y sin esto se
  // insertaba una bnet duplicada: `battlenet_accounts.email` no tiene índice único.
  const [dup] = await db(DB.auth).query<RowDataPacket[]>(
    'SELECT COUNT(*) AS n FROM battlenet_accounts WHERE email = ?',
    [emailNorm],
  )
  if (Number(dup[0]?.n ?? 0) > 0) {
    // La activación ya no sirve para nada: la cuenta existe. Se borra para que el
    // enlace no quede dando vueltas.
    await db(DB.default).query('DELETE FROM accountactivation WHERE id = ?', [act.id])
    return { success: false, error: 'emailExists' }
  }

  // Cuenta Battle.net (SRP6 v2)
  const bnet = bnetMakeRegistration(emailNorm, password)
  const [bnetRes] = await db(DB.auth).query<ResultSetHeader>(
    'INSERT INTO battlenet_accounts (email, srp_version, salt, verifier) VALUES (?, ?, ?, ?)',
    [emailNorm, bnet.srpVersion, bnet.salt, bnet.verifier],
  )
  const bnetId = bnetRes.insertId

  // Cuenta de juego (SRP6 Grunt)
  const gameUsername = makeGameAccountUsername(bnetId, 1)
  const game = gameMakeRegistration(gameUsername, password)
  await db(DB.auth).query<ResultSetHeader>(
    'INSERT INTO account (username, salt, verifier, email, reg_mail, recruiter, joindate, last_ip, expansion, battlenet_account, battlenet_index) ' +
      'VALUES (?, ?, ?, ?, ?, ?, NOW(), ?, ?, ?, 1)',
    [gameUsername, game.salt, game.verifier, emailNorm, emailNorm, act.recruiter_id || 0, lastIp, 2, bnetId],
  )

  await db(DB.default).query('DELETE FROM accountactivation WHERE id = ?', [act.id])
  // El correo tal cual se registró, no `emailNorm`: ese va en MAYÚSCULAS porque lo
  // exige el SRP6 de Battle.net, y en pantalla quedaría como INNA@INNA.CL.
  return { success: true, email: act.email }
}
