import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { Link } from 'react-router-dom'
import { api } from '../api/client'
import { isTerminalJobStatus, jobStatusBadgeClass, jobStatusLabel } from '../lib/jobStatus'
import { STYLE_8D_PIPELINE_STEPS } from '../lib/styleProfile'
import type { JobData, Pagination } from '../types/api'
import { Button, ProgressBar, StatusBadge } from './ui'

const JOB_MONITOR_EXPANDED_KEY = 'haixun.job_monitor_expanded'
const DOCK_SIZE = 48
const DOCK_ANCHOR = { width: DOCK_SIZE, height: DOCK_SIZE }
const EDGE_PADDING = 12
const MOBILE_DOCK_HEIGHT = 88
const DRAG_THRESHOLD = 5

type MonitorPosition = { x: number; y: number }

type DragSession = {
  active: boolean
  moved: boolean
  pointerId: number
  startX: number
  startY: number
  originX: number
  originY: number
}

const ACTIVE_STATUSES = new Set(['pending', 'queued', 'running', 'waiting_worker', 'cancel_requested'])

const STEP_STATUS_LABEL: Record<string, string> = {
  pending: '等待',
  running: '進行中',
  succeeded: '完成',
  failed: '失敗',
  skipped: '略過',
  cancelled: '取消',
}

const DEFAULT_STEPS = [
  { id: 'prepare', title: '準備', hint: '整理任務資料與環境' },
  { id: 'execute', title: '執行', hint: 'Worker 正在處理任務' },
  { id: 'finalize', title: '收尾', hint: '寫入結果與完成狀態' },
]

function isActiveJob(job: JobData) {
  return ACTIVE_STATUSES.has(job.status)
}

function recentEnough(job: JobData) {
  if (!isTerminalJobStatus(job.status)) return false
  const updatedAtMs = Math.floor((job.update_at || job.create_at || 0) / 1_000_000)
  if (!updatedAtMs) return false
  return Date.now() - updatedAtMs < 3 * 60 * 1000
}

function shortJobTitle(job: JobData) {
  if (job.template_type === 'style-8d') return '8D 風格分析'
  if (job.template_type === 'demo_long_task') return 'Demo 任務'
  return job.template_type
}

function phaseHint(job: JobData) {
  if (job.template_type === 'style-8d') {
    switch (job.phase) {
      case 'session':
        return '確認 Chrome session 與 Worker 心跳'
      case 'samples':
        return '讀取對標帳號近期貼文樣本'
      case 'style':
        return '交給 AI 分析 D1-D8 風格策略'
      case 'store':
        return '寫回人設，讓後續產文套用'
      default:
        return '等待 8D worker 更新階段'
    }
  }
  return job.phase ? `目前階段：${job.phase}` : '等待 Worker 回報階段'
}

function stepDefinitions(job: JobData) {
  if (job.template_type === 'style-8d') return STYLE_8D_PIPELINE_STEPS
  const liveSteps = job.progress?.steps ?? []
  if (liveSteps.length > 0) {
    return liveSteps.map((step) => ({ id: step.id, title: step.id, hint: step.message || step.id }))
  }
  return DEFAULT_STEPS
}

function StepDot({ status }: { status: string }) {
  const classes =
    status === 'succeeded'
      ? 'bg-success'
      : status === 'running'
        ? 'bg-brand shadow-[0_0_0_4px_var(--hx-brand-soft)]'
        : status === 'failed'
          ? 'bg-danger'
          : status === 'cancelled'
            ? 'bg-muted'
            : 'bg-line'
  return <span className={`mt-1 h-2.5 w-2.5 shrink-0 rounded-full ${classes}`} />
}

