import {
  ContainerOutlined,
  LogoutOutlined,
  SafetyCertificateOutlined,
  ShoppingOutlined,
  UserOutlined,
} from '@ant-design/icons'
import { App, Button, Layout, Menu, Typography } from 'antd'
import { useQueryClient } from '@tanstack/react-query'
import type { MenuProps } from 'antd'
import { useEffect } from 'react'
import { Outlet, useLocation, useNavigate } from 'react-router'

import { logoutWorker } from '@/services/worker'
import { useIsMobile } from '@/utils/use-is-mobile'
import {
  clearWorkerSession,
  expireWorkerSession,
  getWorkerSessionExpiryDelay,
  getWorkerStatus,
  getWorkerToken,
  getWorkerTokenExpiresAt,
  getWorkerUsername,
  isWorkerSessionStorageKey,
} from '@/utils/worker-auth'
import { useRealtimeEvents, type RealtimeEvent } from '@/hooks/useRealtimeEvents'
import WorkerAnnouncementBanner from '@/components/worker/WorkerAnnouncementBanner'
import WorkerNewOrderSpeech from '@/components/worker/WorkerNewOrderSpeech'

const menuItems: MenuProps['items'] = [
  { key: '/hall', icon: <ShoppingOutlined />, label: '抢单大厅' },
  { key: '/orders', icon: <ContainerOutlined />, label: '我的订单' },
  { key: '/orders/after-sales', icon: <SafetyCertificateOutlined />, label: '售后问题单' },
  { key: '/profile', icon: <UserOutlined />, label: '个人中心' },
]

const tabItems = [
  { key: '/hall', icon: ShoppingOutlined, label: '抢单大厅' },
  { key: '/orders', icon: ContainerOutlined, label: '我的订单' },
  { key: '/profile', icon: UserOutlined, label: '个人中心' },
]

export default function WorkerLayout() {
  const navigate = useNavigate()
  const location = useLocation()
  const isMobile = useIsMobile()
  const { message, modal } = App.useApp()
  const queryClient = useQueryClient()
  const username = getWorkerUsername() || '打手'
  const status = getWorkerStatus()
  const currentKey = resolveSelectedKey(location.pathname)

  useEffect(() => {
    const navigateToLogin = () => {
      if (window.location.pathname !== '/') {
        void navigate('/', { replace: true })
      }
    }
    const handleSessionExpired = () => navigateToLogin()
    const handleStorage = (event: StorageEvent) => {
      if (isWorkerSessionStorageKey(event.key) && event.newValue === null) {
        expireWorkerSession()
        navigateToLogin()
      }
    }

    window.addEventListener('worker-session-expired', handleSessionExpired)
    window.addEventListener('storage', handleStorage)
    return () => {
      window.removeEventListener('worker-session-expired', handleSessionExpired)
      window.removeEventListener('storage', handleStorage)
    }
  }, [navigate])

  useEffect(() => {
    const delay = getWorkerSessionExpiryDelay(getWorkerTokenExpiresAt())
    if (delay === null) return

    const timer = window.setTimeout(() => {
      expireWorkerSession()
    }, delay)
    return () => window.clearTimeout(timer)
  }, [])

  useRealtimeEvents({
    url: '/api/v1/worker/realtime',
    token: getWorkerToken(),
    onEvent: (event) => applyWorkerRealtimeEvent(event, queryClient),
    // 会话过期时清登录态回登录页，避免 SSE 带着失效 token 无限重连刷 401。
    onAuthExpired: () => {
      expireWorkerSession()
    },
  })

  async function submitLogout() {
    try {
      await logoutWorker()
    } catch {
      // 接单端退出为无状态操作，本地清理优先。
    } finally {
      clearWorkerSession()
      message.success('已退出登录')
      void navigate('/', { replace: true })
    }
  }

  function confirmLogout() {
    modal.confirm({
      title: '确认退出登录',
      content: '退出后需要重新登录才能继续抢单和处理订单。',
      okText: '确认退出',
      cancelText: '取消',
      okButtonProps: { danger: true },
      onOk: submitLogout,
    })
  }

  if (isMobile) {
    return (
      <div className="worker-mobile-shell">
        <header className="worker-mobile-header">
          <div className="worker-mobile-brand">
            <span className="worker-mobile-brand-badge">锤</span>
            <div className="worker-mobile-brand-text">
              <strong>王大锤接单</strong>
            </div>
          </div>
          <div className="worker-mobile-user-area">
            <WorkerNewOrderSpeech />
            <Button
              type="text"
              size="small"
              icon={<LogoutOutlined />}
              className="worker-mobile-logout-btn"
              onClick={confirmLogout}
              title="退出登录"
            />
          </div>
        </header>

        <main className="worker-mobile-content">
          <WorkerAnnouncementBanner />
          <Outlet />
        </main>

        <nav className="worker-mobile-tabbar" aria-label="移动端底部导航">
          {tabItems.map((item) => {
            const Icon = item.icon
            const isActive =
              currentKey === item.key ||
              (currentKey === '/orders/after-sales' && item.key === '/orders')
            return (
              <button
                key={item.key}
                type="button"
                className={`worker-mobile-tab-item ${isActive ? 'active' : ''}`}
                onClick={() => navigate(item.key)}
              >
                <div className="worker-mobile-tab-icon">
                  <Icon />
                </div>
                <span className="worker-mobile-tab-label">{item.label}</span>
              </button>
            )
          })}
        </nav>
      </div>
    )
  }

  return (
    <Layout className="admin-shell worker-shell">
      <Layout.Sider width={220} className="admin-sider" trigger={null}>
        <div className="admin-sider-inner">
          <div className="admin-brand">
            <div className="admin-brand-copy">
              <strong>王大锤</strong>
              <span>接单平台</span>
            </div>
          </div>
          <div className="admin-sidebar-profile">
            <Typography.Text strong>{username}</Typography.Text>
            <Typography.Text type="secondary">{formatWorkerStatus(status)}</Typography.Text>
          </div>
          <Menu
            mode="inline"
            inlineIndent={16}
            selectedKeys={[currentKey]}
            items={menuItems}
            className="admin-menu"
            onClick={({ key }) => navigate(String(key))}
          />
          <div className="admin-sider-footer">
            <WorkerNewOrderSpeech />
            <Button block icon={<LogoutOutlined />} onClick={submitLogout}>
              退出登录
            </Button>
          </div>
        </div>
      </Layout.Sider>
      <Layout className="admin-main-shell">
        <Layout.Content className="admin-content">
          <WorkerAnnouncementBanner />
          <Outlet />
        </Layout.Content>
      </Layout>
    </Layout>
  )
}

