import path from 'path'
import fs from 'fs/promises'
import { Router, type Request, type Response } from 'express'
import multer from 'multer'
import { downloadMediaMessage, getContentType } from './engine'
import { prisma } from '../../infra/database/prisma'
import { logger } from '../../config/logger'
import { storageProvider } from '../../core/StorageProvider'
import type { WhatsAppConnectionManager } from './connection/WhatsAppConnectionManager'

// ─── Tipos de mídia suportados ────────────────────────────────────────────────

const MIME_MAP: Record<string, 'image' | 'video' | 'audio' | 'document' | 'sticker'> = {
  'image/jpeg': 'image',
  'image/png': 'image',
  'image/webp': 'sticker',
  'image/gif': 'video',        // GIFs são enviados como video no Baileys
  'video/mp4': 'video',
  'video/3gpp': 'video',
  'video/quicktime': 'video',
  'audio/ogg': 'audio',
  'audio/mpeg': 'audio',
  'audio/mp4': 'audio',
  'audio/wav': 'audio',
  'audio/webm': 'audio',
  'application/pdf': 'document',
  'application/msword': 'document',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'document',
  'application/vnd.ms-excel': 'document',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'document',
  'application/zip': 'document',
  'text/plain': 'document',
}

const MAX_FILE_SIZE = 64 * 1024 * 1024 // 64 MB

// ─── Multer: armazena em memória (buffer) ─────────────────────────────────────
// Buffers são passados diretamente ao Baileys sem gravar arquivo temporário

const upload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: MAX_FILE_SIZE },
  fileFilter: (_req, file, cb) => {
    if (MIME_MAP[file.mimetype]) {
      cb(null, true)
    } else {
      cb(new Error(`Tipo de arquivo não suportado: ${file.mimetype}`))
    }
  },
})

// ─── Helper: salva mídia enviada no disco (para histórico local) ──────────────

async function saveMediaLocally(
  instanceId: string,
  messageId: string,
  buffer: Buffer,
  originalName: string
): Promise<string> {
  const ext = path.extname(originalName) || '.bin'
  const fileName = `${messageId}${ext}`
  const filePath = path.resolve('./media', instanceId, 'sent', fileName)
  await fs.mkdir(path.dirname(filePath), { recursive: true })
  await fs.writeFile(filePath, buffer)
  return `/media/${instanceId}/sent/${fileName}`
}

// ─── Router ───────────────────────────────────────────────────────────────────

