import type { App, SayFn } from '@slack/bolt'
import { type WebClient } from '@slack/web-api'
import axios from 'axios'
import { HTTPClient } from '@/http'
import { getDefaultAgentId } from '@/app/api/nova-config'
import { getConversationId, setConversationId } from './user-store'
import { sendMessageAndWaitResponse } from '@/remote-control/shared/nova-bridge'
import { uploadBotAttachments, type BotAttachment } from '@/remote-control/shared/file-uploader'
import { BotLogger } from '@/remote-control/shared/logger'

/**
 * Download a file from Slack using axios (same HTTP client as @slack/web-api).
 * Node's undici fetch strips Authorization headers on cross-origin redirects,
 * but axios preserves them properly.
 */
async function downloadSlackFile(url: string, token: string): Promise<Buffer | null> {
  try {
    const resp = await axios.get(url, {
      headers: { Authorization: `Bearer ${token}` },
      responseType: 'arraybuffer',
      maxRedirects: 5,
      validateStatus: (status) => status < 400,
    })
    // Guard: reject HTML responses (Slack login page on auth failure)
    const contentType = resp.headers['content-type'] || ''
    if (contentType.includes('text/html')) {
      console.error('[Slack Bot] 下载返回 HTML，Bot 可能缺少 files:read 权限')
      return null
    }
    return Buffer.from(resp.data)
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err)
    console.error(`[Slack Bot] 文件下载失败: ${msg}`)
    return null
  }
}

const oapiClient = new HTTPClient({
  baseURL: process.env.NOVA_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
    'Tenant-Id': process.env.NOVA_TENANT_ID!,
    'Authorization': process.env.NOVA_ACCESS_KEY!,
  },
})

const userQueues = new Map<string, Promise<void>>()

/**
 * Convert standard Markdown to Slack mrkdwn format.
 * - `[text](url)` -> `<url|text>`
 * - Remove `![alt](url)` image syntax (images are handled via Block Kit)
 */
function convertMarkdownToMrkdwn(text: string): string {
  // Remove image syntax first
  let result = text.replace(/!\[[^\]]*\]\([^)]+\)/g, '')
  // Convert links
  result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<$2|$1>')
  return result.trim()
}

/**
 * Extract image URLs from Markdown `![alt](url)` syntax.
 */
function extractImageUrls(text: string): string[] {
  const regex = /!\[[^\]]*\]\(([^)]+)\)/g
  const urls: string[] = []
  let match: RegExpExecArray | null
  while ((match = regex.exec(text)) !== null) {
    urls.push(match[1])
  }
  return urls
}

async function getOrCreateConversation(slackUserId: string, name: string): Promise<string> {
  const existing = getConversationId(slackUserId)
  if (existing) return existing

  const res = await oapiClient.post<{ conversation_id: string }>(
    '/v1/oapi/super_agent/chat/create_conversation',
    {
      agent_id: getDefaultAgentId(),
      title: `Slack User ${name || slackUserId}`,
    },
  )

  const conversationId = res.conversation_id
  setConversationId(slackUserId, conversationId, name)
  return conversationId
}

