import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import Dashboard from './Dashboard'
import type { DashboardData, DashboardOrgRow } from '../api/client'

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

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

const DAY = 24 * 60 * 60 * 1000

function row(overrides: Partial<DashboardOrgRow> = {}): DashboardOrgRow {
  return {
    id: 1,
    orgNo: '001',
    orgName: '한국출판문화산업진흥원',
    stage: 5,
    hasChannel: true,
    lawyerName: '전세준',
    mjName: '이연경',
    lawyerAssignedDate: '2026-06-19',
    reviewTotal: 392,
    reviewDone: 78,
    processTotal: 0,
    processDone: 0,
    stageChangedAt: Date.now() - 3 * DAY,
    lastChangedAt: Date.now() - 3 * DAY,
    reCount: 0,
    ...overrides,
  }
}

function data(rows: DashboardOrgRow[], summary: Partial<DashboardData['summary']> = {}): DashboardData {
  return {
    summary: {
      applied: rows.length,
      docSubmitted: 0,
      reviewTotal: 0,
      reviewDone: 0,
      processTotal: 0,
      processDone: 0,
      unresolvedRe: 0,
      reportWriting: 0,
      completed: 0,
      ...summary,
    },
    orgs: rows,
  }
}

function renderDashboard(onOpenOrg = vi.fn(), onOpenOrgList = vi.fn()) {
  render(<Dashboard onOpenOrg={onOpenOrg} onOpenOrgList={onOpenOrgList} />)
  return { onOpenOrg, onOpenOrgList }
}

/** 기관명은 칸반 카드와 표에 동시에 나오므로, 표를 볼 때는 항상 표 안에서만 찾는다. */
function table() {
  return within(screen.getByTestId('recent-table'))
}

