import { useCallback, useEffect, useRef, useState } from 'react'
import Taro, { useRouter, useDidShow, useDidHide } from '@tarojs/taro'
import { Image, Input, ScrollView, Text, View } from '@tarojs/components'
import { useSystemLayout } from '@/hooks/useSystemLayout'
import Icon from '@/components/ui/Icon'
import Avatar from '@/components/ui/Avatar'
import { getThread, sendImageMessage, sendMessage } from '@/services/message.service'
import { resolveCloudFileUrl } from '@/services/cloud-file'
import { ensureProfileReady } from '@/store/session.store'
import type { ChatMessage, ChatPeer } from '@/types/domain'
import './index.scss'

const CHAT_CACHE_PREFIX = 'wangquan_chat_messages:'
const CHAT_CACHE_LIMIT = 300
const PAGE_SIZE = 30
const ACTIVE_POLL_MS = 3000
const IDLE_POLL_MS = 12000
const ACTIVE_POLL_WINDOW_MS = 60 * 1000
const SEND_TIMEOUT_MS = 12000
const BOTTOM_ANCHOR_PREFIX = 'chat-bottom'
const EMOJI_OPTIONS = ['🐶', '🐱', '🥰', '😂', '👍', '❤️']

type SendingKind = 'text' | 'image'

type ThreadParams = {
  conversationId?: string
  targetUserId?: string
  cursor?: string
  pageSize?: number
  markRead?: boolean
}

interface LoadLatestOptions {
  silent?: boolean
  scrollToEnd?: boolean
}

function timeText(ts: number) {
  const d = new Date(ts)
  const hh = String(d.getHours()).padStart(2, '0')
  const mm = String(d.getMinutes()).padStart(2, '0')
  return `${hh}:${mm}`
}

function uniqueMessages(list: ChatMessage[], limit?: number) {
  const map = new Map<string, ChatMessage>()
  list.forEach(item => {
    const key = item._id || `${item.fromMe ? 'me' : 'peer'}:${item.createdAt}:${item.content}`
    map.set(key, item)
  })
  const next = Array.from(map.values()).sort((a, b) => {
    const timeDiff = a.createdAt - b.createdAt
    return timeDiff || a._id.localeCompare(b._id)
  })
  return typeof limit === 'number' ? next.slice(-limit) : next
}

function messageNodeId(id: string) {
  const normalized = id.replace(/[^A-Za-z0-9_-]/g, '-')
  return `msg-${normalized || 'empty'}`
}

function isCanceled(error: unknown) {
  const message = typeof error === 'object' && error
    ? String((error as { errMsg?: string; message?: string }).errMsg || (error as { message?: string }).message || '')
    : String(error || '')
  return message.toLowerCase().includes('cancel')
}

function logChatError(scope: string, error: unknown) {
  // Keep the toast friendly, but leave the real cause visible in devtools.
  console.error(`[chat] ${scope}`, error)
}

function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error(message)), ms)
    promise
      .then(value => resolve(value))
      .catch(error => reject(error))
      .finally(() => clearTimeout(timer))
  })
}

function getCacheKeys(params: ThreadParams) {
  return [
    params.conversationId ? `${CHAT_CACHE_PREFIX}conv:${params.conversationId}` : '',
    params.targetUserId ? `${CHAT_CACHE_PREFIX}user:${params.targetUserId}` : ''
  ].filter(Boolean)
}

function readCachedMessages(keys: string[]) {
  const cached: ChatMessage[] = []
  keys.forEach(key => {
    try {
      const value = Taro.getStorageSync(key)
      if (Array.isArray(value)) cached.push(...(value as ChatMessage[]))
    } catch {
      // Ignore cache read failures; cloud data is still the source of truth.
    }
  })
  return uniqueMessages(cached, CHAT_CACHE_LIMIT)
}

