import React from 'react'
import { render, fireEvent, wait } from '@testing-library/react'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { Player } from '@/scripts/types'
import Players from '@/views/user/Players'

jest.mock('@/scripts/net')

const fixture: Readonly<Player> = Object.freeze<Player>({
  pid: 1,
  name: 'kumiko',
  tid_skin: 1,
  tid_cape: 2,
})

beforeEach(() => {
  const container = document.createElement('div')
  container.id = 'previewer'
  document.body.appendChild(container)

  blessing.extra = {
    rule: 'please follow the rule',
    length: 'greater than 3',
    score: 1000,
    cost: 50,
  }
})

afterEach(() => {
  document.querySelector('#previewer')!.remove()
})

test('loading indicator', () => {
  fetch.get.mockResolvedValue({ data: [] })
  const { queryByTitle } = render(<Players />)
  expect(queryByTitle('Loading...')).toBeInTheDocument()
})

test('search players', async () => {
  const fixture2: Player = { pid: 2, name: 'reina', tid_skin: 3, tid_cape: 4 }
  fetch.get.mockResolvedValue({ data: [fixture, fixture2] })

  const { getByPlaceholderText, queryByText } = render(<Players />)
  await wait()

  fireEvent.input(getByPlaceholderText(t('user.typeToSearch')), {
    target: { value: 'k' },
  })

  expect(queryByText(fixture.name)).toBeInTheDocument()
  expect(queryByText(fixture2.name)).not.toBeInTheDocument()
})

describe('select player automatically', () => {
  it('only one player', async () => {
    fetch.get.mockResolvedValue({ data: [fixture] })
    render(<Players />)
    await wait()

    expect(fetch.get).toBeCalledWith(`/skinlib/info/${fixture.tid_skin}`)
    expect(fetch.get).toBeCalledWith(`/skinlib/info/${fixture.tid_cape}`)
  })

  it('more players', async () => {
    const fixture2: Player = { pid: 2, name: 'reina', tid_skin: 3, tid_cape: 4 }
    fetch.get.mockResolvedValue({ data: [fixture, fixture2] })
    render(<Players />)
    await wait()

    expect(fetch.get).not.toBeCalledWith(`/skinlib/info/${fixture.tid_skin}`)
    expect(fetch.get).not.toBeCalledWith(`/skinlib/info/${fixture.tid_cape}`)
  })
})

describe('2d preview', () => {
  it('skin and cape', async () => {
    fetch.get
      .mockResolvedValueOnce({ data: [fixture] })
      .mockResolvedValueOnce({ data: { hash: 'a', type: 'steve' } })
      .mockResolvedValueOnce({ data: { hash: 'b', type: 'cape' } })

    const { getByAltText, getByText } = render(<Players />)
    await wait()

    fireEvent.click(getByText(t('user.switch2dPreview')))

    expect(getByAltText(t('general.skin'))).toHaveAttribute(
      'src',
      `${blessing.base_url}/textures/a`,
    )
    expect(getByAltText(t('general.cape'))).toHaveAttribute(
      'src',
      `${blessing.base_url}/textures/b`,
    )
  })

  it('skin only', async () => {
    fetch.get
      .mockResolvedValueOnce({ data: [{ ...fixture, tid_cape: 0 }] })
      .mockResolvedValueOnce({ data: { hash: 'a', type: 'steve' } })

    const { getByAltText, queryByAltText, getByText, queryByText } = render(
      <Players />,
    )
    await wait()

    fireEvent.click(getByText(t('user.switch2dPreview')))

    expect(getByAltText(t('general.skin'))).toHaveAttribute(
      'src',
      `${blessing.base_url}/textures/a`,
    )
    expect(queryByAltText(t('general.cape'))).not.toBeInTheDocument()
    expect(queryByText(t('user.player.texture-empty'))).toBeInTheDocument()
  })

  it('cape only', async () => {
    fetch.get
      .mockResolvedValueOnce({ data: [{ ...fixture, tid_skin: 0 }] })
      .mockResolvedValueOnce({ data: { hash: 'a', type: 'cape' } })

    const { getByAltText, queryByAltText, getByText, queryByText } = render(
      <Players />,
    )
    await wait()

    fireEvent.click(getByText(t('user.switch2dPreview')))

    expect(getByAltText(t('general.cape'))).toHaveAttribute(
      'src',
      `${blessing.base_url}/textures/a`,
    )
    expect(queryByAltText(t('general.skin'))).not.toBeInTheDocument()
    expect(queryByText(t('user.player.texture-empty'))).toBeInTheDocument()
  })
})

