import { fireEvent, screen, waitFor, within } from '@testing-library/react'
import { assert, expect } from 'chai'
import fetchMock from 'fetch-mock'
import TagsList from '../../../../../../frontend/js/features/project-list/components/sidebar/tags-list'
import { projectsData } from '../../fixtures/projects-data'
import { renderWithProjectListContext } from '../../helpers/render-with-context'

describe('<TagsList />', function () {
  beforeEach(async function () {
    global.localStorage.clear()
    window.metaAttributesCache = new Map()
    window.metaAttributesCache.set('ol-tags', [
      {
        _id: 'abc123def456',
        name: 'Tag 1',
        project_ids: [projectsData[0].id],
      },
      {
        _id: 'bcd234efg567',
        name: 'Another tag',
        project_ids: [projectsData[0].id, projectsData[1].id],
      },
    ])

    fetchMock.post('/tag', {
      _id: 'eee888eee888',
      name: 'New Tag',
      project_ids: [],
    })
    fetchMock.post('express:/tag/:tagId/projects', 200)
    fetchMock.post('express:/tag/:tagId/rename', 200)
    fetchMock.delete('express:/tag/:tagId', 200)

    renderWithProjectListContext(<TagsList />)

    await fetchMock.flush(true)
    await waitFor(() => expect(fetchMock.called('/api/project')))
  })

  afterEach(function () {
    fetchMock.reset()
  })

  it('displays the tags list', function () {
    screen.getByRole('heading', {
      name: 'Tags/Folders',
    })
    screen.getByRole('button', {
      name: 'New Folder',
    })
    screen.getByRole('button', {
      name: 'Tag 1 (1)',
    })
    screen.getByRole('button', {
      name: 'Another tag (2)',
    })
    screen.getByRole('button', {
      name: 'Uncategorized (3)',
    })
  })

  it('selects the tag when clicked', async function () {
    const tag1Button = screen.getByText('Tag 1')
    assert.isFalse(tag1Button.closest('li')?.classList.contains('active'))

    await fireEvent.click(tag1Button)
    assert.isTrue(tag1Button.closest('li')?.classList.contains('active'))
  })

  it('selects uncategorized when clicked', function () {
    const uncategorizedButton = screen.getByText('Uncategorized')
    assert.isFalse(
      uncategorizedButton.closest('li')?.classList.contains('active')
    )

    fireEvent.click(uncategorizedButton)
    assert.isTrue(
      uncategorizedButton.closest('li')?.classList.contains('active')
    )
  })

  describe('create modal', function () {
    beforeEach(async function () {
      const newTagButton = screen.getByRole('button', {
        name: 'New Folder',
      })

      await fireEvent.click(newTagButton)
    })

    it('modal is open', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      within(modal).getByRole('heading', { name: 'Create New Folder' })
    })

    it('click on cancel closes the modal', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const cancelButton = within(modal).getByRole('button', { name: 'Cancel' })

      await fireEvent.click(cancelButton)
      expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
    })

    it('Create button is disabled when input is empty', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const createButton = within(modal).getByRole('button', { name: 'Create' })

      expect(createButton.hasAttribute('disabled')).to.be.true
    })

    it('Create button is disabled with error message when tag name is too long', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const input = within(modal).getByRole('textbox')
      fireEvent.change(input, {
        target: {
          value: 'This is a very very very very very very long tag name',
        },
      })

      const createButton = within(modal).getByRole('button', { name: 'Create' })
      expect(createButton.hasAttribute('disabled')).to.be.true
      screen.getByText('Tag name cannot exceed 50 characters')
    })

    it('Create button is disabled with error message when tag name is already used', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const input = within(modal).getByRole('textbox')
      fireEvent.change(input, {
        target: {
          value: 'Tag 1',
        },
      })

      const createButton = within(modal).getByRole('button', { name: 'Create' })
      expect(createButton.hasAttribute('disabled')).to.be.true
      screen.getByText('Tag "Tag 1" already exists')
    })

    it('filling the input and clicking Create sends a request', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const input = within(modal).getByRole('textbox')
      fireEvent.change(input, { target: { value: 'New Tag' } })

      const createButton = within(modal).getByRole('button', { name: 'Create' })
      expect(createButton.hasAttribute('disabled')).to.be.false

      await fireEvent.click(createButton)

      await waitFor(() => expect(fetchMock.called(`/tag`)).to.be.true)

      expect(screen.queryByRole('dialog', { hidden: false })).to.be.null

      screen.getByRole('button', {
        name: 'New Tag (0)',
      })
    })
  })

  describe('rename modal', function () {
    beforeEach(async function () {
      const tag1Button = screen.getByText('Tag 1')

      const renameButton = within(
        tag1Button.closest('li') as HTMLElement
      ).getByRole('button', {
        name: 'Rename',
      })

      await fireEvent.click(renameButton)
    })

    it('modal is open', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      within(modal).getByRole('heading', { name: 'Rename Folder' })
    })

    it('click on cancel closes the modal', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const cancelButton = within(modal).getByRole('button', { name: 'Cancel' })

      await fireEvent.click(cancelButton)
      expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
    })

    it('Rename button is disabled when input is empty', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const renameButton = within(modal).getByRole('button', { name: 'Rename' })

      expect(renameButton.hasAttribute('disabled')).to.be.true
    })

    it('Rename button is disabled with error message when tag name is too long', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const input = within(modal).getByRole('textbox')
      fireEvent.change(input, {
        target: {
          value: 'This is a very very very very very very long tag name',
        },
      })

      const createButton = within(modal).getByRole('button', { name: 'Rename' })
      expect(createButton.hasAttribute('disabled')).to.be.true
      screen.getByText('Tag name cannot exceed 50 characters')
    })

    it('Rename button is disabled with no error message when tag name is unchanged', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const createButton = within(modal).getByRole('button', { name: 'Rename' })
      expect(createButton.hasAttribute('disabled')).to.be.true
    })

    it('Rename button is disabled with error message when tag name is already used', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const input = within(modal).getByRole('textbox')
      fireEvent.change(input, {
        target: {
          value: 'Another tag',
        },
      })

      const createButton = within(modal).getByRole('button', { name: 'Rename' })
      expect(createButton.hasAttribute('disabled')).to.be.true
      screen.getByText('Tag "Another tag" already exists')
    })

    it('filling the input and clicking Rename sends a request', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const input = within(modal).getByRole('textbox')
      fireEvent.change(input, { target: { value: 'New Tag Name' } })

      const renameButton = within(modal).getByRole('button', { name: 'Rename' })
      expect(renameButton.hasAttribute('disabled')).to.be.false

      await fireEvent.click(renameButton)

      await waitFor(() => expect(fetchMock.called(`/tag/abc123def456/rename`)))

      expect(screen.queryByRole('dialog', { hidden: false })).to.be.null

      screen.getByRole('button', {
        name: 'New Tag Name (1)',
      })
    })
  })

  describe('delete modal', function () {
    beforeEach(async function () {
      const tag1Button = screen.getByText('Another tag')

      const deleteButton = within(
        tag1Button.closest('li') as HTMLElement
      ).getByRole('button', {
        name: 'Delete',
      })

      await fireEvent.click(deleteButton)
    })

    it('modal is open', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      within(modal).getByRole('heading', { name: 'Delete Folder' })
    })

    it('click on Cancel closes the modal', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const cancelButton = within(modal).getByRole('button', { name: 'Cancel' })

      await fireEvent.click(cancelButton)
      expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
    })

    it('clicking Delete sends a request', async function () {
      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const deleteButton = within(modal).getByRole('button', { name: 'Delete' })
      await fireEvent.click(deleteButton)

      await waitFor(() => expect(fetchMock.called(`/tag/bcd234efg567`)))

      expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
      expect(
        screen.queryByRole('button', {
          name: 'Another Tag (2)',
        })
      ).to.be.null
    })

    it('a failed request displays an error message', async function () {
      fetchMock.delete('express:/tag/:tagId', 500, { overwriteRoutes: true })

      const modal = screen.getAllByRole('dialog', { hidden: false })[0]
      const deleteButton = within(modal).getByRole('button', { name: 'Delete' })
      await fireEvent.click(deleteButton)

      await waitFor(() => expect(fetchMock.called(`/tag/bcd234efg567`)))

      within(modal).getByText('Sorry, something went wrong')
    })
  })
})
