import { useEffect, useRef, useState } from 'react'
import {
  clearNotice,
  fileDownloadUrl,
  getNotice,
  getPosts,
  sendMessage,
  type ChannelKindKey,
  type FileRef,
  type Org,
  type PostView,
} from '../api/client'

const POLL_INTERVAL_MS = 3000

const timeFormatter = new Intl.DateTimeFormat('ko-KR', { hour: '2-digit', minute: '2-digit' })

const dateTimeFormatter = new Intl.DateTimeFormat('ko-KR', {
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
  hour: '2-digit',
  minute: '2-digit',
})

function humanizeSize(bytes: number): string {
  if (bytes < 1024) {
    return `${bytes} B`
  }
  const kb = bytes / 1024
  if (kb < 1024) {
    return `${kb.toFixed(1)} KB`
  }
  return `${(kb / 1024).toFixed(1)} MB`
}

function AttachmentChip({ file }: { file: FileRef }) {
  return (
    <a
      href={fileDownloadUrl(file.id)}
      className="inline-flex items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-1 text-xs text-gray-600 hover:bg-gray-100"
    >
      <span className="font-medium text-gray-700">{file.name}</span>
      <span className="text-gray-400">({humanizeSize(file.size)})</span>
    </a>
  )
}

// 이름에서 결정되는 아바타 색 — 같은 사람은 항상 같은 색이 나온다 (Mattermost 이니셜 아바타 방식)
const AVATAR_COLORS = [
  'bg-rose-500',
  'bg-orange-500',
  'bg-amber-500',
  'bg-emerald-500',
  'bg-teal-500',
  'bg-sky-500',
  'bg-indigo-500',
  'bg-violet-500',
  'bg-fuchsia-500',
]

function avatarColor(name: string): string {
  let hash = 0
  for (const ch of name) {
    hash = (hash + ch.codePointAt(0)!) % AVATAR_COLORS.length
  }
  return AVATAR_COLORS[hash]
}