function writeCachedMessages(keys: string[], messages: ChatMessage[]) {
  const next = uniqueMessages(messages, CHAT_CACHE_LIMIT)
  keys.forEach(key => {
    try {
      Taro.setStorageSync(key, next)
    } catch {
      // Cache is a best-effort local history, not a blocking send dependency.
    }
  })
}

export default function ChatPage() {
  const { statusBarHeight, safeBottom } = useSystemLayout()
  const router = useRouter()
  const initialConvId = router.params.conversationId || ''
  const targetUserId = router.params.targetUserId || ''
  const initialTitle = decodeURIComponent(router.params.title || '聊天')

  const [conversationId, setConversationId] = useState(initialConvId)
  const [peer, setPeer] = useState<ChatPeer | null>(null)
  const [messages, setMessages] = useState<ChatMessage[]>([])
  const [draft, setDraft] = useState('')
  const [sending, setSending] = useState<SendingKind | null>(null)
  const [loadingInitial, setLoadingInitial] = useState(true)
  const [loadingOlder, setLoadingOlder] = useState(false)
  const [nextCursor, setNextCursor] = useState('')
  const [showEmoji, setShowEmoji] = useState(false)
  const [bottomAnchorId, setBottomAnchorId] = useState(`${BOTTOM_ANCHOR_PREFIX}-0`)
  const [scrollIntoView, setScrollIntoView] = useState('')
  const conversationIdRef = useRef(initialConvId)
  const latestMessageIdRef = useRef('')
  const latestRequestSeq = useRef(0)
  const lastActivityAtRef = useRef(Date.now())
  const messagesRef = useRef<ChatMessage[]>([])
  const pollRef = useRef<ReturnType<typeof setTimeout> | null>(null)
  const visibleRef = useRef(false)
  const scrollSeq = useRef(0)
  const schedulePollRef = useRef<() => void>(() => {})
  const isSending = Boolean(sending)

  const commitConversationId = useCallback((id: string) => {
    if (!id || id === conversationIdRef.current) return
    conversationIdRef.current = id
    setConversationId(id)
  }, [])

  const commitMessages = useCallback((nextMessages: ChatMessage[], cacheParams?: ThreadParams) => {
    const next = uniqueMessages(nextMessages)
    messagesRef.current = next
    const latestId = next[next.length - 1]?._id || ''
    if (latestId !== latestMessageIdRef.current) {
      if (latestId) lastActivityAtRef.current = Date.now()
      latestMessageIdRef.current = latestId
    }
    setMessages(next)
    if (cacheParams) writeCachedMessages(getCacheKeys(cacheParams), next)
    return next
  }, [])

  const getSendOptions = useCallback((activeConversationId = conversationIdRef.current) => ({
    conversationId: activeConversationId || undefined,
    toUserId: activeConversationId ? undefined : targetUserId || undefined
  }), [targetUserId])

  const getThreadParams = useCallback((override: ThreadParams = {}): ThreadParams => {
    const activeConversationId = override.conversationId || conversationIdRef.current
    return {
      conversationId: activeConversationId || undefined,
      targetUserId: activeConversationId ? undefined : override.targetUserId || targetUserId || undefined,
      cursor: override.cursor,
      pageSize: override.pageSize,
      markRead: override.markRead
    }
  }, [targetUserId])

  const scrollToBottom = useCallback(() => {
    scrollSeq.current += 1
    const nextAnchorId = `${BOTTOM_ANCHOR_PREFIX}-${scrollSeq.current}`
    setBottomAnchorId(nextAnchorId)
    setScrollIntoView(nextAnchorId)
  }, [])

  const loadLatest = useCallback(async ({ silent = false, scrollToEnd = false }: LoadLatestOptions = {}) => {
    const requestId = latestRequestSeq.current + 1
    latestRequestSeq.current = requestId
    if (!silent) setLoadingInitial(true)

    const params = getThreadParams({ pageSize: PAGE_SIZE, markRead: !silent })
    if (messagesRef.current.length === 0) {
      const cachedMessages = readCachedMessages(getCacheKeys(params))
      if (cachedMessages.length) {
        commitMessages(cachedMessages)
        if (scrollToEnd) scrollToBottom()
      }
    }

    try {
      const res = await getThread(params)
      if (requestId !== latestRequestSeq.current) return messagesRef.current

      const nextConversationId = res.conversationId || params.conversationId || ''
      const cacheParams = {
        conversationId: nextConversationId || undefined,
        targetUserId: targetUserId || params.targetUserId
      }
      if (nextConversationId) commitConversationId(nextConversationId)
      if (res.peer) setPeer(res.peer)

      const nextMessages = commitMessages([...messagesRef.current, ...res.messages], cacheParams)
      setNextCursor(current => current || res.nextCursor || '')
      if (scrollToEnd) scrollToBottom()
      return nextMessages
    } catch (error) {
      logChatError('load latest failed', error)
      if (!silent) Taro.showToast({ title: '消息加载失败，请重试', icon: 'none' })
      return messagesRef.current
    } finally {
      if (!silent) setLoadingInitial(false)
    }
  }, [commitConversationId, commitMessages, getThreadParams, scrollToBottom, targetUserId])

  const loadLatestRef = useRef(loadLatest)
  useEffect(() => {
    loadLatestRef.current = loadLatest
  }, [loadLatest])

  const loadOlder = useCallback(async () => {
    if (!nextCursor || loadingInitial || loadingOlder) return

    const anchorMessageId = messagesRef.current[0]?._id
    setLoadingOlder(true)
    try {
      const params = getThreadParams({ cursor: nextCursor, pageSize: PAGE_SIZE, markRead: false })
      const res = await getThread(params)
      const nextConversationId = res.conversationId || params.conversationId || ''
      const cacheParams = {
        conversationId: nextConversationId || undefined,
        targetUserId: targetUserId || params.targetUserId
      }

      if (nextConversationId) commitConversationId(nextConversationId)
      if (res.peer) setPeer(res.peer)
      commitMessages([...res.messages, ...messagesRef.current], cacheParams)
      setNextCursor(res.nextCursor || '')
      if (anchorMessageId) setScrollIntoView(messageNodeId(anchorMessageId))
    } catch (error) {
      logChatError('load older failed', error)
      Taro.showToast({ title: '历史消息加载失败', icon: 'none' })
    } finally {
      setLoadingOlder(false)
    }
  }, [commitConversationId, commitMessages, getThreadParams, loadingInitial, loadingOlder, nextCursor, targetUserId])

  useEffect(() => {
    loadLatest({ scrollToEnd: true })
  }, [loadLatest])

  const clearPoll = useCallback(() => {
    if (!pollRef.current) return
    clearTimeout(pollRef.current)
    pollRef.current = null
  }, [])

  const schedulePoll = useCallback(() => {
    clearPoll()
    if (!visibleRef.current) return

    const recentlyActive = Date.now() - lastActivityAtRef.current < ACTIVE_POLL_WINDOW_MS
    const delay = recentlyActive ? ACTIVE_POLL_MS : IDLE_POLL_MS
    pollRef.current = setTimeout(async () => {
      await loadLatestRef.current({ silent: true })
      schedulePollRef.current()
    }, delay)
  }, [clearPoll])

  useEffect(() => {
    schedulePollRef.current = schedulePoll
  }, [schedulePoll])

  // Poll more often right after chat activity, then back off while the thread is idle.
  useDidShow(() => {
    visibleRef.current = true
    schedulePoll()
  })
  useDidHide(() => {
    visibleRef.current = false
    clearPoll()
  })
  useEffect(() => () => {
    visibleRef.current = false
    clearPoll()
  }, [clearPoll])

  const send = async () => {
    const text = draft.trim()
    if (!text || isSending) return
    if (!conversationIdRef.current && !targetUserId) {
      Taro.showToast({ title: '缺少聊天对象', icon: 'none' })
      logChatError('missing chat target', router.params)
      return
    }
    if (!(await ensureProfileReady())) return

    const sentAt = Date.now()
    setSending('text')
    setShowEmoji(false)
    try {
      const res = await withTimeout(
        sendMessage(text, getSendOptions()),
        SEND_TIMEOUT_MS,
        '发送超时，请检查网络后重试'
      )
      const nextConversationId = res.conversationId || conversationIdRef.current
      if (nextConversationId) commitConversationId(nextConversationId)
      setDraft('')

      const sentMessage: ChatMessage = {
        _id: res.messageId,
        fromMe: true,
        content: text,
        type: 'text',
        createdAt: sentAt,
        timeText: timeText(sentAt)
      }
      commitMessages([...messagesRef.current, sentMessage], {
        conversationId: nextConversationId || undefined,
        targetUserId: targetUserId || undefined
      })
      scrollToBottom()
      loadLatest({ silent: true, scrollToEnd: true }).catch(error => logChatError('refresh after send failed', error))
    } catch (error) {
      logChatError('send text failed', error)
      Taro.showToast({ title: error instanceof Error ? error.message : '发送失败', icon: 'none' })
    } finally {
      setSending(null)
    }
  }

  const sendImages = async () => {
    if (isSending) return
    if (!conversationIdRef.current && !targetUserId) {
      Taro.showToast({ title: '缺少聊天对象', icon: 'none' })
      logChatError('missing chat target', router.params)
      return
    }
    if (!(await ensureProfileReady())) return

    const sentMessages: ChatMessage[] = []
    try {
      const result = await Taro.chooseMedia({
        count: 3,
        mediaType: ['image'],
        sourceType: ['album', 'camera']
      })
      const filePaths = result.tempFiles.map(file => file.tempFilePath).filter(Boolean)
      if (!filePaths.length) return

      setSending('image')
      setShowEmoji(false)
      let activeConversationId = conversationIdRef.current
      for (const filePath of filePaths) {
        const sentAt = Date.now()
        const res = await withTimeout(
          sendImageMessage(filePath, getSendOptions(activeConversationId)),
          SEND_TIMEOUT_MS,
          '图片发送超时，请检查网络后重试'
        )
        activeConversationId = res.conversationId || activeConversationId
        if (activeConversationId) commitConversationId(activeConversationId)
        sentMessages.push({
          _id: res.messageId,
          fromMe: true,
          content: res.content,
          fileId: res.fileId,
          assetState: res.assetState,
          type: 'image',
          createdAt: sentAt,
          timeText: timeText(sentAt)
        })
        commitMessages([...messagesRef.current, sentMessages[sentMessages.length - 1]], {
          conversationId: activeConversationId || undefined,
          targetUserId: targetUserId || undefined
        })
        scrollToBottom()
      }

      loadLatest({ silent: true, scrollToEnd: true }).catch(error => logChatError('refresh after image send failed', error))
    } catch (error) {
      if (!isCanceled(error)) {
        logChatError('send image failed', error)
        Taro.showToast({ title: error instanceof Error ? error.message : '图片发送失败', icon: 'none' })
      }
    } finally {
      setSending(null)
    }
  }

  const appendEmoji = (emoji: string) => {
    if (isSending) return
    setDraft(current => `${current}${emoji}`)
  }

  const retryImage = async (message: ChatMessage) => {
    if (!message.fileId) {
      Taro.showToast({ title: '图片暂不可用', icon: 'none' })
      return
    }

    const resolved = await resolveCloudFileUrl(message.fileId)
    if (!resolved) {
      Taro.showToast({ title: '图片加载失败，请稍后再试', icon: 'none' })
      return
    }

    commitMessages(messagesRef.current.map(item => (
      item._id === message._id
        ? { ...item, content: resolved, assetState: 'ready' }
        : item
    )), getThreadParams())
  }

  const title = peer?.name || initialTitle
  const sendDisabled = !draft.trim() || isSending
  const imageUrls = messages
    .filter(message => message.type === 'image' && message.content)
    .map(message => message.content)
  const previewImage = (current: string) => {
    if (!imageUrls.length) return
    Taro.previewImage({ current, urls: imageUrls })
  }

  return (
    <View className='chat'>
      <View className='chat__nav' style={{ paddingTop: `${statusBarHeight}px` }}>
        <View className='chat__nav-row'>
          <View className='chat__back' onClick={() => Taro.navigateBack()}>
            <Icon name='chev' size={22} className='chat__back-icon' />
          </View>
          <Text className='chat__title'>{title}</Text>
          <View className='chat__nav-spacer' />
        </View>
      </View>

      <ScrollView
        scrollY
        className='chat__scroll'
        scrollIntoView={scrollIntoView}
        scrollWithAnimation
        enhanced
        showScrollbar={false}
        upperThreshold={48}
        onScrollToUpper={loadOlder}
      >
        <View className='chat__list'>
          {loadingOlder ? <Text className='chat__history-state'>正在加载历史消息…</Text> : null}
          {messages.length === 0 ? (
            <Text className='chat__empty'>{loadingInitial ? '加载中…' : '还没有消息，打个招呼吧 👋'}</Text>
          ) : (
            messages.map(m => (
              <View id={messageNodeId(m._id)} key={m._id} className={`chat__row ${m.fromMe ? 'chat__row--me' : ''}`}>
                {!m.fromMe ? (
                  <Avatar avatarKey={peer?.avatarKey} avatarUrl={peer?.avatarUrl} label={title} size='sm' />
                ) : null}
                <View className='chat__bubble-wrap'>
                  {m.type === 'image' && m.content ? (
                    <Image
                      className={`chat__image ${m.fromMe ? 'chat__image--me' : ''}`}
                      src={m.content}
                      mode='aspectFill'
                      onClick={() => previewImage(m.content)}
                    />
                  ) : (
                    <View
                      className={`chat__bubble ${m.fromMe ? 'chat__bubble--me' : ''}`}
                      onClick={m.type === 'image' ? () => retryImage(m) : undefined}
                    >
                      {m.type === 'image' ? '图片暂不可用' : m.content}
                    </View>
                  )}
                  <Text className='chat__time'>{m.timeText}</Text>
                </View>
              </View>
            ))
          )}
          <View id={bottomAnchorId} className='chat__bottom-anchor' />
        </View>
      </ScrollView>

      {showEmoji ? (
        <View className='chat__emoji-panel'>
          {EMOJI_OPTIONS.map(emoji => (
            <Text key={emoji} className='chat__emoji' onClick={() => appendEmoji(emoji)}>
              {emoji}
            </Text>
          ))}
        </View>
      ) : null}
      <View className='chat__input-bar' style={{ paddingBottom: `${Math.max(12, safeBottom)}px` }}>
        <View className={`chat__tool ${isSending ? 'chat__tool--disabled' : ''}`} onClick={sendImages}>
          <Icon name='image' size={18} />
        </View>
        <View
          className={`chat__tool ${showEmoji ? 'chat__tool--active' : ''} ${isSending ? 'chat__tool--disabled' : ''}`}
          onClick={() => !isSending && setShowEmoji(current => !current)}
        >
          <Icon name='smile' size={18} />
        </View>
        <Input
          className='chat__input'
          value={draft}
          placeholder='发消息…'
          placeholderClass='chat__input-ph'
          confirmType='send'
          disabled={isSending}
          onInput={e => setDraft(e.detail.value)}
          onConfirm={send}
        />
        <View className={`chat__send ${sendDisabled ? 'chat__send--disabled' : ''} ${isSending ? 'chat__send--sending' : ''}`} onClick={send}>
          {isSending ? '发送中' : '发送'}
        </View>
      </View>
    </View>
  )
}