describe('create player', () => {
  beforeEach(() => {
    fetch.get.mockResolvedValue({ data: [] })
  })

  it('alert if score is enough', async () => {
    const { getByRole, getByText, queryByText } = render(<Players />)
    await wait()

    fireEvent.click(getByText(t('user.player.add-player')))

    expect(
      queryByText(`${t('user.cur-score')} ${blessing.extra.score}`),
    ).toBeInTheDocument()
    expect(getByRole('alert')).toHaveClass('alert-success')
  })

  it('alert if lack of score', async () => {
    blessing.extra.score = 0
    const { getByRole, getByText, queryByText } = render(<Players />)
    await wait()

    fireEvent.click(getByText(t('user.player.add-player')))

    expect(
      queryByText(`${t('user.cur-score')} ${blessing.extra.score}`),
    ).toBeInTheDocument()
    expect(getByRole('alert')).toHaveClass('alert-danger')
  })

  it('succeeded', async () => {
    fetch.post.mockResolvedValue({ code: 0, message: 'success', data: fixture })

    const { getByText, getByLabelText, getByRole, queryByText } = render(
      <Players />,
    )
    await wait()

    fireEvent.click(getByText(t('user.player.add-player')))
    fireEvent.input(getByLabelText(t('general.player.player-name')), {
      target: { value: fixture.name },
    })
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).toBeCalledWith('/user/player/add', {
      name: fixture.name,
    })
    expect(queryByText('success')).toBeInTheDocument()
    expect(getByRole('status')).toHaveClass('alert-success')
    expect(queryByText(fixture.pid.toString())).toBeInTheDocument()
    expect(queryByText(fixture.name)).toBeInTheDocument()
  })

  it('failed', async () => {
    fetch.post.mockResolvedValue({ code: 1, message: 'failed' })

    const { getByText, getByLabelText, getByRole, queryByText } = render(
      <Players />,
    )
    await wait()

    fireEvent.click(getByText(t('user.player.add-player')))
    fireEvent.input(getByLabelText(t('general.player.player-name')), {
      target: { value: fixture.name },
    })
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).toBeCalledWith('/user/player/add', {
      name: fixture.name,
    })
    expect(queryByText('failed')).toBeInTheDocument()
    expect(getByRole('alert')).toHaveClass('alert-danger')
    expect(queryByText(fixture.name)).not.toBeInTheDocument()
  })

  it('cancelled', async () => {
    const { getByText, getByLabelText, queryByText } = render(<Players />)
    await wait()

    fireEvent.click(getByText(t('user.player.add-player')))
    fireEvent.input(getByLabelText(t('general.player.player-name')), {
      target: { value: fixture.name },
    })
    fireEvent.click(getByText(t('general.cancel')))
    await wait()

    expect(fetch.post).not.toBeCalled()
    expect(queryByText(fixture.name)).not.toBeInTheDocument()
  })

  it('clear form on close', async () => {
    const { getByText, getByLabelText } = render(<Players />)
    await wait()

    fireEvent.click(getByText(t('user.player.add-player')))
    fireEvent.input(getByLabelText(t('general.player.player-name')), {
      target: { value: fixture.name },
    })
    fireEvent.click(getByText(t('general.cancel')))
    fireEvent.click(getByText(t('user.player.add-player')))

    expect(getByLabelText(t('general.player.player-name'))).toHaveValue('')
  })
})

