import crypto from 'crypto'
import nodemailer from 'nodemailer'
import { prisma } from '../../infra/database/prisma'
import { dragonfly } from '../../infra/cache/dragonfly'
import { logger } from '../../config/logger'

const OTP_TTL    = 10 * 60          // 10 minutos
const KEY_TTL    = 90 * 24 * 3600   // 90 dias

interface OtpPayload {
  code:         string
  clientSystem: string
  clientName:   string
  clientUrl:    string | null
}

interface KeyPayload {
  userId:       string
  email:        string
  clientSystem: string
  clientName:   string
}

// ─── Keys do Dragonfly ────────────────────────────────────────────────────────

const otpKey = (email: string)           => `pair:otp:${email}`
const intKey = (integrationKey: string)  => `pair:key:${integrationKey}`

// ─── Email sender ─────────────────────────────────────────────────────────────

async function sendOtpEmail(to: string, code: string, clientName: string): Promise<boolean> {
  // Lê config SMTP do admin settings
  const raw = await dragonfly.getJson<any>('system:settings')
  const smtp = raw?.smtp ?? {}

  if (!smtp.host || !smtp.user) {
    logger.warn('[pair] SMTP não configurado — email de OTP não enviado. Configure em Admin → Configurações.')
    logger.info(`[pair] OTP para ${to}: ${code}`)  // fallback: loga localmente
    return false
  }

  const transporter = nodemailer.createTransport({
    host:   smtp.host,
    port:   smtp.port ?? 587,
    secure: smtp.secure ?? false,
    auth:   { user: smtp.user, pass: smtp.password },
  })

  await transporter.sendMail({
    from:    smtp.from ?? smtp.user,
    to,
    subject: `[NewWhats] Código de pareamento — ${clientName}`,
    text: [
      `Olá!`,
      ``,
      `O sistema "${clientName}" solicitou conexão com sua conta NewWhats.`,
      ``,
      `Seu código de confirmação: ${code}`,
      ``,
      `Válido por 10 minutos.`,
      ``,
      `Se você não solicitou isso, ignore este email.`,
    ].join('\n'),
    html: `
      <p>Olá!</p>
      <p>O sistema <strong>${clientName}</strong> solicitou conexão com sua conta NewWhats.</p>
      <h2 style="letter-spacing:8px;font-size:32px">${code}</h2>
      <p style="color:#888">Válido por 10 minutos. Se não foi você, ignore este email.</p>
    `,
  })

  logger.info(`[pair] OTP enviado para ${to}`)
  return true
}

// ─── PairService ──────────────────────────────────────────────────────────────

export class PairService {

  // Etapa 1 — solicitar pareamento
  async request(input: {
    email:        string
    clientSystem: string
    clientName:   string
    clientUrl:    string | null
  }) {
    const user = await prisma.user.findUnique({ where: { email: input.email } })
    if (!user) {
      // Resposta genérica — não revela se o email existe ou não
      return { ok: true, message: 'Se o email existir, um código será enviado.' }
    }

    if (!user.isActive) {
      throw Object.assign(new Error('Conta desativada'), { statusCode: 403 })
    }

    // Gera OTP de 6 dígitos
    const code = Math.floor(100_000 + Math.random() * 900_000).toString()

    const payload: OtpPayload = {
      code,
      clientSystem: input.clientSystem,
      clientName:   input.clientName,
      clientUrl:    input.clientUrl,
    }

    await dragonfly.setJson(otpKey(input.email), payload, OTP_TTL)
    const emailSent = await sendOtpEmail(input.email, code, input.clientName)

    // Quando SMTP não está configurado, devolve o código na resposta
    // para que o admin possa completar o pareamento manualmente.
    if (!emailSent) {
      return { ok: true, message: 'SMTP não configurado.', dev_code: code }
    }

    return { ok: true, message: 'Se o email existir, um código será enviado.' }
  }