function Avatar({ name }: { name: string }) {
  return (
    <span
      aria-hidden="true"
      className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-sm font-semibold text-white ${avatarColor(name)}`}
    >
      {[...name][0] ?? '?'}
    </span>
  )
}

/** 같은 사람이 5분 안에 연달아 쓴 메시지는 아바타·이름 없이 이어 붙인다 (Mattermost와 동일) */
const GROUP_WINDOW_MS = 5 * 60 * 1000

function PostRow({ post, prev }: { post: PostView; prev: PostView | null }) {
  if (post.system) {
    return <p className="py-2 text-center text-xs text-gray-400">{post.message}</p>
  }

  const continued =
    prev !== null &&
    !prev.system &&
    prev.user === post.user &&
    post.createAt - prev.createAt < GROUP_WINDOW_MS

  return (
    <div className={`flex gap-3 ${continued ? 'mt-0.5' : 'mt-4 first:mt-0'}`}>
      {continued ? <span className="w-9 shrink-0" /> : <Avatar name={post.user} />}

      <div className="min-w-0 flex-1">
        {!continued && (
          <div className="flex items-baseline gap-2">
            <span className="text-sm font-semibold text-gray-900">{post.user}</span>
            <span className="text-xs text-gray-400">
              {timeFormatter.format(new Date(post.createAt))}
            </span>
          </div>
        )}
        {post.message && (
          <p className="whitespace-pre-wrap text-sm leading-relaxed text-gray-800">
            {post.message}
          </p>
        )}
        {post.files.length > 0 && (
          <div className="mt-1.5 flex flex-wrap gap-1.5">
            {post.files.map((f) => (
              <AttachmentChip key={f.id} file={f} />
            ))}
          </div>
        )}
      </div>
    </div>
  )
}

/** 공지 배너: 현재 채널의 단일 활성 공지를 목록 위에 항상 고정해 보여준다. */
function NoticeBanner({
  notice,
  busy,
  onClear,
}: {
  notice: PostView
  busy: boolean
  onClear: () => void
}) {
  return (
    <div className="mt-3 rounded-lg border border-amber-200 bg-amber-50 p-3">
      <div className="flex items-baseline gap-2">
        <span className="text-sm font-bold text-amber-700">📢 공지</span>
        <span className="text-xs text-gray-500">
          {notice.user} · {dateTimeFormatter.format(new Date(notice.createAt))}
        </span>
        <span className="flex-1" />
        <button
          type="button"
          disabled={busy}
          onClick={onClear}
          className="shrink-0 rounded-md border border-amber-300 px-2 py-0.5 text-xs text-amber-700 hover:bg-amber-100 disabled:opacity-40"
        >
          공지 해제
        </button>
      </div>
      {notice.message && (
        <p className="mt-1.5 whitespace-pre-wrap text-sm text-gray-800">{notice.message}</p>
      )}
      {notice.files.length > 0 && (
        <div className="mt-1.5 flex flex-wrap gap-1.5">
          {notice.files.map((f) => (
            <AttachmentChip key={f.id} file={f} />
          ))}
        </div>
      )}
    </div>
  )
}

/** 기관관리 상세의 채팅 탭. 선택한 채널의 최근 게시글을 3초 간격으로 폴링해서 보여준다. */
export default function ChannelPosts({ org }: { org: Org }) {
  const bothMissing = !org.channelIdMj && !org.channelIdLaw
  const [channel, setChannel] = useState<ChannelKindKey>(org.channelIdMj ? 'mj' : 'law')
  const [posts, setPosts] = useState<PostView[]>([])
  const [notice, setNotice] = useState<PostView | null>(null)
  const [error, setError] = useState<string | null>(null)
  // 전송 성공 직후 3초 폴링을 기다리지 않고 즉시 다시 읽기 위한 트리거
  const [refreshTick, setRefreshTick] = useState(0)

  const [draft, setDraft] = useState('')
  const [attachments, setAttachments] = useState<File[]>([])
  const [asNotice, setAsNotice] = useState(false)
  const [sending, setSending] = useState(false)
  const [sendError, setSendError] = useState<string | null>(null)
  const [noticeWarning, setNoticeWarning] = useState<string | null>(null)
  const [dragging, setDragging] = useState(false)

  const containerRef = useRef<HTMLDivElement>(null)
  const fileInputRef = useRef<HTMLInputElement>(null)
  const prevCountRef = useRef(0)
  // dragenter/dragleave는 자식 요소를 지날 때마다 쌍으로 발생하므로,
  // 카운터로 세지 않으면 안내 표시가 깜빡인다
  const dragDepthRef = useRef(0)

  useEffect(() => {
    if (bothMissing) {
      return
    }

    let cancelled = false

    async function load() {
      try {
        const data = await getPosts(org.id, channel)
        if (!cancelled) {
          setPosts(data)
          setError(null)
        }
      } catch {
        if (!cancelled) {
          setError('연결이 원활하지 않습니다. 재시도 중…')
        }
      }
      // 공지 조회 실패는 목록을 깨뜨리지 않는다 - 마지막 값을 유지한다
      try {
        const current = await getNotice(org.id, channel)
        if (!cancelled) {
          setNotice(current)
        }
      } catch {
        /* keep last notice */
      }
    }

    void load()
    const timer = setInterval(() => void load(), POLL_INTERVAL_MS)
    return () => {
      cancelled = true
      clearInterval(timer)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [channel, org.id, refreshTick])

  const canSend = !sending && (draft.trim().length > 0 || attachments.length > 0)

  async function submit() {
    if (!canSend) {
      return
    }
    setSending(true)
    setSendError(null)
    setNoticeWarning(null)
    try {
      const result = await sendMessage(org.id, channel, {
        message: draft,
        notice: asNotice,
        files: attachments,
      })
      setDraft('')
      setAttachments([])
      setAsNotice(false)
      setNoticeWarning(result.noticeWarning)
      setRefreshTick((t) => t + 1)
    } catch (e) {
      setSendError((e as Error).message)
    } finally {
      setSending(false)
    }
  }

  async function handleClearNotice() {
    if (!window.confirm('공지를 해제할까요? (원본 글은 채팅 기록에 남습니다)')) {
      return
    }
    setSending(true)
    try {
      await clearNotice(org.id, channel)
      setNotice(null)
      setRefreshTick((t) => t + 1)
    } catch (e) {
      setSendError((e as Error).message)
    } finally {
      setSending(false)
    }
  }

  useEffect(() => {
    if (posts.length > prevCountRef.current && containerRef.current) {
      containerRef.current.scrollTop = containerRef.current.scrollHeight
    }
    prevCountRef.current = posts.length
  }, [posts.length])

  return (
    <div className="p-4">
      <div className="flex gap-2">
        <button
          type="button"
          disabled={!org.channelIdMj}
          title={org.channelIdMj ? undefined : '채널 미생성'}
          onClick={() => setChannel('mj')}
          className={`rounded-full border px-3 py-1 text-xs ${
            channel === 'mj'
              ? 'border-blue-600 bg-blue-50 font-semibold text-blue-700'
              : 'border-gray-200 text-gray-500'
          } disabled:cursor-not-allowed disabled:opacity-40`}
        >
          문정원
        </button>
        <button
          type="button"
          disabled={!org.channelIdLaw}
          title={org.channelIdLaw ? undefined : '채널 미생성'}
          onClick={() => setChannel('law')}
          className={`rounded-full border px-3 py-1 text-xs ${
            channel === 'law'
              ? 'border-blue-600 bg-blue-50 font-semibold text-blue-700'
              : 'border-gray-200 text-gray-500'
          } disabled:cursor-not-allowed disabled:opacity-40`}
        >
          법률검토
        </button>
      </div>

      {bothMissing ? (
        <p className="mt-6 text-center text-sm text-gray-400">채널이 아직 생성되지 않았습니다.</p>
      ) : (
        <>
          {error && (
            <p className="mt-3 rounded-md bg-amber-50 px-3 py-1.5 text-xs text-amber-700">{error}</p>
          )}

          {notice && (
            <NoticeBanner notice={notice} busy={sending} onClear={() => void handleClearNotice()} />
          )}

          <div
            ref={containerRef}
            className="mt-3 h-96 overflow-y-auto rounded-lg border border-gray-200 bg-white p-4"
          >
            {posts.length === 0 ? (
              <p className="py-6 text-center text-sm text-gray-400">
                아직 게시글이 없습니다. 아래 입력창이나 Mattermost에서 메시지를 보내면 여기 나타납니다.
              </p>
            ) : (
              posts.map((post, i) => (
                <PostRow key={post.id} post={post} prev={i > 0 ? posts[i - 1] : null} />
              ))
            )}
          </div>

          {/* 입력창 — 파일을 끌어다 놓으면 첨부된다 */}
          <div
            data-testid="composer-dropzone"
            onDragEnter={(e) => {
              e.preventDefault()
              dragDepthRef.current += 1
              setDragging(true)
            }}
            onDragOver={(e) => e.preventDefault()}
            onDragLeave={() => {
              dragDepthRef.current = Math.max(0, dragDepthRef.current - 1)
              if (dragDepthRef.current === 0) {
                setDragging(false)
              }
            }}
            onDrop={(e) => {
              e.preventDefault()
              dragDepthRef.current = 0
              setDragging(false)
              const dropped = Array.from(e.dataTransfer?.files ?? [])
              if (dropped.length > 0) {
                setAttachments((prev) => [...prev, ...dropped])
              }
            }}
            className={`mt-3 rounded-lg border bg-white p-3 transition-colors ${
              dragging
                ? 'border-2 border-dashed border-blue-400 bg-blue-50/50'
                : asNotice
                  ? 'border-amber-300'
                  : 'border-gray-200'
            }`}
          >
            {dragging && (
              <p className="mb-2 text-center text-xs font-medium text-blue-600">
                파일을 여기에 놓으면 첨부됩니다
              </p>
            )}
            {attachments.length > 0 && (
              <div className="mb-2 flex flex-wrap gap-1.5">
                {attachments.map((file, i) => (
                  <span
                    key={`${file.name}-${i}`}
                    className="inline-flex items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-1 text-xs text-gray-700"
                  >
                    {file.name}
                    <button
                      type="button"
                      aria-label={`${file.name} 제거`}
                      onClick={() => setAttachments((prev) => prev.filter((_, j) => j !== i))}
                      className="text-gray-400 hover:text-gray-600"
                    >
                      ×
                    </button>
                  </span>
                ))}
              </div>
            )}

            <textarea
              aria-label="메시지 입력"
              placeholder="메시지를 입력하세요 (Enter 전송, Shift+Enter 줄바꿈)"
              rows={2}
              value={draft}
              onChange={(e) => setDraft(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === 'Enter' && !e.shiftKey) {
                  e.preventDefault()
                  void submit()
                }
              }}
              className="w-full resize-none rounded-md border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-blue-400"
            />

            <div className="mt-2 flex items-center gap-3">
              <input
                ref={fileInputRef}
                type="file"
                multiple
                aria-label="첨부파일 선택"
                className="hidden"
                onChange={(e) => {
                  const picked = Array.from(e.target.files ?? [])
                  if (picked.length > 0) {
                    setAttachments((prev) => [...prev, ...picked])
                  }
                  e.target.value = ''
                }}
              />
              <button
                type="button"
                onClick={() => fileInputRef.current?.click()}
                className="inline-flex items-center gap-1 rounded-md border border-gray-200 px-2.5 py-1.5 text-xs text-gray-600 hover:bg-gray-50"
              >
                <svg
                  className="h-3.5 w-3.5"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  strokeWidth="2"
                  strokeLinecap="round"
                >
                  <path d="M21 12l-8.5 8.5a5 5 0 0 1-7-7L14 5a3.5 3.5 0 0 1 5 5l-8.5 8.5a2 2 0 0 1-3-3L16 7" />
                </svg>
                파일 첨부
              </button>

              <label className="flex cursor-pointer items-center gap-1.5 text-xs text-gray-600">
                <input
                  type="checkbox"
                  checked={asNotice}
                  onChange={(e) => setAsNotice(e.target.checked)}
                  className="h-3.5 w-3.5 accent-amber-500"
                />
                공지로 등록
              </label>

              <span className="flex-1" />

              <button
                type="button"
                disabled={!canSend}
                onClick={() => void submit()}
                className="rounded-md bg-blue-600 px-4 py-1.5 text-sm font-medium text-white disabled:bg-gray-300"
              >
                보내기
              </button>
            </div>

            {asNotice && (
              <p className="mt-2 text-xs text-amber-700">
                등록하면 채널 멤버 전체에게 알림이 가고, 이전 공지는 해제됩니다.
              </p>
            )}
            {noticeWarning && <p className="mt-2 text-xs text-amber-700">{noticeWarning}</p>}
            {sendError && <p className="mt-2 text-xs text-red-600">{sendError}</p>}
          </div>
        </>
      )}
    </div>
  )
}