describe('edit player name', () => {
  beforeEach(() => {
    fetch.get.mockResolvedValue({ data: [fixture] })
  })

  it('succeeded', async () => {
    fetch.post.mockResolvedValue({ code: 0, message: 'success' })

    const {
      getByText,
      getByTitle,
      getByDisplayValue,
      getByRole,
      queryByText,
    } = render(<Players />)
    await wait()

    fireEvent.click(getByTitle(t('user.player.edit-pname')))
    fireEvent.input(getByDisplayValue(fixture.name), {
      target: { value: 'reina' },
    })
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).toBeCalledWith(`/user/player/rename/${fixture.pid}`, {
      name: 'reina',
    })
    expect(queryByText('success')).toBeInTheDocument()
    expect(getByRole('status')).toHaveClass('alert-success')
    expect(queryByText('reina')).toBeInTheDocument()
  })

  it('empty name', async () => {
    const { getByText, getByTitle, getByDisplayValue, queryByText } = render(
      <Players />,
    )
    await wait()

    fireEvent.click(getByTitle(t('user.player.edit-pname')))
    fireEvent.input(getByDisplayValue(fixture.name), {
      target: { value: '' },
    })
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).not.toBeCalled()
    expect(queryByText(t('user.emptyPlayerName'))).toBeInTheDocument()

    fireEvent.click(getByText(t('general.cancel')))
  })

  it('failed', async () => {
    fetch.post.mockResolvedValue({ code: 1, message: 'failed' })

    const {
      getByText,
      getByTitle,
      getByDisplayValue,
      getByRole,
      queryByText,
    } = render(<Players />)
    await wait()

    fireEvent.click(getByTitle(t('user.player.edit-pname')))
    fireEvent.input(getByDisplayValue(fixture.name), {
      target: { value: 'reina' },
    })
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).toBeCalledWith(`/user/player/rename/${fixture.pid}`, {
      name: 'reina',
    })
    expect(queryByText('failed')).toBeInTheDocument()
    expect(getByRole('alert')).toHaveClass('alert-danger')
    expect(queryByText(fixture.name)).toBeInTheDocument()
  })

  it('cancelled', async () => {
    const { getByText, getByTitle, getByDisplayValue, queryByText } = render(
      <Players />,
    )
    await wait()

    fireEvent.click(getByTitle(t('user.player.edit-pname')))
    fireEvent.input(getByDisplayValue(fixture.name), {
      target: { value: 'reina' },
    })
    fireEvent.click(getByText(t('general.cancel')))
    await wait()

    expect(fetch.post).not.toBeCalled()
    expect(queryByText(fixture.name)).toBeInTheDocument()
  })
})