function applyWorkerRealtimeEvent(
  event: RealtimeEvent,
  queryClient: ReturnType<typeof useQueryClient>,
) {
  if (event.type === 'work_order.changed' && isRecord(event.data)) {
    const data = event.data
    if (event.scopes.includes('worker_my_orders')) {
      patchWorkerMyOrderCaches(queryClient, { ...event, data })
      void queryClient.invalidateQueries({ queryKey: ['worker-after-sales-cases'] })
    }
    if (event.scopes.includes('worker_hall')) {
      patchWorkerHallCaches(queryClient, { ...event, data })
    }
  }

  if (event.type === 'worker_wallet.changed' && isRecord(event.data)) {
    const walletData = event.data
    queryClient.setQueryData(['worker-profile'], (current: unknown) => {
      if (!isRecord(current) || !isRecord(current.data) || !isRecord(current.data.worker))
        return current
      return {
        ...current,
        data: {
          ...current.data,
          worker: { ...current.data.worker, wallet: walletData.wallet || walletData },
        },
      }
    })
  }

  if (event.type === 'worker_finance_request.changed' && isRecord(event.data)) {
    const data = event.data
    queryClient.setQueriesData({ queryKey: ['worker-finance-requests'] }, (current: unknown) =>
      patchFinanceRequestListEnvelope(current, { ...event, data }),
    )
  }
}

function patchWorkerMyOrderCaches(
  queryClient: ReturnType<typeof useQueryClient>,
  event: RealtimeEvent,
) {
  for (const [queryKey, current] of queryClient.getQueriesData({
    queryKey: ['worker-my-orders'],
  })) {
    const status = String(queryKey[1] || '')
    const keyword = String(queryKey[2] || '')
    const page = Number(queryKey[3] || 1)
    const item = event.data as {
      status?: unknown
      productName?: unknown
      platformOrderId?: unknown
    }
    const matchesStatus = !status || status === String(item.status || '')
    const matchesKeyword = matchesWorkOrderKeyword(item, keyword)
    const matches = matchesStatus && matchesKeyword
    const shouldInsert = event.operation === 'upsert' && page === 1 && matches
    const operation = event.operation === 'remove' || !matches ? 'remove' : 'upsert'
    queryClient.setQueryData(
      queryKey,
      patchWorkOrderListEnvelope(current, { ...event, operation }, shouldInsert),
    )
  }
}

/** 新订单广播最多推迟入缓存的时长；超过则不占定时器，交给列表刷新自然带出。 */
const HALL_BROADCAST_MAX_SCHEDULE_MS = 60_000

