import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import { useNotification } from '@/composables/useNotification'
import { DEFAULT_PAGE_SIZE } from '@/types/pagination'
import { escapeHtml, relativeTimeKo, taskDueInfo } from '@/shared/utils/format'
import type { ApiNotification, NotificationView } from '@/types/notification'

// payload(자유형)에서 문자열/숫자 값 안전 추출
function pstr(p: Record<string, unknown>, k: string): string {
  const v = p[k]
  return typeof v === 'string' ? v : ''
}
function pnum(p: Record<string, unknown>, k: string): number | null {
  const v = p[k]
  return typeof v === 'number' ? v : null
}

// API 알림 → 화면 뷰(type+payload → 톤/문구/이동경로). 사용자 입력은 escapeHtml 후 신뢰 태그만 조립
function toView(n: ApiNotification): NotificationView {
  const p = n.payload
  const actor = escapeHtml(pstr(p, 'actorName') || '누군가')
  const projectName = escapeHtml(pstr(p, 'projectName'))
  const taskTitle = escapeHtml(pstr(p, 'taskTitle'))
  const projectId = pstr(p, 'projectId')
  const taskSeq = pnum(p, 'taskSeq')
  const taskTo =
    projectId && taskSeq !== null
      ? `/projects/${projectId}/tasks/${taskSeq}`
      : null

  let tone = ''
  let html = '새 알림'
  let to: string | null = taskTo
  switch (n.type) {
    case 'task.assigned':
      tone = 'blue'
      html = `<b>${actor}</b>님이 <b>${taskTitle}</b> 업무를 지시했습니다`
      break
    case 'task.unassigned':
      tone = 'red'
      html = `<b>${actor}</b>님이 <b>${taskTitle}</b> 업무 담당에서 회원님을 제외했습니다`
      break
    case 'task.started':
      tone = 'blue'
      html = `<b>${actor}</b>님이 <b>${taskTitle}</b> 업무를 시작했습니다`
      break
    case 'task.due_changed': {
      const due = pstr(p, 'dueDate')
      const body = due
        ? `마감기한을 <b>${escapeHtml(taskDueInfo(due, false).dateLabel)}</b>(으)로 변경했습니다`
        : '마감기한을 해제했습니다'
      tone = 'blue'
      html = `<b>${actor}</b>님이 <b>${taskTitle}</b>의 ${body}`
      break
    }
    case 'task.checkpoint_reported':
      tone = 'blue'
      html = `<b>${actor}</b>님이 <b>${taskTitle}</b>의 중간 점검 상태를 보고했습니다`
      break
    case 'task.removed':
      tone = 'red'
      to = projectId ? `/projects/${projectId}` : null // 업무가 삭제되므로 프로젝트로 이동
      html = `<b>${actor}</b>님이 <b>${taskTitle}</b> 업무를 삭제했습니다`
      break
    case 'task.approval_requested':
      tone = 'amber'
      html = `<b>${actor}</b>님이 <b>${taskTitle}</b> 승인을 요청했습니다`
      break
    case 'task.approved':
      tone = 'green'
      html = `<b>${taskTitle}</b> 업무가 승인되었습니다`
      break
    case 'task.changes_requested':
      tone = 'red'
      html = `<b>${actor}</b>님이 <b>${taskTitle}</b> 수정을 요청했습니다`
      break
    case 'task.commented':
      tone = ''
      html = `<b>${actor}</b>님이 <b>${taskTitle}</b>에 ${p.isReply === true ? '답글' : '댓글'}을 남겼습니다`
      break
    case 'member.invited':
      tone = 'accent'
      to = projectId ? `/projects/${projectId}` : null
      html = `<b>${actor}</b>님이 <b>${projectName}</b> 프로젝트에 초대했습니다`
      break
    case 'member.role_changed': {
      const role = pstr(p, 'roleType') === 'admin' ? '관리자' : '멤버'
      tone = 'accent'
      to = projectId ? `/projects/${projectId}` : null
      html = `<b>${actor}</b>님이 <b>${projectName}</b>에서 회원님의 역할을 <b>${role}</b>(으)로 변경했습니다`
      break
    }
    case 'member.removed':
      tone = 'red'
      to = projectId ? `/projects/${projectId}` : null
      html = `<b>${actor}</b>님이 <b>${projectName}</b> 프로젝트에서 회원님을 제외했습니다`
      break
  }
  return { id: n.id, read: n.read, tone, html, time: relativeTimeKo(n.createdAt), to }
}

// SSE 재연결 지연(ms) — 토큰 만료/네트워크 끊김 시 정리 후 재시도
const RECONNECT_DELAY = 5_000