async function processMessage(
  message: any,
  say: SayFn,
  client: WebClient,
  botToken: string,
  onMessage?: () => void,
): Promise<void> {
  // Filter out bot messages
  if (message.bot_id || message.subtype === 'bot_message') return

  const userId = message.user as string
  if (!userId) return

  // Extract text, strip mention markup
  const text = ((message.text as string) || '').replace(/<@[A-Z0-9]+>/g, '').trim()
  const hasFiles = message.files && Array.isArray(message.files) && message.files.length > 0

  // Skip empty messages (no text, no files)
  if (!text && !hasFiles) return

  // Fetch user info for display name
  let userName = userId
  try {
    const userInfo = await client.users.info({ user: userId })
    userName = userInfo.user?.real_name || userInfo.user?.name || userId
  } catch {
    // fallback to userId
  }

  // Extract file attachments — download with auth and convert to data URI
  const attachments: BotAttachment[] = []
  let fileDownloadFailed = false
  if (hasFiles) {
    for (const file of message.files as any[]) {
      const downloadUrl = file.url_private_download || file.url_private
      if (!downloadUrl) { console.warn('[Slack Bot] 文件无下载地址:', file.id, file.name); continue }
      try {
        console.log(`[Slack Bot] 开始下载文件: ${file.name}, URL: ${downloadUrl.substring(0, 80)}...`)
        const buffer = await downloadSlackFile(downloadUrl, botToken)
        if (!buffer) {
          console.error(`[Slack Bot] 下载文件失败: ${file.name}，请检查 Bot 是否有 files:read 权限`)
          fileDownloadFailed = true
          continue
        }
        const mimeType = file.mimetype || 'application/octet-stream'
        const dataUri = `data:${mimeType};base64,${buffer.toString('base64')}`
        attachments.push({
          url: dataUri,
          fileName: file.name ?? `slack_file_${Date.now()}`,
          mimeType,
          size: buffer.length,
        })
      } catch (err) {
        console.error(`[Slack Bot] 下载文件异常:`, err)
        fileDownloadFailed = true
      }
    }
  }

  BotLogger.log({
    platform: 'slack',
    eventType: 'message_received',
    severity: 'info',
    message: `收到来自 ${userName} 的消息 (${attachments.length} 附件)`,
    details: { userId, channel: message.channel },
  })

  // If only files were sent and all downloads failed, notify user
  if (!text && attachments.length === 0 && fileDownloadFailed) {
    await say({ text: '文件下载失败，请联系管理员检查 Bot 的 files:read 权限配置。' }).catch(() => {})
    BotLogger.log({
      platform: 'slack',
      eventType: 'error',
      severity: 'error',
      message: `文件下载失败，可能缺少 files:read 权限`,
      details: { userId },
    })
    return
  }

  if (!text && attachments.length === 0) return

  try {
    // Upload attachments if any
    let uploadFileIds: string[] | undefined
    if (attachments.length > 0) {
      uploadFileIds = await uploadBotAttachments(attachments)
      if (uploadFileIds.length === 0) uploadFileIds = undefined
    }

    const conversationId = await getOrCreateConversation(userId, userName)
    const response = await sendMessageAndWaitResponse(
      `slack:${userId}`,
      conversationId,
      text || '',
      uploadFileIds,
    )

    if (!response) {
      await say({ text: '没有收到回复，请稍后重试。' })
      return
    }

    // Check for images in response
    const imageUrls = extractImageUrls(response)

    if (imageUrls.length > 0) {
      const mrkdwnText = convertMarkdownToMrkdwn(response)
      const blocks: any[] = []

      // Add text section if there's text content
      if (mrkdwnText) {
        blocks.push({
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: mrkdwnText,
          },
        })
      }

      // Add image blocks
      for (const url of imageUrls) {
        blocks.push({
          type: 'image',
          image_url: url,
          alt_text: 'image',
        })
      }

      await say({
        text: mrkdwnText || 'image',
        blocks,
      })
    } else {
      await say({ text: convertMarkdownToMrkdwn(response) })
    }

    if (onMessage) onMessage()

    BotLogger.log({
      platform: 'slack',
      eventType: 'message_sent',
      severity: 'info',
      message: `已回复用户 ${userName}`,
    })
  } catch (error) {
    const errMsg = error instanceof Error ? error.message : '未知错误'
    console.error('[Slack Bot] 处理消息失败:', errMsg)
    await say({ text: `处理消息时出错: ${errMsg}` }).catch(() => {})
    BotLogger.log({
      platform: 'slack',
      eventType: 'error',
      severity: 'error',
      message: `处理消息失败: ${errMsg}`,
      details: { userId },
    })
  }
}

function enqueueMessage(
  userId: string,
  message: any,
  say: SayFn,
  client: WebClient,
  botToken: string,
  onMessage?: () => void,
): void {
  const currentQueue = userQueues.get(userId) ?? Promise.resolve()
  const newQueue = currentQueue.then(() => processMessage(message, say, client, botToken, onMessage))
  userQueues.set(userId, newQueue)

  newQueue.finally(() => {
    if (userQueues.get(userId) === newQueue) {
      userQueues.delete(userId)
    }
  })
}

export function registerHandlers(app: App, botToken: string, onMessage?: () => void): void {
  // Handle direct messages and channel messages
  app.message(async ({ message, say, client }) => {
    const userId = (message as any).user as string | undefined
    if (!userId) return
    enqueueMessage(userId, message, say, client, botToken, onMessage)
  })

  // Handle @mentions
  app.event('app_mention', async ({ event, say, client }) => {
    const userId = event.user
    if (!userId) return
    enqueueMessage(userId, event, say, client, botToken, onMessage)
  })
}