/** 等待可见延迟到期的挂起插入；期间收到下架事件需取消，避免把已下架订单插回缓存。 */
const pendingHallUpsertTimers = new Map<number, ReturnType<typeof setTimeout>>()

function patchWorkerHallCaches(
  queryClient: ReturnType<typeof useQueryClient>,
  event: RealtimeEvent,
) {
  if (event.operation === 'refresh') {
    // 仅大厅全局配置变化走全量刷新；不清缓存，旧列表保持展示避免误点。
    void queryClient.invalidateQueries({ queryKey: ['worker-hall-orders'] })
    return
  }
  const workOrderId = Number(event.entityId || 0)
  const pendingTimer = pendingHallUpsertTimers.get(workOrderId)
  if (pendingTimer) {
    clearTimeout(pendingTimer)
    pendingHallUpsertTimers.delete(workOrderId)
  }
  if (event.operation === 'remove') {
    applyWorkerHallCachePatch(queryClient, event)
    return
  }
  // 服务端广播无法按打手等级过滤可见延迟；未到解锁时间的订单推迟入缓存，与列表接口和抢单校验对齐。
  const delayMs = resolveHallVisibilityDelayMs(queryClient, event.data, event.occurredAt)
  if (delayMs > 0) {
    if (delayMs <= HALL_BROADCAST_MAX_SCHEDULE_MS) {
      const timer = setTimeout(() => {
        pendingHallUpsertTimers.delete(workOrderId)
        applyWorkerHallCachePatch(queryClient, event)
      }, delayMs)
      pendingHallUpsertTimers.set(workOrderId, timer)
    }
    return
  }
  applyWorkerHallCachePatch(queryClient, event)
}

function applyWorkerHallCachePatch(
  queryClient: ReturnType<typeof useQueryClient>,
  event: RealtimeEvent,
) {
  const workerSnapshot = applyWorkerDepositRule(queryClient, event.data)
  const item = workerSnapshot as {
    status?: unknown
    categoryId?: unknown
    productName?: unknown
    platformOrderId?: unknown
  }
  for (const [queryKey, current] of queryClient.getQueriesData({
    queryKey: ['worker-hall-orders'],
  })) {
    const keyword = String(queryKey[1] || '')
    const categoryId = Number(queryKey[2] || 0)
    const matchesCategory = !categoryId || categoryId === Number(item.categoryId || 0)
    const matches =
      String(item.status || '') === 'open' &&
      matchesCategory &&
      matchesWorkOrderKeyword(item, keyword)
    const shouldInsert = event.operation === 'upsert' && matches
    const operation = event.operation === 'remove' || !matches ? 'remove' : 'upsert'
    queryClient.setQueryData(
      queryKey,
      patchInfiniteWorkOrderEnvelope(
        current,
        { ...event, data: workerSnapshot, operation },
        shouldInsert,
      ),
    )
  }
}

/** 订单入厅时间 + 当前打手等级的可见延迟尚未到期时，返回需要推迟的毫秒数。 */
function resolveHallVisibilityDelayMs(
  queryClient: ReturnType<typeof useQueryClient>,
  data: unknown,
  occurredAt: string | undefined,
): number {
  if (!isRecord(data)) return 0
  const queuedAtMs = Date.parse(String(data.hallQueuedAt || ''))
  if (!Number.isFinite(queuedAtMs)) return 0
  const visibleDelaySeconds = readWorkerHallVisibleDelaySeconds(queryClient)
  if (visibleDelaySeconds <= 0) return 0
  const unlockAtMs = queuedAtMs + visibleDelaySeconds * 1000
  // 入厅时间是服务器时钟，用事件的服务器时间戳校正浏览器钟差：快钟提前展示会重现“看得见抢不了”。
  const occurredAtMs = occurredAt ? Date.parse(occurredAt) : Number.NaN
  const serverNowMs = Number.isFinite(occurredAtMs) ? occurredAtMs : Date.now()
  const clockSkewMs = Date.now() - serverNowMs
  return unlockAtMs - (Date.now() - clockSkewMs)
}

/** 大厅列表响应自带当前打手的 visibleDelaySeconds，从已缓存分页读取，无需额外接口。 */
function readWorkerHallVisibleDelaySeconds(queryClient: ReturnType<typeof useQueryClient>): number {
  for (const [, cached] of queryClient.getQueriesData({ queryKey: ['worker-hall-orders'] })) {
    const pages = (cached as { pages?: unknown } | undefined)?.pages
    if (!Array.isArray(pages)) continue
    for (const page of pages) {
      const delaySeconds = Number(
        (page as { data?: { visibleDelaySeconds?: unknown } } | null)?.data?.visibleDelaySeconds ||
          0,
      )
      if (delaySeconds > 0) return delaySeconds
    }
  }
  return 0
}

