import crypto from 'node:crypto'
import type { RowDataPacket } from 'mysql2'
import { db, DB } from './db'
import { normalizeEmail, bnetMakeRegistration, EMAIL_RE } from './bnet'
import { sendMail } from './mail'
import {
  activationEmailHtml,
  accountNamesEmailHtml,
  passwordResetEmailHtml,
  securityTokensListEmailHtml,
} from './emails'


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

/**
 * Recuperación por email/usuario (anti-enumeración: respuesta genérica siempre).
 * `value` es el nombre de usuario (type=password) o el correo (resto).
 */
export async function requestRecovery(type: string, value: string): Promise<Result> {
  const site = process.env.SITE_URL || ''

  // Las CUATRO opciones se recuperan por CORREO (con Battle.net, la cuenta es el
  // correo). Solo se valida que lo sea: NO se exige Gmail, porque el registro solo
  // lo admite hoy pero las cuentas ya creadas tienen correos de todo tipo y no
  // podrían recuperarse.
  const email = (value || '').trim()
  if (!email || !EMAIL_RE.test(email)) return { success: false, error: 'invalidEmail' }

  if (type === 'password') {
    try {
      const [bn] = await db(DB.auth).query<RowDataPacket[]>(
        'SELECT email FROM battlenet_accounts WHERE email = ? LIMIT 1',
        [normalizeEmail(email)],
      )
      // El correo se toma de la BD, NO el tecleado: el verifier SRP6 de bnet se
      // deriva del correo, así que `resetPassword` necesita exactamente la misma
      // forma (MAYÚSCULAS) o generaría un verifier con el que no se podría entrar.
      const found = bn[0]?.email
      if (found) {
        const token = crypto.randomBytes(32).toString('hex')
        await db(DB.default).query(
          'INSERT INTO passwordreset (email, token, created_at, used) VALUES (?, ?, NOW(), 0)',
          [found, token],
        )
        const link = `${site}/reset-password?token=${token}`
        await sendMail(found, 'Restablecer contraseña - NightSpire', passwordResetEmailHtml(found, link))
      }
    } catch {
      /* acore no disponible: respuesta genérica igualmente */
    }
    return { success: true }
  }

  if (type === 'securitytoken') {
    try {
      const [bn] = await db(DB.auth).query<RowDataPacket[]>(
        'SELECT id FROM battlenet_accounts WHERE email = ?',
        [normalizeEmail(email)],
      )
      if (bn[0]) {
        const [games] = await db(DB.auth).query<RowDataPacket[]>(
          'SELECT id, username FROM account WHERE battlenet_account = ? ORDER BY battlenet_index',
          [bn[0].id],
        )
        const ids = games.map((g) => g.id)
        if (ids.length) {
          const placeholders = ids.map(() => '?').join(',')
          const [toks] = await db(DB.default).query<RowDataPacket[]>(
            `SELECT user_id, token FROM securitytoken WHERE user_id IN (${placeholders})`,
            ids,
          )
          if (toks.length) {
            const nameById = new Map(games.map((g) => [g.id, g.username]))
                        await sendMail(
              email,
              'Tu Token de seguridad - NightSpire',
              securityTokensListEmailHtml(
                email,
                toks.map((r) => ({ username: String(nameById.get(r.user_id) ?? r.user_id), token: r.token })),
              ),
            )
          }
        }
      }
    } catch {
      /* ignore */
    }
    return { success: true }
  }

  if (type === 'accountname') {
    try {
      const [acc] = await db(DB.auth).query<RowDataPacket[]>(
        'SELECT id FROM battlenet_accounts WHERE email = ?',
        [normalizeEmail(email)],
      )
      if (acc[0]) {
        const [games] = await db(DB.auth).query<RowDataPacket[]>(
          'SELECT username FROM account WHERE battlenet_account = ? ORDER BY battlenet_index',
          [acc[0].id],
        )
        await sendMail(
          email,
          'Tus cuentas de juego - NightSpire',
          accountNamesEmailHtml(
            email,
            games.map((g) => String(g.username)),
          ),
        )
      }
    } catch {
      /* ignore */
    }
    return { success: true }
  }

  if (type === 'activation') {
    try {
      const [rows] = await db(DB.default).query<RowDataPacket[]>(
        'SELECT hash, password FROM accountactivation WHERE email = ? AND old_email IS NULL ORDER BY created_at DESC LIMIT 1',
        [email],
      )
      if (rows[0]) {
        const link = `${site}/activate-account?act=${rows[0].hash}`
        // Mismo correo que manda `registerAccount`: normalizado (MAYÚSCULAS) al
        // mostrarlo, sin normalizar como destinatario.
        await sendMail(
          email,
          `Activación de la cuenta ${normalizeEmail(email)} - NightSpire`,
          activationEmailHtml(normalizeEmail(email), link),
        )
      }
    } catch {
      /* ignore */
    }
    return { success: true }
  }

  return { success: false, error: 'invalidType' }
}

/**
 * ¿El enlace de restablecer sigue sirviendo? Mismas condiciones que `resetPassword`
 * (existe, sin usar y dentro de la hora) pero SIN consumirlo: lo llama la página al
 * abrirse, para no enseñar el formulario de un enlace que ya no vale.
 */
export async function isResetTokenValid(token: string): Promise<boolean> {
  if (!token) return false
  try {
    const [rows] = await db(DB.default).query<RowDataPacket[]>(
      'SELECT created_at FROM passwordreset WHERE token = ? AND used = 0',
      [token],
    )
    const pr = rows[0]
    if (!pr) return false
    return Date.now() - new Date(pr.created_at).getTime() <= 3600_000
  } catch {
    return false
  }
}

export async function resetPassword(token: string, newPassword: string, confPassword: string): Promise<Result> {
  const [rows] = await db(DB.default).query<RowDataPacket[]>(
    'SELECT id, email, created_at FROM passwordreset WHERE token = ? AND used = 0',
    [token],
  )
  const pr = rows[0]
  if (!pr) return { success: false, error: 'invalidLink' }
  if (Date.now() - new Date(pr.created_at).getTime() > 3600_000) return { success: false, error: 'expiredLink' }
  if (!newPassword || newPassword !== confPassword) return { success: false, error: 'passwordMismatch' }
  if (newPassword.length > 16) return { success: false, error: 'passwordTooLong' }

  try {
    const reg = bnetMakeRegistration(pr.email, newPassword)
    const [res] = await db(DB.auth).query(
      'UPDATE battlenet_accounts SET srp_version = ?, salt = ?, verifier = ? WHERE email = ?',
      [reg.srpVersion, reg.salt, reg.verifier, normalizeEmail(pr.email)],
    )
    // @ts-expect-error affectedRows
    if (!res.affectedRows) return { success: false, error: 'accountNotFound' }
  } catch {
    return { success: false, error: 'genericError' }
  }
  await db(DB.default).query('UPDATE passwordreset SET used = 1 WHERE id = ?', [pr.id])
  return { success: true, redirect: '/log-in' }
}