  // Etapa 2 — confirmar com o código recebido
  async confirm(input: {
    email:        string
    code:         string
    clientSystem: string
    clientName:   string
    clientUrl:    string | null
  }) {
    const stored = await dragonfly.getJson<OtpPayload>(otpKey(input.email))

    if (!stored) {
      throw Object.assign(new Error('Código expirado ou inexistente. Solicite um novo.'), { statusCode: 400 })
    }

    if (stored.code !== input.code) {
      throw Object.assign(new Error('Código incorreto.'), { statusCode: 400 })
    }

    if (stored.clientSystem !== input.clientSystem) {
      throw Object.assign(new Error('Sistema não corresponde ao da solicitação.'), { statusCode: 400 })
    }

    const user = await prisma.user.findUnique({ where: { email: input.email } })
    if (!user || !user.isActive) {
      throw Object.assign(new Error('Conta não encontrada ou inativa.'), { statusCode: 404 })
    }

    // Revoga par anterior do mesmo sistema (evita duplicatas)
    await prisma.pluginPair.updateMany({
      where: { userId: user.id, clientSystem: input.clientSystem, revokedAt: null },
      data:  { revokedAt: new Date() },
    })

    // Gera integration_key exclusiva
    const integrationKey = `nwk_${crypto.randomUUID().replace(/-/g, '')}`

    const pair = await prisma.pluginPair.create({
      data: {
        userId:         user.id,
        clientSystem:   input.clientSystem,
        clientName:     input.clientName,
        clientUrl:      input.clientUrl,
        integrationKey,
      },
    })

    // Cache rápido para validação sem bater no banco
    const keyPayload: KeyPayload = {
      userId:       user.id,
      email:        user.email,
      clientSystem: input.clientSystem,
      clientName:   input.clientName,
    }
    await dragonfly.setJson(intKey(integrationKey), keyPayload, KEY_TTL)

    // Limpa OTP usado
    await dragonfly.del(otpKey(input.email))

    logger.info(`[pair] ${user.email} ↔ ${input.clientName} pareados (${pair.id})`)

    return {
      integration_key: integrationKey,
      account: { name: user.name, email: user.email },
      paired_at: pair.createdAt,
    }
  }

  // Verifica se uma integration_key é válida (chamada pelo sistema cliente)
  async verify(integrationKey: string) {
    // Fast path — Dragonfly
    const cached = await dragonfly.getJson<KeyPayload>(intKey(integrationKey))
    if (cached) {
      // Atualiza lastSeenAt de forma assíncrona (sem bloquear a resposta)
      prisma.pluginPair.updateMany({
        where: { integrationKey, revokedAt: null },
        data:  { lastSeenAt: new Date() },
      }).catch(() => {})

      return { valid: true, account: { email: cached.email }, client: cached.clientSystem }
    }

    // Slow path — banco
    const pair = await prisma.pluginPair.findUnique({
      where: { integrationKey },
      include: { user: { select: { name: true, email: true, isActive: true } } },
    })

    if (!pair || pair.revokedAt || !pair.user.isActive) {
      return { valid: false }
    }

    // Restaura cache
    const keyPayload: KeyPayload = {
      userId:       pair.userId,
      email:        pair.user.email,
      clientSystem: pair.clientSystem,
      clientName:   pair.clientName,
    }
    await dragonfly.setJson(intKey(integrationKey), keyPayload, KEY_TTL)

    await prisma.pluginPair.update({
      where: { integrationKey },
      data:  { lastSeenAt: new Date() },
    })

    return { valid: true, account: { name: pair.user.name, email: pair.user.email }, client: pair.clientSystem }
  }

  // Lista pares ativos do usuário autenticado
  async list(userId: string) {
    const pairs = await prisma.pluginPair.findMany({
      where:   { userId, revokedAt: null },
      orderBy: { createdAt: 'desc' },
      select: {
        id:            true,
        clientSystem:  true,
        clientName:    true,
        clientUrl:     true,
        lastSeenAt:    true,
        createdAt:     true,
      },
    })
    return pairs
  }

  // Revoga um par (usuário autenticado só pode revogar os seus)
  async revoke(userId: string, pairId: string) {
    const pair = await prisma.pluginPair.findFirst({
      where: { id: pairId, userId, revokedAt: null },
    })

    if (!pair) {
      throw Object.assign(new Error('Conexão não encontrada'), { statusCode: 404 })
    }

    await prisma.pluginPair.update({
      where: { id: pairId },
      data:  { revokedAt: new Date() },
    })

    // Remove do cache imediatamente
    await dragonfly.del(intKey(pair.integrationKey))

    logger.info(`[pair] Par ${pairId} revogado por ${userId}`)
    return { ok: true }
  }
}