function applyWorkerDepositRule(queryClient: ReturnType<typeof useQueryClient>, data: unknown) {
  if (!isRecord(data)) return data
  const profile = queryClient.getQueryData(['worker-profile'])
  const depositFreeAmount =
    isRecord(profile) && isRecord(profile.data) && isRecord(profile.data.worker)
      ? Number(
          (profile.data.worker.level as { permissions?: { depositFreeAmount?: unknown } } | null)
            ?.permissions?.depositFreeAmount || 0,
        )
      : 0
  const requiredDepositAmount = Number(data.requiredDepositAmount || 0)
  return {
    ...data,
    depositFreeAmount,
    freezeDepositAmount: Math.max(0, requiredDepositAmount - depositFreeAmount),
  }
}

function matchesWorkOrderKeyword(
  item: { productName?: unknown; platformOrderId?: unknown; material?: unknown },
  keyword: string,
) {
  const normalizedKeyword = keyword.trim().toLowerCase()
  if (!normalizedKeyword) return true
  const material = isRecord(item.material) ? item.material : {}
  const collect = isRecord(material.collect) ? material.collect : {}
  const fields = isRecord(collect.fields) ? collect.fields : {}
  return [
    item.productName,
    item.platformOrderId,
    fields.gameId,
    fields.gameNickname,
    fields.gameAccount,
    fields.roleName,
    material.gameId,
    material.gameNickname,
  ]
    .map((value) => String(value || '').toLowerCase())
    .some((value) => value.includes(normalizedKeyword))
}

function patchWorkOrderListEnvelope(current: unknown, event: RealtimeEvent, insert = false) {
  if (!isRecord(current) || !isRecord(current.data) || !Array.isArray(current.data.items)) {
    return current
  }
  const id = Number((event.data as { workOrderId?: unknown }).workOrderId || event.entityId)
  const items = current.data.items as unknown[]
  const nextItems =
    event.operation === 'remove'
      ? items.filter((entry) => Number((entry as { workOrderId?: unknown })?.workOrderId) !== id)
      : upsertById(items, event.data, 'workOrderId', insert)
  return { ...current, data: { ...current.data, items: nextItems } }
}

function patchInfiniteWorkOrderEnvelope(current: unknown, event: RealtimeEvent, insert = false) {
  if (!isRecord(current) || !Array.isArray(current.pages)) return current
  return {
    ...current,
    pages: current.pages.map((page: unknown, index) =>
      patchWorkOrderListEnvelope(page, event, insert && index === 0),
    ),
  }
}

function patchFinanceRequestListEnvelope(current: unknown, event: RealtimeEvent) {
  if (!isRecord(current) || !isRecord(current.data) || !Array.isArray(current.data.items)) {
    return current
  }
  const id = Number((event.data as { requestId?: unknown }).requestId || event.entityId)
  const items = current.data.items as unknown[]
  const nextItems =
    event.operation === 'remove'
      ? items.filter((entry) => Number((entry as { requestId?: unknown })?.requestId) !== id)
      : upsertById(items, event.data, 'requestId')
  return { ...current, data: { ...current.data, items: nextItems } }
}

function upsertById(items: unknown[], nextItem: unknown, idKey: string, insert = false) {
  const id = Number((nextItem as Record<string, unknown>)[idKey] || 0)
  const index = items.findIndex(
    (item) => Number((item as Record<string, unknown>)?.[idKey] || 0) === id,
  )
  if (index < 0) return insert ? [nextItem, ...items] : items
  return items.map((item, itemIndex) => {
    if (itemIndex !== index) return item
    if (!isRecord(item) || !isRecord(nextItem)) return nextItem
    // 大厅广播是公共快照，合并后保留当前打手私有的 myShare 等字段。
    return { ...item, ...nextItem }
  })
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value && typeof value === 'object' && !Array.isArray(value))
}

function resolveSelectedKey(pathname: string) {
  if (pathname.startsWith('/orders/after-sales')) return '/orders/after-sales'
  if (pathname.startsWith('/orders')) return '/orders'
  if (pathname.startsWith('/profile')) return '/profile'
  return '/hall'
}

function formatWorkerStatus(status: string) {
  if (status === 'active') return '已启用'
  if (status === 'pending_review') return '待处理'
  if (status === 'rejected') return '已冻结'
  if (status === 'disabled') return '已停用'
  return '未登录'
}
