import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import Timeline from './Timeline'
import { withCodes } from '../codes/fixtures'
import type { Org, TimelineEvent } from '../api/client'

const mocks = vi.hoisted(() => ({
  getTimeline: vi.fn(),
}))

vi.mock('../api/client', async (importOriginal) => ({
  ...(await importOriginal<typeof import('../api/client')>()),
  getTimeline: mocks.getTimeline,
}))

function org(overrides: Partial<Org> = {}): Org {
  return {
    id: 1,
    orgNo: '001',
    orgName: '국제방송교류재단',
    status: 'ACTIVE',
    applicant: null,
    mj: null,
    itn: null,
    lawyer: null,
    lawyerAssignedDate: null,
    channelIdMj: 'chan-mj',
    channelIdLaw: 'chan-law',
    stage: 5,
    ...overrides,
  }
}

function event(overrides: Partial<TimelineEvent> = {}): TimelineEvent {
  return {
    type: 'STAGE',
    at: Date.now(),
    title: '1',
    detail: null,
    ...overrides,
  }
}

beforeEach(() => {
  mocks.getTimeline.mockReset()
})

describe('Timeline', () => {
  it('불러오는 동안 로딩 문구를 보여준다', () => {
    mocks.getTimeline.mockReturnValue(new Promise(() => {}))

    render(withCodes(<Timeline org={org()} />))

    expect(screen.getByText('불러오는 중…')).toBeTruthy()
  })

  it('기록이 없으면 빈 상태 문구를 보여준다', async () => {
    mocks.getTimeline.mockResolvedValue([])

    render(withCodes(<Timeline org={org()} />))

    await waitFor(() =>
      expect(
        screen.getByText('아직 기록이 없습니다. 채널을 만들고 업무를 진행하면 여기에 쌓입니다.'),
      ).toBeTruthy(),
    )
  })

  it('세 종류 이벤트를 종류별 라벨과 함께 날짜로 묶어서 보여준다', async () => {
    const day1 = new Date('2026-07-20T09:00:00').getTime()
    const day2Morning = new Date('2026-07-21T08:30:00').getTime()
    const day2Afternoon = new Date('2026-07-21T15:00:00').getTime()

    mocks.getTimeline.mockResolvedValue([
      event({ type: 'FILE', at: day2Afternoon, title: '보고서.pdf', detail: '문정원 · 박아이티' }),
      event({ type: 'MEMO', at: day2Morning, title: '송민지', detail: '전화 통화 내용' }),
      event({ type: 'STAGE', at: day1, title: '4', detail: null }),
    ])

    render(withCodes(<Timeline org={org()} />))

    await waitFor(() => expect(screen.getByText('보고서.pdf')).toBeTruthy())

    expect(screen.getAllByText('자료 등록')).toHaveLength(1)
    expect(screen.getByText('문정원 · 박아이티')).toBeTruthy()

    expect(screen.getByText('업무메모')).toBeTruthy()
    expect(screen.getByText('송민지')).toBeTruthy()
    expect(screen.getByText('전화 통화 내용')).toBeTruthy()

    expect(screen.getByText('단계 변경')).toBeTruthy()
    expect(screen.getByText('4. 변호사 배당 단계로 진행')).toBeTruthy()

    // 날짜 그룹 헤더 2개(2026-07-21, 2026-07-20)가 최신순으로 보여야 한다.
    const headers = screen.getAllByText(/2026-07-2[01]/)
    expect(headers[0].textContent).toContain('2026-07-21')
    expect(headers[1].textContent).toContain('2026-07-20')
  })

  it('새로고침 버튼을 누르면 다시 조회한다', async () => {
    mocks.getTimeline.mockResolvedValue([])

    render(withCodes(<Timeline org={org()} />))

    await waitFor(() => expect(mocks.getTimeline).toHaveBeenCalledTimes(1))

    fireEvent.click(screen.getByRole('button', { name: '새로고침' }))

    await waitFor(() => expect(mocks.getTimeline).toHaveBeenCalledTimes(2))
  })

  it('조회가 실패하면 오류 메시지를 보여준다', async () => {
    mocks.getTimeline.mockRejectedValue(new Error('타임라인을 불러오지 못했습니다.'))

    render(withCodes(<Timeline org={org()} />))

    await waitFor(() => expect(screen.getByText('타임라인을 불러오지 못했습니다.')).toBeTruthy())
  })
})