describe('reset texture', () => {
  beforeEach(() => {
    fetch.get.mockResolvedValue({ data: [fixture] })
  })

  it('clear skin and cape', async () => {
    fetch.post.mockResolvedValue({ code: 0, message: 'success' })

    const { getByText, getByRole, getByLabelText, queryByText } = render(
      <Players />,
    )
    await wait()

    fireEvent.click(getByText(t('user.player.delete-texture')))
    fireEvent.click(getByLabelText(t('general.skin')))
    fireEvent.click(getByLabelText(t('general.cape')))
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).toBeCalledWith(
      `/user/player/texture/clear/${fixture.pid}`,
      {
        type: ['skin', 'cape'],
      },
    )
    expect(queryByText('success')).toBeInTheDocument()
    expect(getByRole('status')).toHaveClass('alert-success')
  })

  it('clear skin', async () => {
    fetch.post.mockResolvedValue({ code: 0, message: 'success' })

    const { getByText, getByRole, getByLabelText, queryByText } = render(
      <Players />,
    )
    await wait()

    fireEvent.click(getByText(t('user.player.delete-texture')))
    fireEvent.click(getByLabelText(t('general.skin')))
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).toBeCalledWith(
      `/user/player/texture/clear/${fixture.pid}`,
      {
        type: ['skin'],
      },
    )
    expect(queryByText('success')).toBeInTheDocument()
    expect(getByRole('status')).toHaveClass('alert-success')
  })

  it('clear cape', async () => {
    fetch.post.mockResolvedValue({ code: 0, message: 'success' })

    const { getByText, getByRole, getByLabelText, queryByText } = render(
      <Players />,
    )
    await wait()

    fireEvent.click(getByText(t('user.player.delete-texture')))
    fireEvent.click(getByLabelText(t('general.cape')))
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).toBeCalledWith(
      `/user/player/texture/clear/${fixture.pid}`,
      {
        type: ['cape'],
      },
    )
    expect(queryByText('success')).toBeInTheDocument()
    expect(getByRole('status')).toHaveClass('alert-success')
  })

  it('select nothing', async () => {
    const { getByText, getByRole, queryByText } = render(<Players />)
    await wait()

    fireEvent.click(getByText(t('user.player.delete-texture')))
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).not.toBeCalled()
    expect(queryByText(t('user.noClearChoice'))).toBeInTheDocument()
    expect(getByRole('alert')).toHaveClass('alert-warning')
  })

  it('failed', async () => {
    fetch.post.mockResolvedValue({ code: 1, message: 'failed' })

    const { getByText, getByRole, getByLabelText, queryByText } = render(
      <Players />,
    )
    await wait()

    fireEvent.click(getByText(t('user.player.delete-texture')))
    fireEvent.click(getByLabelText(t('general.skin')))
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).toBeCalledWith(
      `/user/player/texture/clear/${fixture.pid}`,
      {
        type: ['skin'],
      },
    )
    expect(queryByText('failed')).toBeInTheDocument()
    expect(getByRole('alert')).toHaveClass('alert-danger')
  })

  it('cancelled', async () => {
    const { getByText, getByLabelText } = render(<Players />)
    await wait()

    fireEvent.click(getByText(t('user.player.delete-texture')))
    fireEvent.click(getByLabelText(t('general.skin')))
    fireEvent.click(getByText(t('general.cancel')))
    await wait()

    expect(fetch.post).not.toBeCalled()
  })
})

describe('delete player', () => {
  beforeEach(() => {
    fetch.get.mockResolvedValue({ data: [fixture] })
  })

  it('succeeded', async () => {
    fetch.post.mockResolvedValue({ code: 0, message: 'success' })

    const { getByText, getByRole, queryByText } = render(<Players />)
    await wait()

    fireEvent.click(getByText(t('user.player.delete-player')))
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).toBeCalledWith(`/user/player/delete/${fixture.pid}`)
    expect(getByText('success')).toBeInTheDocument()
    expect(getByRole('status')).toHaveClass('alert-success')
    expect(queryByText(fixture.name)).not.toBeInTheDocument()
  })

  it('failed', async () => {
    fetch.post.mockResolvedValue({ code: 1, message: 'failed' })

    const { getByText, getByRole, queryByText } = render(<Players />)
    await wait()

    fireEvent.click(getByText(t('user.player.delete-player')))
    fireEvent.click(getByText(t('general.confirm')))
    await wait()

    expect(fetch.post).toBeCalledWith(`/user/player/delete/${fixture.pid}`)
    expect(getByText('failed')).toBeInTheDocument()
    expect(getByRole('alert')).toHaveClass('alert-danger')
    expect(queryByText(fixture.name)).toBeInTheDocument()
  })

  it('cancelled', async () => {
    const { getByText, queryByText } = render(<Players />)
    await wait()

    fireEvent.click(getByText(t('user.player.delete-player')))
    fireEvent.click(getByText(t('general.cancel')))
    await wait()

    expect(fetch.post).not.toBeCalled()
    expect(queryByText(fixture.name)).toBeInTheDocument()
  })
})
