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

const mocks = vi.hoisted(() => ({
  updateAssignments: vi.fn(),
  provisionChannels: vi.fn(),
  getContacts: vi.fn(),
}))

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

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',
    orgName: '국제방송교류재단',
    status: 'INFO_PENDING',
    applicant: null,
    mj: null,
    lawyer: null,
    lawyerAssignedDate: null,
    channelIdMj: null,
    channelIdLaw: null,
    ...overrides,
  }
}

beforeEach(() => {
  mocks.updateAssignments.mockReset()
  mocks.provisionChannels.mockReset()
  mocks.getContacts.mockReset().mockResolvedValue([])
})

describe('OrgDetail', () => {
  it('정보 대기 상태면 채널 생성 버튼이 비활성이다', () => {
    render(<OrgDetail org={org()} onChanged={() => {}} />)

    expect(screen.getByRole('button', { name: '채널 생성' })).toBeDisabled()
  })

  it('생성 가능 상태면 채널 생성 버튼이 활성이다', () => {
    render(<OrgDetail org={org({ status: 'READY' })} onChanged={() => {}} />)

    expect(screen.getByRole('button', { name: '채널 생성' })).toBeEnabled()
  })

  it('배정되지 않은 역할은 미지정으로 표시된다', () => {
    render(<OrgDetail org={org()} onChanged={() => {}} />)

    expect(screen.getAllByText('미지정')).toHaveLength(3)
  })

  it('배정된 담당자는 요약(이름·부서·연락처)으로 표시된다', () => {
    render(
      <OrgDetail
        org={org({
          applicant: contact({
            name: '송민지',
            deptName: '데이터정보화팀',
            phone: '02-3475-5434',
          }),
        })}
        onChanged={() => {}}
      />,
    )

    expect(screen.getByText('송민지 · 데이터정보화팀 · 02-3475-5434')).toBeTruthy()
  })

  it('선택 버튼을 누르면 담당자 선택 모달이 뜬다', async () => {
    mocks.getContacts.mockResolvedValue([contact({ id: 9, name: '송민지' })])

    render(<OrgDetail org={org()} onChanged={() => {}} />)

    fireEvent.click(screen.getByRole('button', { name: '신청기관 담당자 선택' }))

    expect(screen.getByText('신청기관 담당자 선택')).toBeTruthy()
    await waitFor(() => expect(screen.getByText('송민지')).toBeTruthy())
    expect(mocks.getContacts).toHaveBeenCalledWith('APPLICANT')
  })

  it('모달에서 담당자를 고르면 updateAssignments가 그 담당자ID로 호출된다', async () => {
    mocks.getContacts.mockResolvedValue([contact({ id: 9, name: '송민지' })])
    mocks.updateAssignments.mockResolvedValue(org({ status: 'READY' }))
    const onChanged = vi.fn()

    render(<OrgDetail org={org()} onChanged={onChanged} />)

    fireEvent.click(screen.getByRole('button', { name: '신청기관 담당자 선택' }))
    await waitFor(() => expect(screen.getByText('송민지')).toBeTruthy())
    fireEvent.click(screen.getByRole('button', { name: '선택' }))

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

  it('해제 버튼을 누르면 해당 역할만 null로 배정을 해제한다', async () => {
    mocks.updateAssignments.mockResolvedValue(org())
    const onChanged = vi.fn()

    render(
      <OrgDetail
        org={org({
          status: 'READY',
          applicant: contact({ id: 5, name: '송민지' }),
          mj: contact({ id: 6, category: 'MJ', name: '김문정' }),
        })}
        onChanged={onChanged}
      />,
    )

    fireEvent.click(screen.getByRole('button', { name: '신청기관 담당자 해제' }))

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

  it('배정이 없는 역할에는 해제 버튼이 없다', () => {
    render(<OrgDetail org={org()} onChanged={() => {}} />)

    expect(screen.queryByRole('button', { name: '신청기관 담당자 해제' })).toBeNull()
  })

  it('담당 변호사 배정일을 바꾸면 updateAssignments가 호출된다', async () => {
    mocks.updateAssignments.mockResolvedValue(org())
    const onChanged = vi.fn()

    render(
      <OrgDetail
        org={org({ lawyer: contact({ id: 7, category: 'LAWYER', name: '이변호' }) })}
        onChanged={onChanged}
      />,
    )

    fireEvent.change(screen.getByLabelText('배정일'), { target: { value: '2026-07-21' } })

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

  it('채널 생성은 확인 다이얼로그에 만들어질 채널 2개를 보여준다', () => {
    render(<OrgDetail org={org({ status: 'READY' })} onChanged={() => {}} />)

    fireEvent.click(screen.getByRole('button', { name: '채널 생성' }))

    expect(screen.getByText('001_국제방송교류재단 (문정원)')).toBeTruthy()
    expect(screen.getByText('001_국제방송교류재단 (법률검토)')).toBeTruthy()
    expect(mocks.provisionChannels).not.toHaveBeenCalled()
  })

  it('확인을 눌러야 채널 생성 API가 호출된다', async () => {
    mocks.provisionChannels.mockResolvedValue({ mj: 'CREATED', law: 'CREATED', message: '' })

    render(<OrgDetail org={org({ status: 'READY' })} onChanged={() => {}} />)

    fireEvent.click(screen.getByRole('button', { name: '채널 생성' }))
    fireEvent.click(screen.getByRole('button', { name: '생성' }))

    await waitFor(() => expect(mocks.provisionChannels).toHaveBeenCalledWith(1))
  })

  it('부분 실패하면 재시도 안내를 보여준다', async () => {
    mocks.provisionChannels.mockResolvedValue({
      mj: 'CREATED',
      law: 'FAILED',
      message: '[법률검토] 서버 오류',
    })

    render(<OrgDetail org={org({ status: 'READY' })} onChanged={() => {}} />)

    fireEvent.click(screen.getByRole('button', { name: '채널 생성' }))
    fireEvent.click(screen.getByRole('button', { name: '생성' }))

    await waitFor(() => {
      expect(screen.getByText(/법률검토 채널 생성에 실패/)).toBeTruthy()
      expect(screen.getByText(/다시 눌러도 안전/)).toBeTruthy()
    })
  })

  it('양쪽 다 실패하면 문정원 채널이 살아있다는 잘못된 안내를 보여주지 않는다', async () => {
    mocks.provisionChannels.mockResolvedValue({
      mj: 'FAILED',
      law: 'FAILED',
      message: '[문정원] 서버 오류\n[법률검토] 서버 오류',
    })

    render(<OrgDetail org={org({ status: 'READY' })} onChanged={() => {}} />)

    fireEvent.click(screen.getByRole('button', { name: '채널 생성' }))
    fireEvent.click(screen.getByRole('button', { name: '생성' }))

    await waitFor(() => {
      expect(screen.getByText(/문정원 채널.*법률검토 채널.*모두 생성에 실패/)).toBeTruthy()
    })
    expect(screen.queryByText(/문정원 채널은 그대로 남아 있으니/)).toBeNull()
  })

  it('문정원만 실패하면 법률검토 채널이 살아있다는 안내를 보여준다', async () => {
    mocks.provisionChannels.mockResolvedValue({
      mj: 'FAILED',
      law: 'CREATED',
      message: '[문정원] 서버 오류',
    })

    render(<OrgDetail org={org({ status: 'READY' })} onChanged={() => {}} />)

    fireEvent.click(screen.getByRole('button', { name: '채널 생성' }))
    fireEvent.click(screen.getByRole('button', { name: '생성' }))

    await waitFor(() => {
      expect(screen.getByText(/문정원 채널 생성에 실패/)).toBeTruthy()
      expect(screen.getByText(/법률검토 채널은 그대로 남아 있으니/)).toBeTruthy()
    })
  })

  it('채널 생성이 진행 중이면 생성 버튼과 채널 생성 버튼이 비활성화된다', async () => {
    let resolvePromise: (value: { mj: string; law: string; message: string }) => void = () => {}
    mocks.provisionChannels.mockReturnValue(
      new Promise((resolve) => {
        resolvePromise = resolve
      }),
    )

    render(<OrgDetail org={org({ status: 'READY' })} onChanged={() => {}} />)

    fireEvent.click(screen.getByRole('button', { name: '채널 생성' }))
    fireEvent.click(screen.getByRole('button', { name: '생성' }))

    await waitFor(() => {
      expect(screen.getByRole('button', { name: '생성' })).toBeDisabled()
    })
    expect(screen.getByRole('button', { name: '채널 생성' })).toBeDisabled()

    resolvePromise({ mj: 'CREATED', law: 'CREATED', message: '' })

    await waitFor(() => {
      expect(screen.getByRole('button', { name: '채널 생성' })).toBeEnabled()
    })
  })

  it('reload로 org 객체가 새로 만들어져도 같은 기관이면 결과가 지워지지 않는다', async () => {
    mocks.provisionChannels.mockResolvedValue({
      mj: 'CREATED',
      law: 'FAILED',
      message: '[법률검토] 서버 오류',
    })

    const { rerender } = render(<OrgDetail org={org({ status: 'READY' })} onChanged={() => {}} />)

    fireEvent.click(screen.getByRole('button', { name: '채널 생성' }))
    fireEvent.click(screen.getByRole('button', { name: '생성' }))

    await waitFor(() => {
      expect(screen.getByText(/법률검토 채널 생성에 실패/)).toBeTruthy()
    })

    // App.reload()가 만드는, id는 같지만 identity가 다른 새 객체를 흉내낸다.
    rerender(
      <OrgDetail
        org={org({ status: 'PARTIAL', channelIdMj: 'chan-mj' })}
        onChanged={() => {}}
      />,
    )

    expect(screen.getByText(/법률검토 채널 생성에 실패/)).toBeTruthy()
  })

  it('다른 기관으로 전환하면 이전 결과가 초기화된다', async () => {
    mocks.provisionChannels.mockResolvedValue({
      mj: 'CREATED',
      law: 'FAILED',
      message: '[법률검토] 서버 오류',
    })

    const { rerender } = render(<OrgDetail org={org({ status: 'READY' })} onChanged={() => {}} />)

    fireEvent.click(screen.getByRole('button', { name: '채널 생성' }))
    fireEvent.click(screen.getByRole('button', { name: '생성' }))

    await waitFor(() => {
      expect(screen.getByText(/법률검토 채널 생성에 실패/)).toBeTruthy()
    })

    rerender(<OrgDetail org={org({ id: 2, status: 'READY' })} onChanged={() => {}} />)

    expect(screen.queryByText(/법률검토 채널 생성에 실패/)).toBeNull()
  })
})