describe('Dashboard', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mocks.getDashboard.mockResolvedValue(data([row()]))
    mocks.updateStage.mockResolvedValue({})
  })

  it('KPI 타일에 서버가 준 집계를 그대로 보여준다', async () => {
    mocks.getDashboard.mockResolvedValue(
      data([row()], {
        applied: 50,
        docSubmitted: 9,
        reviewTotal: 2012,
        reviewDone: 503,
        processTotal: 197,
        processDone: 0,
        completed: 1,
      }),
    )
    renderDashboard()

    expect(await screen.findByText('503 / 2,012')).toBeTruthy()
    expect(screen.getByText('0 / 197')).toBeTruthy()
    // 권리확인 진척률
    expect(screen.getByText('25%')).toBeTruthy()
  })

  it('채널이 없는 기관은 단계 미지정 컬럼에 담긴다', async () => {
    mocks.getDashboard.mockResolvedValue(
      data([row({ id: 1, orgName: '채널없는기관', hasChannel: false, stage: null })]),
    )
    renderDashboard()

    const column = await screen.findByTestId('column-unassigned')
    expect(column.textContent).toContain('채널없는기관')
    expect(column.textContent).toContain('단계 미지정')
  })

  it('단계 미지정 카드를 끌면 이동하지 않고 채널 생성 경고창이 뜬다', async () => {
    mocks.getDashboard.mockResolvedValue(
      data([row({ orgName: '채널없는기관', hasChannel: false, stage: null })]),
    )
    renderDashboard()

    const column = await screen.findByTestId('column-unassigned')
    const card = within(column).getByText('채널없는기관')
    fireEvent.dragStart(card.parentElement!, {
      dataTransfer: { setData: vi.fn(), getData: vi.fn() },
    })

    expect(await screen.findByText('채널을 먼저 생성해 주세요')).toBeTruthy()
    expect(mocks.updateStage).not.toHaveBeenCalled()
  })

  it('채널이 있는 카드를 다른 컬럼에 떨어뜨리면 단계가 바뀐다', async () => {
    renderDashboard()
    await screen.findByTestId('recent-table')

    const target = screen.getByTestId('column-6')
    fireEvent.drop(target, {
      dataTransfer: { getData: () => '1', setData: vi.fn() },
    })

    await waitFor(() => expect(mocks.updateStage).toHaveBeenCalledWith(1, 6))
  })

  it('같은 컬럼에 떨어뜨리면 아무 것도 하지 않는다', async () => {
    renderDashboard()
    await screen.findByTestId('recent-table')

    fireEvent.drop(screen.getByTestId('column-5'), {
      dataTransfer: { getData: () => '1', setData: vi.fn() },
    })

    await waitFor(() => expect(mocks.getDashboard).toHaveBeenCalledTimes(1))
    expect(mocks.updateStage).not.toHaveBeenCalled()
  })

  it('단계 미지정 컬럼에는 떨어뜨려도 단계가 바뀌지 않는다', async () => {
    renderDashboard()
    await screen.findByTestId('recent-table')

    fireEvent.drop(screen.getByTestId('column-unassigned'), {
      dataTransfer: { getData: () => '1', setData: vi.fn() },
    })

    await waitFor(() => expect(mocks.updateStage).not.toHaveBeenCalled())
  })

  it('카드에 현재 단계 진입 후 경과일을 D+n으로 보여준다', async () => {
    mocks.getDashboard.mockResolvedValue(
      data([row({ stageChangedAt: Date.now() - 30 * DAY })]),
    )
    renderDashboard()

    expect(await screen.findByText('D+30')).toBeTruthy()
  })

  it('최근 진행 기관 표는 최근 변경순으로 정렬한다', async () => {
    mocks.getDashboard.mockResolvedValue(
      data([
        row({ id: 1, orgNo: '001', orgName: '오래된기관', lastChangedAt: Date.now() - 10 * DAY }),
        row({ id: 2, orgNo: '002', orgName: '최근기관', lastChangedAt: Date.now() }),
      ]),
    )
    renderDashboard()

    await screen.findByTestId('recent-table')
    const names = table().getAllByRole('row').slice(1).map((tr) => tr.children[1].textContent)
    expect(names).toEqual(['최근기관', '오래된기관'])
  })

  it('표는 8개까지만 보여주고 더보기로 늘린다', async () => {
    const rows = Array.from({ length: 10 }, (_, i) =>
      row({ id: i + 1, orgNo: String(i + 1).padStart(3, '0'), orgName: `기관${i + 1}` }),
    )
    mocks.getDashboard.mockResolvedValue(data(rows))
    renderDashboard()

    await screen.findByTestId('recent-table')
    expect(table().getAllByRole('row')).toHaveLength(9) // 헤더 1 + 8

    fireEvent.click(screen.getByText('더보기 (2개 남음)'))
    expect(table().getAllByRole('row')).toHaveLength(11)
  })

  it('기관명 검색으로 표를 거른다', async () => {
    mocks.getDashboard.mockResolvedValue(
      data([
        row({ id: 1, orgNo: '001', orgName: '한국출판문화산업진흥원' }),
        row({ id: 2, orgNo: '002', orgName: '세종학당재단' }),
      ]),
    )
    renderDashboard()

    await screen.findByTestId('recent-table')
    fireEvent.change(screen.getByLabelText('기관명/기관코드 검색'), { target: { value: '세종' } })

    expect(table().queryByText('한국출판문화산업진흥원')).toBeNull()
    expect(table().getByText('세종학당재단')).toBeTruthy()
  })

  it('표에서 기관을 클릭하면 기관관리로 넘긴다', async () => {
    const onOpenOrg = vi.fn()
    renderDashboard(onOpenOrg)

    await screen.findByTestId('recent-table')
    fireEvent.click(table().getByText('한국출판문화산업진흥원'))
    expect(onOpenOrg).toHaveBeenCalledWith(1)
  })

  it('하단 전체 보기는 사업관리로 넘긴다', async () => {
    const onOpenOrgList = vi.fn()
    renderDashboard(vi.fn(), onOpenOrgList)

    fireEvent.click(await screen.findByText('사업관리에서 전체 보기 →'))
    expect(onOpenOrgList).toHaveBeenCalled()
  })
})
