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

const mocks = vi.hoisted(() => ({
  getPosts: vi.fn(),
  getFiles: vi.fn(),
  getMemos: vi.fn(),
  getContacts: vi.fn(),
  updateAssignments: vi.fn(),
  getTimeline: vi.fn(),
  updateStage: vi.fn(),
  getStageHistory: vi.fn(),
  getReviewPage: vi.fn(),
  getProcessPage: vi.fn(),
}))

vi.mock('../api/client', async (importOriginal) => ({
  ...(await importOriginal<typeof import('../api/client')>()),
  getPosts: mocks.getPosts,
  getFiles: mocks.getFiles,
  getMemos: mocks.getMemos,
  getContacts: mocks.getContacts,
  updateAssignments: mocks.updateAssignments,
  getTimeline: mocks.getTimeline,
  updateStage: mocks.updateStage,
  getStageHistory: mocks.getStageHistory,
  getReviewPage: mocks.getReviewPage,
  getProcessPage: mocks.getProcessPage,
}))

function contact(overrides: Partial<Contact> = {}): Contact {
  return {
    id: 1,
    category: 'APPLICANT',
    name: '이름없음',
    affiliation: null,
    deptName: null,
    title: null,
    phone: null,
    email: null,
    ...overrides,
  }
}

function org(overrides: Partial<Org> = {}): Org {
  return {
    id: 1,
    orgNo: '001_00',
    orgName: '국제방송교류재단',
    status: 'ACTIVE',
    applicant: contact({
      id: 1,
      category: 'APPLICANT',
      name: '송민지',
      affiliation: '국제방송교류재단',
      deptName: '데이터정보화팀',
      title: '과장',
      phone: '02-3475-5434',
      email: 'ming@arirang.com',
    }),
    mj: null,
    itn: null,
    lawyer: null,
    lawyerAssignedDate: null,
    channelIdMj: 'chan-mj',
    channelIdLaw: 'chan-law',
    stage: null,
    ...overrides,
  }
}

beforeEach(() => {
  mocks.getPosts.mockReset().mockResolvedValue([])
  mocks.getFiles.mockReset().mockResolvedValue([])
  mocks.getMemos.mockReset().mockResolvedValue([])
  mocks.getContacts.mockReset().mockResolvedValue([])
  mocks.updateAssignments.mockReset().mockResolvedValue(undefined)
  mocks.getStageHistory.mockReset().mockResolvedValue([])
  mocks.getTimeline.mockReset().mockResolvedValue([])
  mocks.updateStage.mockReset().mockResolvedValue(undefined)
  mocks.getReviewPage.mockReset().mockResolvedValue({ items: [], total: 0, done: 0, page: 0, size: 30 })
  mocks.getProcessPage.mockReset().mockResolvedValue({ items: [], total: 0, done: 0, page: 0, size: 30 })
})