export function buildMediaRoutes(manager: WhatsAppConnectionManager): Router {
  const router = Router({ mergeParams: true })

  // ── POST /api/instances/:instanceId/send-media ─────────────────────────────
  // Body: multipart/form-data  { file, jid, caption?, replyToMessageId? }
  router.post(
    '/send-media',
    upload.single('file'),
    async (req: Request, res: Response) => {
      try {
        const tenantId = req.tenantId!
        const instanceId = req.params['instanceId'] as string
        const { jid, caption, replyToMessageId } = req.body as {
          jid: string
          caption?: string
          replyToMessageId?: string
        }

        if (!req.file) { res.status(400).json({ error: 'Arquivo não enviado' }); return }
        if (!jid)      { res.status(400).json({ error: 'jid é obrigatório' }); return }

        const instance = await prisma.instance.findFirst({ where: { id: instanceId, tenantId } })
        if (!instance || instance.status !== 'CONNECTED') {
          res.status(400).json({ error: 'Instância não conectada' }); return
        }

        const sock = manager.getSocket(instanceId)
        if (!sock) { res.status(503).json({ error: 'Socket não disponível' }); return }

        const { buffer, mimetype, originalname, size } = req.file
        const mediaType = MIME_MAP[mimetype] ?? 'document'

        // ── Monta o content para o Baileys ────────────────────────────────────
        let content: Record<string, unknown>

        switch (mediaType) {
          case 'image':
            content = { image: buffer, mimetype, caption: caption ?? '' }
            break
          case 'video':
            content = {
              video: buffer,
              mimetype,
              caption: caption ?? '',
              gifPlayback: mimetype === 'image/gif',
            }
            break
          case 'audio':
            content = { audio: buffer, mimetype, ptt: false }
            break
          case 'sticker':
            content = { sticker: buffer, mimetype }
            break
          default:
            content = { document: buffer, mimetype, fileName: originalname, caption: caption ?? '' }
        }

        // ── Reply context ─────────────────────────────────────────────────────
        let quotedMsg: any
        if (replyToMessageId) {
          const reply = await prisma.message.findUnique({
            where: { instanceId_messageId: { instanceId, messageId: replyToMessageId } },
          })
          if (reply?.body) {
            quotedMsg = {
              key: { remoteJid: jid, fromMe: reply.fromMe, id: reply.messageId },
              message: { conversation: reply.body },
            }
          }
        }

        // ── Envia via Baileys ─────────────────────────────────────────────────
        const sent = await sock.sendMessage(jid, content as any, quotedMsg ? { quoted: quotedMsg } : undefined)

        // ── Salva arquivo e faz upload para Wasabi ────────────────────────────
        const messageId = sent?.key.id ?? `local-${Date.now()}`
        const localUrl  = await saveMediaLocally(instanceId, messageId, buffer, originalname)

        let mediaUrl = localUrl
        if (storageProvider.isRegistered()) {
          try {
            const result = await storageProvider.uploadFile({
              category: 'whatsapp',
              usuarioSis: tenantId,
              sessionJID: instanceId,
              file: { originalname, buffer, mimetype },
            })
            mediaUrl = result.path
          } catch (uploadErr) {
            logger.warn({ uploadErr, messageId }, 'Upload Wasabi falhou (mídia enviada), usando path local')
          }
        }

        // ── Garante chat + persiste mensagem ─────────────────────────────────
        const chat = await prisma.chat.upsert({
          where: { tenantId_instanceId_jid: { tenantId, instanceId, jid } },
          create: { tenantId, instanceId, jid, lastMessageAt: new Date() },
          update: { lastMessageAt: new Date() },
        })

        const messageType = mediaType.toUpperCase() as any
        // Usa upsert para evitar P2002 — o MessageHandler pode ter inserido antes
        const msg = await prisma.message.upsert({
          where: { instanceId_messageId: { instanceId, messageId } },
          create: {
            tenantId,
            instanceId,
            chatId: chat.id,
            remoteJid: jid,
            messageId,
            fromMe: true,
            type: messageType,
            body: caption ?? null,
            mediaUrl,
            mimeType: mimetype,
            fileName: mediaType === 'document' ? originalname : null,
            status: 'SENT',
            timestamp: new Date(),
          },
          update: {
            mediaUrl,
            mimeType: mimetype,
            status: 'SENT',
          },
        })

        logger.info({ instanceId, jid, mediaType, size }, 'Mídia enviada')
        res.status(201).json(msg)
      } catch (err: any) {
        logger.error({ err }, 'Erro ao enviar mídia')
        res.status(500).json({ error: err.message ?? 'Erro ao enviar mídia' })
      }
    }
  )

  // ── POST /api/instances/:instanceId/send-audio ─────────────────────────────
  // Áudio gravado pelo InputBar (PTT — Push to Talk)
  // Body: multipart/form-data  { audio (blob ogg/webm), jid }
  router.post(
    '/send-audio',
    upload.single('audio'),
    async (req: Request, res: Response) => {
      try {
        const tenantId = req.tenantId!
        const instanceId = req.params['instanceId'] as string
        const { jid } = req.body as { jid: string }

        if (!req.file) { res.status(400).json({ error: 'Áudio não enviado' }); return }
        if (!jid)      { res.status(400).json({ error: 'jid é obrigatório' }); return }

        const instance = await prisma.instance.findFirst({ where: { id: instanceId, tenantId } })
        if (!instance || instance.status !== 'CONNECTED') {
          res.status(400).json({ error: 'Instância não conectada' }); return
        }

        const sock = manager.getSocket(instanceId)
        if (!sock) { res.status(503).json({ error: 'Socket não disponível' }); return }

        const { buffer, mimetype, originalname } = req.file

        // PTT (push-to-talk) — aparece como nota de voz no WhatsApp
        const sent = await sock.sendMessage(jid, {
          audio: buffer,
          mimetype: mimetype || 'audio/ogg; codecs=opus',
          ptt: true,
        } as any)

        const messageId = sent?.key.id ?? `local-${Date.now()}`
        const localUrl = await saveMediaLocally(instanceId, messageId, buffer, originalname || 'audio.ogg')

        let mediaUrl = localUrl
        if (storageProvider.isRegistered()) {
          try {
            const result = await storageProvider.uploadFile({
              category: 'whatsapp',
              usuarioSis: tenantId,
              sessionJID: instanceId,
              file: { originalname: originalname || 'audio.ogg', buffer, mimetype: mimetype || 'audio/ogg' },
            })
            mediaUrl = result.path
          } catch (uploadErr) {
            logger.warn({ uploadErr, messageId }, 'Upload Wasabi falhou (áudio enviado), usando path local')
          }
        }

        const chat = await prisma.chat.upsert({
          where: { tenantId_instanceId_jid: { tenantId, instanceId, jid } },
          create: { tenantId, instanceId, jid, lastMessageAt: new Date() },
          update: { lastMessageAt: new Date() },
        })

        // upsert evita colisão com o registro criado pelo MessageHandler via messages.upsert
        const msg = await prisma.message.upsert({
          where: { instanceId_messageId: { instanceId, messageId } },
          create: {
            tenantId,
            instanceId,
            chatId: chat.id,
            remoteJid: jid,
            messageId,
            fromMe: true,
            type: 'AUDIO',
            mimeType: mimetype || 'audio/ogg',
            mediaUrl,
            status: 'SENT',
            timestamp: new Date(),
          },
          update: { mediaUrl, status: 'SENT' },
        })

        logger.info({ instanceId, jid }, 'Áudio PTT enviado')
        res.status(201).json(msg)
      } catch (err: any) {
        logger.error({ err }, 'Erro ao enviar áudio')
        res.status(500).json({ error: err.message ?? 'Erro ao enviar áudio' })
      }
    }
  )

  // ── POST /api/instances/:instanceId/redownload-media/:messageDbId ─────────
  // Re-solicita ao WhatsApp a mídia de uma mensagem cujo download falhou.
  // Usa o payload WAMessage salvo em messages.mediaPayload para reconstruir
  // a requisição com updateMediaMessage (renova URL expirada se necessário).
  router.post('/redownload-media/:messageDbId', async (req: Request, res: Response) => {
    try {
      const tenantId = req.tenantId!
      const instanceId = req.params['instanceId'] as string
      const messageDbId = req.params['messageDbId'] as string

      const msg = await prisma.message.findFirst({
        where: { id: messageDbId, instanceId, tenantId },
        select: { id: true, mediaPayload: true, type: true, chatId: true, mediaUrl: true, messageId: true },
      })
      if (!msg) { res.status(404).json({ error: 'Mensagem não encontrada' }); return }
      if (msg.mediaUrl) { res.json({ mediaUrl: msg.mediaUrl }); return }
      if (!msg.mediaPayload) { res.status(400).json({ error: 'Payload de mídia não disponível para esta mensagem' }); return }

      const sock = manager.getSocket(instanceId)
      if (!sock) { res.status(503).json({ error: 'Instância não conectada' }); return }

      // Desserializa payload: { _b64: base64 } → Buffer/Uint8Array
      const waMsg = deserializePayload(msg.mediaPayload as Record<string, unknown>) as any

      const buffer = await downloadMediaMessage(
        waMsg,
        'buffer',
        {},
        { logger: logger as any, reuploadRequest: sock.updateMediaMessage as any }
      )
      if (!buffer) { res.status(500).json({ error: 'Nenhum buffer retornado pelo WhatsApp' }); return }

      const contentType = getContentType(waMsg.message) ?? 'imageMessage'
      const ext = EXT_MAP[contentType] ?? 'bin'
      const mime = MIME_FOR_TYPE[contentType] ?? 'application/octet-stream'
      const fileName = `${messageDbId}.${ext}`

      let mediaUrl = `/media/${instanceId}/${fileName}`
      if (storageProvider.isRegistered()) {
        try {
          const result = await storageProvider.uploadFile({
            category: 'whatsapp',
            usuarioSis: tenantId,
            sessionJID: instanceId,
            file: { originalname: fileName, buffer: buffer as Buffer, mimetype: mime },
          })
          mediaUrl = result.path
        } catch (uploadErr) {
          logger.warn({ uploadErr, messageDbId }, 'Upload Wasabi falhou no re-download, usando path local')
          const filePath = path.resolve('./media', instanceId, fileName)
          await fs.mkdir(path.dirname(filePath), { recursive: true })
          await fs.writeFile(filePath, buffer as Buffer)
        }
      } else {
        const filePath = path.resolve('./media', instanceId, fileName)
        await fs.mkdir(path.dirname(filePath), { recursive: true })
        await fs.writeFile(filePath, buffer as Buffer)
      }

      await prisma.message.update({ where: { id: messageDbId }, data: { mediaUrl } })

      manager.getIO().to(`chat:${msg.chatId}`).emit('message:media_ready', {
        messageId: messageDbId,
        mediaUrl,
      })

      logger.info({ messageDbId, contentType }, 'Mídia re-baixada com sucesso')
      res.json({ mediaUrl })
    } catch (err: any) {
      logger.error({ err }, 'Erro ao re-baixar mídia')
      res.status(500).json({ error: err.message ?? 'Erro ao re-baixar mídia' })
    }
  })

  return router
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

const EXT_MAP: Record<string, string> = {
  imageMessage:    'jpg',
  videoMessage:    'mp4',
  audioMessage:    'ogg',
  documentMessage: 'bin',
  stickerMessage:  'webp',
}

const MIME_FOR_TYPE: Record<string, string> = {
  imageMessage:    'image/jpeg',
  videoMessage:    'video/mp4',
  audioMessage:    'audio/ogg',
  documentMessage: 'application/octet-stream',
  stickerMessage:  'image/webp',
}

function deserializePayload(val: unknown): unknown {
  if (Array.isArray(val)) return val.map(deserializePayload)
  if (val !== null && typeof val === 'object') {
    const obj = val as Record<string, unknown>
    if ('_b64' in obj && typeof obj._b64 === 'string') {
      return Buffer.from(obj._b64, 'base64')
    }
    return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, deserializePayload(v)]))
  }
  return val
}