// 알림 스토어 — 수신함(목록/미읽음) + 실시간 스트림(SSE)
// EventSource 는 store 싱글톤이 보유해 화면 전환과 무관하게 연결을 유지한다(로그아웃 시에만 해제).
export const useNotificationStore = defineStore('notification', () => {
  const items = ref<ApiNotification[]>([])
  const unreadCount = ref(0)
  const total = ref(0)
  const page = ref(0)
  const loading = ref(false)
  const loadingMore = ref(false)

  const views = computed<NotificationView[]>(() => items.value.map(toView))
  const hasMore = computed(() => items.value.length < total.value)

  const notificationApi = useNotification()

  // --- SSE 실시간 스트림 ---
  // (Pinia setup store 외부 상태 — 반응형 불필요한 연결 핸들/타이머)
  let es: EventSource | null = null
  let reconnectTimer: ReturnType<typeof setTimeout> | null = null
  let wantConnected = false

  function openStream(): void {
    const base = import.meta.env.VITE_API_BASE_URL || '/api'
    es = new EventSource(`${base}/notifications/stream`, { withCredentials: true })
    es.onmessage = (e: MessageEvent<string>) => {
      try {
        const n = JSON.parse(e.data) as ApiNotification
        // 재연결 시 동일 알림 재수신 방어(중복 id 무시)
        if (items.value.some((x) => x.id === n.id)) return
        items.value.unshift(n)
        total.value += 1
        if (!n.read) unreadCount.value += 1
      } catch {
        // 파싱 실패(하트비트 등 비정상 페이로드) 무시
      }
    }
    es.onerror = () => {
      // 연결 끊김(토큰 만료/네트워크) — 정리 후 재연결 예약. 해제 의사가 있으면 멈춤
      if (es) {
        es.close()
        es = null
      }
      if (wantConnected && !reconnectTimer) {
        reconnectTimer = setTimeout(() => {
          reconnectTimer = null
          if (wantConnected) openStream()
        }, RECONNECT_DELAY)
      }
    }
  }

  // 연결 시작(멱등) — 최초 1회 목록 로드 + 스트림 오픈. 로그인 화면 진입마다 호출돼도 안전
  async function connect(): Promise<void> {
    if (wantConnected) return
    wantConnected = true
    await fetchInitial()
    openStream()
  }

  // 연결 해제 + 상태 초기화(로그아웃 시)
  function disconnect(): void {
    wantConnected = false
    if (reconnectTimer) {
      clearTimeout(reconnectTimer)
      reconnectTimer = null
    }
    if (es) {
      es.close()
      es = null
    }
    items.value = []
    unreadCount.value = 0
    total.value = 0
    page.value = 0
  }

  // --- 목록/읽음 처리 ---

  // 첫 페이지(리셋) 로드 + 미읽음 수 동기화
  async function fetchInitial(): Promise<void> {
    loading.value = true
    try {
      const res = await notificationApi.list(1, DEFAULT_PAGE_SIZE)
      items.value = res.items
      total.value = res.total
      unreadCount.value = res.unreadCount
      page.value = 1
    } finally {
      loading.value = false
    }
  }

  // 다음 페이지 이어붙이기(더보기)
  async function loadMore(): Promise<void> {
    if (loadingMore.value || !hasMore.value) return
    loadingMore.value = true
    try {
      const res = await notificationApi.list(page.value + 1, DEFAULT_PAGE_SIZE)
      // 그 사이 실시간 수신으로 중복될 수 있는 id 는 제외하고 append
      const known = new Set(items.value.map((x) => x.id))
      items.value.push(...res.items.filter((n) => !known.has(n.id)))
      total.value = res.total
      unreadCount.value = res.unreadCount
      page.value += 1
    } finally {
      loadingMore.value = false
    }
  }

  // 단건 읽음 처리(낙관적 갱신 후 서버 미읽음 수로 보정)
  async function markRead(id: string): Promise<void> {
    const target = items.value.find((n) => n.id === id)
    if (!target || target.read) return
    target.read = true
    try {
      const { unreadCount: c } = await notificationApi.markRead(id)
      unreadCount.value = c
    } catch {
      target.read = false // 실패 시 롤백(인터셉터가 알림 처리)
    }
  }

  // 전체 읽음 처리
  async function markAllRead(): Promise<void> {
    if (unreadCount.value === 0) return
    try {
      const { unreadCount: c } = await notificationApi.markAllRead()
      items.value.forEach((n) => (n.read = true))
      unreadCount.value = c
    } catch {
      // 인터셉터가 알림 처리
    }
  }

  return {
    items,
    views,
    unreadCount,
    total,
    loading,
    loadingMore,
    hasMore,
    connect,
    disconnect,
    fetchInitial,
    loadMore,
    markRead,
    markAllRead,
  }
})
