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

const GMAIL_RE = /^[a-zA-Z0-9._%+-]+@gmail\.com$/

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> {
  value = (value || '').trim()
  const site = process.env.SITE_URL || ''

  // La contraseña se recupera por NOMBRE DE USUARIO (p.ej. 15#1): se resuelve el
  // correo Battle.net asociado y se envía el enlace de restablecimiento.
  if (type === 'password') {
    if (!value || value.length > 20) return { success: false, error: 'invalidUsername' }
    try {
      const [acc] = await db(DB.auth).query<RowDataPacket[]>(
        'SELECT battlenet_account FROM account WHERE username = ? LIMIT 1',
        [value],
      )
      const bnetId = acc[0]?.battlenet_account
      if (bnetId) {
        const [bn] = await db(DB.auth).query<RowDataPacket[]>(
          'SELECT email FROM battlenet_accounts WHERE id = ?',
          [bnetId],
        )
        const email = bn[0]?.email
        if (email) {
          const token = crypto.randomBytes(32).toString('hex')
          await db(DB.default).query(
            'INSERT INTO passwordreset (email, token, created_at, used) VALUES (?, ?, NOW(), 0)',
            [email, token],
          )
          const link = `${site}/reset-password?token=${token}`
          await sendMail(
            email,
            'Restablecer contraseña - NightSpire',
            passwordResetEmailHtml(email, link),
          )
        }
      }
    } catch {
      /* acore no disponible: respuesta genérica igualmente */
    }
    return { success: true }
  }

  // El resto se recupera por CORREO.
  const email = value
  if (!email || !GMAIL_RE.test(email)) return { success: false, error: 'invalidEmail' }

  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}`
        await sendMail(
          email,
          `Activación de la cuenta ${email} - NightSpire`,
          activationEmailHtml(email, link),
        )
      }
    } catch {
      /* ignore */
    }
    return { success: true }
  }

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

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' }
}