function JobCard({ job, onCancel }: { job: JobData; onCancel: (id: string) => Promise<void> }) {
  const steps = job.progress?.steps ?? []
  const stepMap = new Map(steps.map((step) => [step.id, step]))
  const definitions = stepDefinitions(job)
  const activeStep = steps.find((step) => step.status === 'running')
  const failedStep = steps.find((step) => step.status === 'failed')
  const canCancel = isActiveJob(job)

  return (
    <div className="ac-island-card p-3">
      <div className="flex items-start justify-between gap-2">
        <div className="min-w-0">
          <div className="flex flex-wrap items-center gap-2">
            <span className="font-bold text-ink">{shortJobTitle(job)}</span>
            <StatusBadge className={jobStatusBadgeClass(job.status)}>
              {jobStatusLabel(job.status)}
            </StatusBadge>
          </div>
          <p className="mt-1 text-xs text-muted">
            Job {job.id.slice(0, 8)} · {job.progress?.percentage ?? 0}%
          </p>
        </div>
        {canCancel ? (
          <Button
            variant="ghost"
            className="min-h-8 px-3 py-1 text-xs"
            onClick={() => onCancel(job.id)}
          >
            停止
          </Button>
        ) : null}
      </div>

      <ProgressBar value={job.progress?.percentage ?? 0} className="mt-3" />

      <div className="ac-island-card__phase mt-3">
        <p className="text-xs font-bold text-ink-secondary">現在在做</p>
        <p className="mt-1 text-sm leading-relaxed text-ink">
          {job.progress?.summary || phaseHint(job)}
        </p>
        {activeStep?.message ? (
          <p className="mt-1 text-xs leading-relaxed text-muted">細節：{activeStep.message}</p>
        ) : null}
        {failedStep?.message || job.error ? (
          <p className="mt-1 text-xs font-semibold leading-relaxed text-danger">
            錯誤：{failedStep?.message || job.error}
          </p>
        ) : null}
      </div>

      <ol className="mt-3 grid gap-2">
        {definitions.map((step) => {
          const live = stepMap.get(step.id)
          const status = live?.status ?? 'pending'
          return (
            <li key={step.id} className="flex gap-2 rounded-[var(--radius-md)] bg-accent-soft/60 px-3 py-2">
              <StepDot status={status} />
              <div className="min-w-0 flex-1">
                <div className="flex flex-wrap items-center gap-1.5">
                  <span className="text-sm font-bold text-ink">{step.title}</span>
                  <span className="text-xs text-muted">{STEP_STATUS_LABEL[status] ?? status}</span>
                </div>
                <p className="text-xs leading-relaxed text-ink-secondary">
                  {live?.message || step.hint}
                </p>
              </div>
            </li>
          )
        })}
      </ol>

      <Link to={`/jobs/${job.id}`} className="ac-link mt-3 inline-block text-xs">
        查看完整事件與原始資料
      </Link>
    </div>
  )
}

function readExpandedPreference() {
  try {
    return localStorage.getItem(JOB_MONITOR_EXPANDED_KEY) === '1'
  } catch {
    return false
  }
}

function defaultMonitorPosition() {
  const mobile = window.innerWidth < 1024
  const bottomGap = mobile ? MOBILE_DOCK_HEIGHT : EDGE_PADDING
  return {
    x: EDGE_PADDING,
    y: Math.max(EDGE_PADDING, window.innerHeight - bottomGap - DOCK_SIZE),
  }
}

function clampMonitorPosition(
  position: MonitorPosition,
  size: { width: number; height: number },
): MonitorPosition {
  const maxX = Math.max(EDGE_PADDING, window.innerWidth - size.width - EDGE_PADDING)
  const maxY = Math.max(EDGE_PADDING, window.innerHeight - size.height - EDGE_PADDING)
  return {
    x: Math.min(Math.max(position.x, EDGE_PADDING), maxX),
    y: Math.min(Math.max(position.y, EDGE_PADDING), maxY),
  }
}

