import { render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { CodeProvider } from './CodeProvider'
import { TEST_CODES, withCodes } from './fixtures'
import { useStages } from './stage'
import { badgeClass, barClass } from './tone'
import { useCodeLabel, useCodes, useScopedCodes, useSplitByFirstRow } from './useCodes'

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

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

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

describe('useCodes', () => {
  it('그룹의 선택지를 정렬순서대로 준다', () => {
    function Probe() {
      return <span>{useCodes('REVIEW_RESULT').map((c) => c.label).join('|')}</span>
    }
    render(withCodes(<Probe />))

    expect(
      screen.getByText('신유형 개방|계약서 등 재확인|권리처리 추진|개방불가|권리처리 추진 미희망'),
    ).toBeTruthy()
  })

  it('없는 그룹이면 빈 배열이라 화면이 죽지 않는다', () => {
    function Probe() {
      return <span data-testid="n">{useCodes('NO_SUCH_GROUP').length}</span>
    }
    render(withCodes(<Probe />))

    expect(screen.getByTestId('n').textContent).toBe('0')
  })

  it('저장된 값의 표시명을 준다', () => {
    function Probe() {
      return <span>{useCodeLabel('CONTACT_CATEGORY', 'MJ')}</span>
    }
    render(withCodes(<Probe />))

    // 엑셀에는 '주관기관'으로 오지만 화면에는 '문정원'으로 나와야 한다.
    expect(screen.getByText('문정원')).toBeTruthy()
  })

  it('코드표에 없는 값이면 값 자체를 보여준다', () => {
    function Probe() {
      return <span>{useCodeLabel('CONTACT_METHOD', '문자')}</span>
    }
    render(withCodes(<Probe />))

    // 과거 데이터가 빈칸으로 사라지는 것보다 원문이라도 보이는 편이 낫다.
    expect(screen.getByText('문자')).toBeTruthy()
  })
})

describe('공공누리 유형의 사용처 구분', () => {
  function ScopeProbe({ scope }: { scope: string }) {
    return <span>{useScopedCodes('KOGL_TYPE', scope).map((c) => c.code).join(',')}</span>
  }

  it('변호사 판정에는 보류가 있다', () => {
    render(withCodes(<ScopeProbe scope="review" />))
    expect(screen.getByText('0유형,1유형,2유형,3유형,4유형,개방불가,보류')).toBeTruthy()
  })

  it('권리처리 판정에는 보류가 없다', () => {
    render(withCodes(<ScopeProbe scope="process" />))
    expect(screen.getByText('0유형,1유형,2유형,3유형,4유형,개방불가')).toBeTruthy()
  })

  it('기존 부착 유형에는 조사표에서 온 값이 들어간다', () => {
    render(withCodes(<ScopeProbe scope="prior" />))
    expect(screen.getByText('0유형,1유형,2유형,3유형,4유형,유형 확인 필요,제4유형')).toBeTruthy()
  })

  it('라디오 첫 줄과 둘째 줄을 나눈다', () => {
    function Probe() {
      const { first, rest } = useSplitByFirstRow('KOGL_TYPE', 'review')
      return (
        <>
          <span data-testid="first">{first.map((c) => c.code).join(',')}</span>
          <span data-testid="rest">{rest.map((c) => c.code).join(',')}</span>
        </>
      )
    }
    render(withCodes(<Probe />))

    expect(screen.getByTestId('first').textContent).toBe('0유형,개방불가,보류')
    expect(screen.getByTestId('rest').textContent).toBe('1유형,2유형,3유형,4유형')
  })
})

describe('useStages', () => {
  function StageProbe() {
    const stages = useStages()
    return (
      <>
        <span data-testid="count">{stages.count}</span>
        <span data-testid="label13">{stages.label(13)}</span>
        <span data-testid="symbol13">{stages.symbol(13)}</span>
        <span data-testid="symbol1">{stages.symbol(1)}</span>
        <span data-testid="group5">{stages.groupKey(5)}</span>
        <span data-testid="tone-assign">{stages.groupTone('assign')}</span>
        <span data-testid="progress1">{stages.progressOf(1)}</span>
        <span data-testid="progress7">{stages.progressOf(7)}</span>
        <span data-testid="progress13">{stages.progressOf(13)}</span>
        <span data-testid="completed-from">{stages.kpiThreshold('completed')}</span>
        <span data-testid="counts12">{String(stages.countsToward('completed', 12))}</span>
        <span data-testid="counts13">{String(stages.countsToward('completed', 13))}</span>
      </>
    )
  }

  it('단계 수와 라벨이 코드표에서 온다', () => {
    render(withCodes(<StageProbe />))

    expect(screen.getByTestId('count').textContent).toBe('13')
    expect(screen.getByTestId('label13').textContent).toBe('보고서 제출 완료')
  })

  it('원문자는 번호에서 계산한다', () => {
    render(withCodes(<StageProbe />))

    expect(screen.getByTestId('symbol1').textContent).toBe('①')
    expect(screen.getByTestId('symbol13').textContent).toBe('⑬')
  })

  it('진행상태는 1단계만 신청이고 13단계만 완료다', () => {
    render(withCodes(<StageProbe />))

    expect(screen.getByTestId('progress1').textContent).toBe('applied')
    expect(screen.getByTestId('progress7').textContent).toBe('active')
    expect(screen.getByTestId('progress13').textContent).toBe('done')
  })

  it('완료 KPI는 보고서가 올라온 13단계부터 센다', () => {
    render(withCodes(<StageProbe />))

    expect(screen.getByTestId('completed-from').textContent).toBe('13')
    expect(screen.getByTestId('counts12').textContent).toBe('false')
    expect(screen.getByTestId('counts13').textContent).toBe('true')
  })

  it('업무구간과 색이 코드표에서 온다', () => {
    render(withCodes(<StageProbe />))

    expect(screen.getByTestId('group5').textContent).toBe('check')
    expect(screen.getByTestId('tone-assign').textContent).toBe('indigo')
  })
})

describe('tone', () => {
  it('의미 토큰을 실제 클래스로 바꾼다', () => {
    expect(badgeClass('emerald')).toBe('bg-emerald-50 text-emerald-700')
    expect(barClass('indigo')).toBe('bg-indigo-400')
  })

  it('모르는 토큰은 회색으로 떨어뜨린다', () => {
    // 코드 관리 화면에서 오타가 들어가도 색만 밋밋해질 뿐 항목이 사라지면 안 된다.
    expect(badgeClass('없는색')).toBe('bg-gray-100 text-gray-700')
    expect(barClass(null)).toBe('bg-gray-300')
  })
})

describe('CodeProvider', () => {
  it('서버에서 선택지를 받아 아래 화면에 넘긴다', async () => {
    mocks.getCodes.mockResolvedValue(TEST_CODES)

    function Probe() {
      return <span>{useCodes('PROCESS_STATUS').map((c) => c.label).join(',')}</span>
    }
    render(
      <CodeProvider>
        <Probe />
      </CodeProvider>,
    )

    await waitFor(() => expect(screen.getByText('미처리,처리완료')).toBeTruthy())
  })

  it('선택지를 못 받으면 화면을 그리지 않고 오류를 보여준다', async () => {
    mocks.getCodes.mockRejectedValue(new Error('서버 오류'))

    function Probe() {
      return <span>그려지면 안 됨</span>
    }
    render(
      <CodeProvider>
        <Probe />
      </CodeProvider>,
    )

    // 빈 드롭다운으로 저장하면 값이 지워지므로 아예 안 그리는 편이 안전하다.
    await waitFor(() => expect(screen.getByText(/선택지 정보를 불러오지 못했습니다/)).toBeTruthy())
    expect(screen.queryByText('그려지면 안 됨')).toBeNull()
  })
})