describe('OrgOverview', () => {
  it('연번_기관명 헤더와 신청기관 담당자 정보를 보여준다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    expect(screen.getByText('001_00_국제방송교류재단')).toBeTruthy()
    expect(screen.getByText('데이터정보화팀')).toBeTruthy()
    expect(screen.getByText('송민지 과장')).toBeTruthy()
    expect(screen.getByText('02-3475-5434')).toBeTruthy()
    expect(screen.getByText('ming@arirang.com')).toBeTruthy()
    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
  })

  it('문정원 담당자와 아이티앤 담당자와 담당 변호사 정보가 비어 있으면 카드에 -로 표시된다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    const mjCard = screen.getByText('문정원 담당자').closest('section') as HTMLElement
    const itnCard = screen.getByText('아이티앤 담당자').closest('section') as HTMLElement
    const lawyerCard = screen.getByText('담당 변호사').closest('section') as HTMLElement

    expect(within(mjCard).getAllByText('-')).toHaveLength(4)
    expect(within(itnCard).getAllByText('-')).toHaveLength(4)
    expect(within(lawyerCard).getAllByText('-')).toHaveLength(4)
    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
  })

  it('담당자 4종이 값을 가지고 있으면 각 카드에 값이 표시된다', async () => {
    render(
      withCodes(
      <OrgOverview
        org={org({
          mj: contact({
            id: 2,
            category: 'MJ',
            name: '김문정',
            deptName: '문화체육관광부 저작권정책과',
            phone: '02-1234-5678',
            email: 'mj@mcst.go.kr',
          }),
          itn: contact({
            id: 4,
            category: 'ITN',
            name: '박아이티',
            deptName: '개발팀',
            phone: '02-1111-2222',
            email: 'itn@iten.co.kr',
          }),
          lawyer: contact({
            id: 3,
            category: 'LAWYER',
            name: '이변호',
            phone: '02-9876-5432',
            email: 'lawyer@lawfirm.kr',
          }),
          lawyerAssignedDate: '2026-07-21',
        })}
      />,
      ),
    )

    const applicantCard = screen.getByText('신청기관 담당자').closest('section') as HTMLElement
    const mjCard = screen.getByText('문정원 담당자').closest('section') as HTMLElement
    const itnCard = screen.getByText('아이티앤 담당자').closest('section') as HTMLElement
    const lawyerCard = screen.getByText('담당 변호사').closest('section') as HTMLElement

    expect(within(applicantCard).getByText('데이터정보화팀')).toBeTruthy()
    expect(within(applicantCard).getByText('송민지 과장')).toBeTruthy()

    expect(within(mjCard).getByText('문화체육관광부 저작권정책과')).toBeTruthy()
    expect(within(mjCard).getByText('김문정')).toBeTruthy()
    expect(within(mjCard).getByText('02-1234-5678')).toBeTruthy()
    expect(within(mjCard).getByText('mj@mcst.go.kr')).toBeTruthy()

    expect(within(itnCard).getByText('개발팀')).toBeTruthy()
    expect(within(itnCard).getByText('박아이티')).toBeTruthy()
    expect(within(itnCard).getByText('02-1111-2222')).toBeTruthy()
    expect(within(itnCard).getByText('itn@iten.co.kr')).toBeTruthy()

    expect(within(lawyerCard).getByText('이변호')).toBeTruthy()
    expect(within(lawyerCard).getByText('02-9876-5432')).toBeTruthy()
    expect(within(lawyerCard).getByText('lawyer@lawfirm.kr')).toBeTruthy()
    expect(within(lawyerCard).getByText('2026-07-21')).toBeTruthy()

    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
  })

  it('배정된 카드에는 변경, 빈 카드에는 생성 버튼이 보인다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    expect(screen.getByRole('button', { name: '신청기관 담당자 변경' })).toBeTruthy()
    expect(screen.getByRole('button', { name: '문정원 담당자 생성' })).toBeTruthy()
    expect(screen.getByRole('button', { name: '아이티앤 담당자 생성' })).toBeTruthy()
    expect(screen.getByRole('button', { name: '담당 변호사 생성' })).toBeTruthy()
    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
  })

  it('생성 버튼을 누르면 선택 모달이 열리고 담당자를 고르면 배정 API를 부른다', async () => {
    mocks.getContacts.mockResolvedValue([
      contact({ id: 7, category: 'MJ', name: '이연경', deptName: '공공저작물팀' }),
    ])
    const onChanged = vi.fn()

    render(withCodes(<OrgOverview org={org()} onChanged={onChanged} />))

    fireEvent.click(screen.getByRole('button', { name: '문정원 담당자 생성' }))

    await waitFor(() => expect(screen.getByText('문정원 담당자 선택')).toBeTruthy())
    fireEvent.click(await screen.findByRole('button', { name: '선택' }))

    await waitFor(() =>
      expect(mocks.updateAssignments).toHaveBeenCalledWith(1, {
        applicantContactId: 1,
        mjContactId: 7,
        itnContactId: null,
        lawyerContactId: null,
        lawyerAssignedDate: null,
      }),
    )
    expect(onChanged).toHaveBeenCalled()
  })

  it('아이티앤 담당자 생성 버튼을 누르면 선택 모달이 열리고 담당자를 고르면 itnContactId로 배정 API를 부른다', async () => {
    mocks.getContacts.mockResolvedValue([
      contact({ id: 8, category: 'ITN', name: '박아이티', deptName: '개발팀' }),
    ])
    const onChanged = vi.fn()

    render(withCodes(<OrgOverview org={org()} onChanged={onChanged} />))

    fireEvent.click(screen.getByRole('button', { name: '아이티앤 담당자 생성' }))

    await waitFor(() => expect(screen.getByText('아이티앤 담당자 선택')).toBeTruthy())
    fireEvent.click(await screen.findByRole('button', { name: '선택' }))

    await waitFor(() =>
      expect(mocks.updateAssignments).toHaveBeenCalledWith(1, {
        applicantContactId: 1,
        mjContactId: null,
        itnContactId: 8,
        lawyerContactId: null,
        lawyerAssignedDate: null,
      }),
    )
    expect(onChanged).toHaveBeenCalled()
  })

  it('상단 동작 버튼에서 담당 변호사 변경은 사라졌다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    expect(screen.queryByRole('button', { name: '담당 변호사 변경' })).toBeNull()
    expect(screen.getByRole('button', { name: '진행단계 변경' })).not.toBeDisabled()
    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
  })

  it('완료된 단계와 진행중 단계 아래에 도달 날짜가 찍힌다', async () => {
    mocks.getStageHistory.mockResolvedValue([
      { stage: 3, changedAt: new Date('2026-07-24T10:00:00').getTime() },
      { stage: 2, changedAt: new Date('2026-07-20T09:00:00').getTime() },
      { stage: 1, changedAt: new Date('2026-07-15T09:00:00').getTime() },
    ])

    render(withCodes(<OrgOverview org={org({ stage: 3 })} />))

    await waitFor(() => {
      expect(screen.getByText('2026-07-24 10:00')).toBeTruthy()
      expect(screen.getByText('2026-07-20 09:00')).toBeTruthy()
      expect(screen.getByText('2026-07-15 09:00')).toBeTruthy()
    })
  })

  it('업무단계 12개가 순서대로 보인다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    expect(screen.getByText('신청')).toBeTruthy()
    expect(screen.getByText('변호사 배당')).toBeTruthy()
    expect(screen.getByText('보고서 작성')).toBeTruthy()
    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
  })

  it('기본 탭인 채팅은 자리표시 대신 채널 선택 화면을 보여준다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    expect(screen.getByRole('button', { name: '문정원' })).toBeTruthy()
    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalledWith(1, 'mj'))
  })

  it('권리확인 탭을 누르면 권리확인 보드가 마운트되어 조회를 부른다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())

    fireEvent.click(screen.getByRole('button', { name: '권리확인' }))

    await waitFor(() => expect(mocks.getReviewPage).toHaveBeenCalledWith(1, expect.anything()))
    expect(screen.queryByText('권리확인 상세 화면은 추후 확정')).toBeNull()
    expect(screen.queryByRole('button', { name: '문정원' })).toBeNull()
  })

  it('탭을 누르면 해당 탭의 자리표시 내용으로 바뀐다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())

    fireEvent.click(screen.getByRole('button', { name: 'RE' }))

    expect(screen.getByText('RE 상세 화면은 추후 확정')).toBeTruthy()
    expect(screen.queryByRole('button', { name: '문정원' })).toBeNull()
  })

  it('권리처리 탭을 누르면 권리처리 보드가 마운트되어 조회를 부른다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())

    fireEvent.click(screen.getByRole('button', { name: '권리처리' }))

    await waitFor(() => expect(mocks.getProcessPage).toHaveBeenCalledWith(1, expect.anything()))
    expect(screen.queryByText('권리처리 상세 화면은 추후 확정')).toBeNull()
    expect(screen.queryByRole('button', { name: '문정원' })).toBeNull()
  })

  it('권리확인/권리처리 통계 카드가 각 조회 결과의 완료·전체 건수를 보여준다', async () => {
    mocks.getReviewPage.mockResolvedValue({ items: [], total: 12, done: 5, page: 0, size: 1 })
    mocks.getProcessPage.mockResolvedValue({ items: [], total: 8, done: 3, page: 0, size: 1 })

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

    await waitFor(() => {
      expect(screen.getByText('완료 5 / 전체 12')).toBeTruthy()
      expect(screen.getByText('완료 3 / 전체 8')).toBeTruthy()
    })
  })

  it('타임라인 탭을 누르면 Timeline이 마운트되어 조회를 부른다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())

    fireEvent.click(screen.getByRole('button', { name: '타임라인' }))

    await waitFor(() => expect(mocks.getTimeline).toHaveBeenCalledWith(1))
    expect(screen.queryByText('타임라인 상세 화면은 추후 확정')).toBeNull()
  })

  it('자료 탭을 누르면 자료 목록 화면으로 바뀐다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    fireEvent.click(screen.getByRole('button', { name: '자료' }))

    await waitFor(() => expect(mocks.getFiles).toHaveBeenCalledWith(1, 'mj'))
    expect(screen.getByRole('button', { name: '새로고침' })).toBeTruthy()
  })

  it('채널이 있으면 Mattermost 열기가 채널 링크로 활성화된다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    const link = screen.getByRole('link', { name: 'Mattermost 열기' })
    expect(link).toHaveAttribute(
      'href',
      'https://hub.iten.co.kr/itn-hub/channels/org-001-00-mj',
    )
    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
  })

  it('채널이 없으면 Mattermost 열기가 비활성이다', () => {
    render(withCodes(<OrgOverview org={org({ channelIdMj: null, channelIdLaw: null, status: 'READY' })} />))

    expect(screen.queryByRole('link', { name: 'Mattermost 열기' })).toBeNull()
    expect(screen.getByRole('button', { name: 'Mattermost 열기' })).toBeDisabled()
  })

  it('치안정책연구소도 합성코드 슬러그로 채널 링크를 만든다', async () => {
    render(
      withCodes(
        <OrgOverview
          org={org({ orgNo: '008_01', orgName: '경찰청_치안정책연구소', channelIdMj: 'x' })}
        />,
      ),
    )

    expect(screen.getByRole('link', { name: 'Mattermost 열기' })).toHaveAttribute(
      'href',
      'https://hub.iten.co.kr/itn-hub/channels/org-008-01-mj',
    )
    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
  })

  it('미구현 동작 버튼들은 비활성이다', async () => {
    render(withCodes(<OrgOverview org={org()} />))

    expect(screen.getByRole('button', { name: '자료 업로드' })).toBeDisabled()
    expect(screen.getByRole('button', { name: '보고서 관리' })).toBeDisabled()
    // 최초 게시글 보기는 구현되어 채널이 있으면 눌릴 수 있다
    expect(screen.getByRole('button', { name: '최초 게시글 보기' })).toBeEnabled()
    await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
  })

  describe('업무단계 현황 스트립', () => {
    it('채널이 없어 단계가 시작되지 않았으면 전부 회색이고 안내 문구가 보인다', async () => {
      render(withCodes(<OrgOverview org={org({ stage: null, channelIdMj: null, channelIdLaw: null, status: 'READY' })} />))

      expect(screen.getByText('채널 생성 시 신청 단계가 시작됩니다')).toBeTruthy()
      expect(screen.queryByText('진행중')).toBeNull()
    })

    it('현재 단계 이전은 완료(체크) 표시, 현재 단계는 진행중, 이후는 미래 상태로 보인다', async () => {
      render(withCodes(<OrgOverview org={org({ stage: 3 })} />))

      const doneButton = screen.getByText('목록접수').closest('button') as HTMLElement
      const currentButton = screen.getByText('예비검토').closest('button') as HTMLElement
      const futureButton = screen.getByText('변호사 배당').closest('button') as HTMLElement

      expect(within(doneButton).getByText('✓')).toBeTruthy()
      expect(within(currentButton).getByText('진행중')).toBeTruthy()
      expect(within(futureButton).getByText('-')).toBeTruthy()
      expect(futureButton).toBeDisabled()
      expect(doneButton).toBeDisabled()
      expect(currentButton).not.toBeDisabled()

      await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
    })

    it('현재 단계를 클릭하면 확인 후 다음 단계로 진행단계 변경 API를 부른다', async () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true)
      const onChanged = vi.fn()

      render(withCodes(<OrgOverview org={org({ stage: 4 })} onChanged={onChanged} />))

      const currentButton = screen.getByText('변호사 배당').closest('button') as HTMLElement
      fireEvent.click(currentButton)

      expect(confirmSpy).toHaveBeenCalledWith(
        '다음 단계 "5. 법률검토(권리확인)"(으)로 진행할까요?',
      )
      await waitFor(() => expect(mocks.updateStage).toHaveBeenCalledWith(1, 5))
      expect(onChanged).toHaveBeenCalled()

      confirmSpy.mockRestore()
    })

    it('확인을 취소하면 진행단계 변경 API를 부르지 않는다', async () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false)

      render(withCodes(<OrgOverview org={org({ stage: 4 })} />))

      fireEvent.click(screen.getByText('변호사 배당').closest('button') as HTMLElement)

      expect(confirmSpy).toHaveBeenCalled()
      expect(mocks.updateStage).not.toHaveBeenCalled()

      confirmSpy.mockRestore()
    })

    it('현재 단계가 아닌 다른 단계를 클릭해도 아무 일도 일어나지 않는다', async () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true)

      render(withCodes(<OrgOverview org={org({ stage: 4 })} />))

      fireEvent.click(screen.getByText('예비검토').closest('button') as HTMLElement)
      fireEvent.click(screen.getByText('RE:확인').closest('button') as HTMLElement)

      expect(confirmSpy).not.toHaveBeenCalled()
      expect(mocks.updateStage).not.toHaveBeenCalled()

      confirmSpy.mockRestore()
    })

    // 요구사항 [11]로 13단계(보고서 제출 완료)가 생겨 마지막 단계가 13으로 내려갔다.
    // 12단계는 이제 마지막이 아니라서 다음 단계로 넘어갈 수 있다.
    it('마지막 단계(13)가 현재 단계이면 클릭해도 아무 일도 일어나지 않는다', async () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true)

      render(withCodes(<OrgOverview org={org({ stage: 13 })} />))

      const lastButton = screen.getByText('보고서 제출 완료').closest('button') as HTMLElement
      expect(lastButton).toBeDisabled()
      fireEvent.click(lastButton)

      expect(confirmSpy).not.toHaveBeenCalled()
      expect(mocks.updateStage).not.toHaveBeenCalled()

      confirmSpy.mockRestore()
    })
  })

  describe('진행단계 변경 모달', () => {
    it('버튼을 누르면 12개 단계 목록이 열리고 하나를 고르면 확인 후 해당 단계로 바꾼다', async () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true)
      const onChanged = vi.fn()

      render(withCodes(<OrgOverview org={org({ stage: 2 })} onChanged={onChanged} />))

      fireEvent.click(screen.getByRole('button', { name: '진행단계 변경' }))

      const modalHeading = screen.getByRole('heading', { name: '진행단계 변경' })
      const modal = modalHeading.closest('div')!.parentElement as HTMLElement
      expect(within(modal).getByText('보고서 작성')).toBeTruthy()

      fireEvent.click(within(modal).getByText('법률검토(권리확인)'))

      expect(confirmSpy).toHaveBeenCalledWith(
        '"5. 법률검토(권리확인)"(으)로 진행단계를 변경할까요?',
      )
      await waitFor(() => expect(mocks.updateStage).toHaveBeenCalledWith(1, 5))
      expect(onChanged).toHaveBeenCalled()

      confirmSpy.mockRestore()
    })

    it('닫기를 누르면 모달이 사라진다', async () => {
      render(withCodes(<OrgOverview org={org({ stage: 2 })} />))

      fireEvent.click(screen.getByRole('button', { name: '진행단계 변경' }))
      expect(screen.getByRole('heading', { name: '진행단계 변경' })).toBeTruthy()

      fireEvent.click(screen.getByRole('button', { name: '닫기' }))

      expect(screen.queryByRole('heading', { name: '진행단계 변경' })).toBeNull()
    })
  })
})