export function JobMonitor() {
  const [jobs, setJobs] = useState<JobData[]>([])
  const [expanded, setExpanded] = useState(readExpandedPreference)
  const [loading, setLoading] = useState(false)
  const [position, setPosition] = useState<MonitorPosition>(defaultMonitorPosition)
  const [dragging, setDragging] = useState(false)
  const rootRef = useRef<HTMLDivElement>(null)
  const dragRef = useRef<DragSession>({
    active: false,
    moved: false,
    pointerId: -1,
    startX: 0,
    startY: 0,
    originX: 0,
    originY: 0,
  })

  const setExpandedPersisted = useCallback((value: boolean | ((prev: boolean) => boolean)) => {
    setExpanded((prev) => {
      const next = typeof value === 'function' ? value(prev) : value
      try {
        localStorage.setItem(JOB_MONITOR_EXPANDED_KEY, next ? '1' : '0')
      } catch {
        // ignore quota / private mode
      }
      return next
    })
  }, [])

  const load = useCallback(async () => {
    setLoading(true)
    try {
      const data = await api.get<{ list: JobData[]; pagination: Pagination }>('/api/v1/jobs', {
        auth: true,
        query: { page: 1, pageSize: 12 },
      })
      setJobs(data.list ?? [])
    } catch {
      // 登入初期或 gateway 重啟時保持安靜，避免干擾主要操作。
    } finally {
      setLoading(false)
    }
  }, [])

  useEffect(() => {
    load().catch(() => undefined)
    const timer = window.setInterval(() => load().catch(() => undefined), 2000)
    return () => window.clearInterval(timer)
  }, [load])

  const syncClampedPosition = useCallback(() => {
    setPosition((prev) => {
      const next = clampMonitorPosition(prev, DOCK_ANCHOR)
      if (next.x === prev.x && next.y === prev.y) return prev
      return next
    })
  }, [])

  useLayoutEffect(() => {
    setPosition(defaultMonitorPosition())
  }, [])

  useEffect(() => {
    const onResize = () => syncClampedPosition()
    window.addEventListener('resize', onResize)
    return () => window.removeEventListener('resize', onResize)
  }, [syncClampedPosition])

  const beginDockDrag = (event: React.PointerEvent<HTMLButtonElement>) => {
    event.preventDefault()
    event.currentTarget.setPointerCapture(event.pointerId)
    dragRef.current = {
      active: true,
      moved: false,
      pointerId: event.pointerId,
      startX: event.clientX,
      startY: event.clientY,
      originX: position.x,
      originY: position.y,
    }
  }

  const moveDockDrag = (event: React.PointerEvent<HTMLButtonElement>) => {
    const drag = dragRef.current
    if (!drag.active || drag.pointerId !== event.pointerId) return

    const dx = event.clientX - drag.startX
    const dy = event.clientY - drag.startY
    if (!drag.moved && (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD)) {
      drag.moved = true
      setDragging(true)
    }
    if (!drag.moved) return

    setPosition(clampMonitorPosition({ x: drag.originX + dx, y: drag.originY + dy }, DOCK_ANCHOR))
  }

  const endDockDrag = (event: React.PointerEvent<HTMLButtonElement>) => {
    const drag = dragRef.current
    if (!drag.active || drag.pointerId !== event.pointerId) return

    drag.active = false
    setDragging(false)

    if (drag.moved) {
      setPosition((prev) => clampMonitorPosition(prev, DOCK_ANCHOR))
      return
    }

    setExpandedPersisted((value) => !value)
  }

  const visibleJobs = useMemo(() => {
    const active = jobs.filter(isActiveJob)
    const recent = jobs.filter((job) => recentEnough(job) && !active.some((item) => item.id === job.id))
    return [...active, ...recent].slice(0, 4)
  }, [jobs])

  const current = visibleJobs[0]
  const activeCount = visibleJobs.filter(isActiveJob).length

  const cancelJob = async (id: string) => {
    await api.post(`/api/v1/jobs/${id}/cancel`, { reason: 'ui cancel' }, { auth: true })
    await load()
  }

  const dockTone =
    activeCount > 0
      ? 'ac-job-monitor__dock--active'
      : current?.status === 'failed'
        ? 'ac-job-monitor__dock--failed'
        : current
          ? 'ac-job-monitor__dock--done'
          : 'ac-job-monitor__dock--idle'

  const openUpward = position.y > window.innerHeight * 0.42

  return (
    <div
      ref={rootRef}
      className={`ac-job-monitor ${dragging ? 'ac-job-monitor--dragging' : ''}`}
      style={{ left: position.x, top: position.y }}
      aria-label="任務觀察站"
    >
      <div className="ac-job-monitor__anchor">
        {activeCount > 0 && !expanded && current ? (
          <div className="ac-job-monitor__peek hidden max-w-[min(18rem,calc(100vw-5rem))] sm:block">
            <p className="truncate text-xs font-semibold text-ink">
              {current.progress?.summary || phaseHint(current)}
            </p>
          </div>
        ) : null}

        <button
          type="button"
          onPointerDown={beginDockDrag}
          onPointerMove={moveDockDrag}
          onPointerUp={endDockDrag}
          onPointerCancel={endDockDrag}
          className={`ac-job-monitor__dock ${dockTone}`}
          aria-expanded={expanded}
          aria-label={expanded ? '收合任務觀察站' : '展開任務觀察站'}
        >
          <span className="text-base font-black">任</span>
          {activeCount > 0 ? (
            <span className="ac-job-monitor__dock-ping" aria-hidden />
          ) : null}
          {visibleJobs.length > 0 ? (
            <span className="ac-job-monitor__dock-badge" aria-hidden>
              {visibleJobs.length}
            </span>
          ) : null}
        </button>

      {expanded ? (
        <div
          className={`ac-job-monitor__panel ac-island-card ac-bulletin--island p-4 backdrop-blur ${
            openUpward ? 'ac-job-monitor__panel--above' : 'ac-job-monitor__panel--below'
          }`}
        >
          <div className="mb-3 flex items-start justify-between gap-3">
            <div>
              <p className="display-en text-[10px] font-bold tracking-[0.16em] text-brand uppercase">
                Task Watch
              </p>
              <h2 className="text-base font-black text-ink">任務觀察站</h2>
              <p className="mt-1 text-xs leading-relaxed text-muted">
                拖曳「任」可移到螢幕任意位置；可自由切頁，worker 會在背景繼續跑。
              </p>
            </div>
            <button
              type="button"
              onClick={() => setExpandedPersisted(false)}
              className="ac-btn-secondary min-h-8 shrink-0 px-3 py-1 text-xs"
            >
              收合
            </button>
          </div>

          {loading && visibleJobs.length === 0 ? (
            <p className="py-4 text-center text-sm text-muted">更新任務狀態中…</p>
          ) : visibleJobs.length === 0 ? (
            <div className="py-4 text-center">
              <p className="text-sm text-muted">目前沒有進行中或剛完成的任務。</p>
              <Link to="/jobs" className="ac-link mt-2 inline-block text-xs">
                前往任務列表
              </Link>
            </div>
          ) : (
            <div className="ac-job-monitor__list space-y-3">
              {visibleJobs.map((job) => (
                <JobCard key={job.id} job={job} onCancel={cancelJob} />
              ))}
            </div>
          )}
        </div>
      ) : null}
      </div>
    </div>
  )
}
