describe('Editorial Workflow', () => {
  const workflowStatus = { draft: 'Drafts', review: 'In Review', ready: 'Ready' }
  const editorStatus = { draft: 'Draft', review: 'In review', ready: 'Ready' }
  const entry1 = { title: 'first title', body: 'first body' }
  const entry2 = { title: 'second title', body: 'second body' }
  const entry3 = { title: 'third title', body: 'third body' }
  const setting1 = { limit: 10, author: 'John Doe' }
  const setting2 = { name: 'Andrew Wommack', description: 'A Gospel Teacher' }
  const notifications = {
    saved: 'Entry saved',
    published: 'Entry published',
    updated: 'Entry status updated',
    deletedUnpublished: 'Unpublished changes deleted',
    error: { missingField: 'Oops, you\'ve missed a required field. Please complete before saving.' }
  }

  describe('Test Backend', () => {
    function login() {
      cy.viewport(1200, 1200)
      cy.visit('/')
      cy.contains('button', 'Login').click()
    }

    function createPost({ title, body }) {
      cy.contains('a', 'New Post').click()
      cy.get('input').first().type(title)
      cy.get('[data-slate-editor]').click().type(body)
      cy.get('input').first().click()
      cy.contains('button', 'Save').click()
      assertNotification(notifications.saved)
    }

    function validateObjectFields({ limit, author }) {
      cy.get('a[href^="#/collections/settings"]').click()
      cy.get('a[href^="#/collections/settings/entries/general"]').click()
      cy.get('input[type=number]').type(limit);
      cy.contains('button', 'Save').click()
      assertNotification(notifications.error.missingField)
      cy.contains('label', 'Default Author').click()
      cy.focused().type(author)
      cy.contains('button', 'Save').click()
      assertNotification(notifications.saved)
    }

    function validateListFields({ name, description }) {
      cy.get('a[href^="#/collections/settings"]').click()
      cy.get('a[href^="#/collections/settings/entries/authors"]').click()
      cy.contains('button', 'Add').click()
      cy.contains('button', 'Save').click()
      assertNotification(notifications.error.missingField)
      cy.get('input').eq(2).type(name)
      cy.get('[data-slate-editor]').eq(2).type(description)
      cy.contains('button', 'Save').click()
    }

    function exitEditor() {
      cy.contains('a[href^="#/collections/"]', 'Writing in').click()
    }

    function deleteEntryInEditor() {
      cy.contains('button', 'Delete').click()
      assertNotification(notifications.deletedUnpublished)
    }

    function assertEntryDeleted(entry) {
      if (Array.isArray(entry)) {
        const titles = entry.map(e => e.title)
        cy.get('a h2').each((el, idx) => {
          expect(titles).not.to.include(el.text())
        })
      } else {
        cy.get('a h2').each((el, idx) => {
          expect(entry.title).not.to.equal(el.text())
        })
      }
    }

    function createAndDeletePost(entry) {
      createPost(entry)
      deleteEntryInEditor()
    }

    function createPostAndExit(entry) {
      createPost(entry)
      exitEditor()
    }

    function createPublishedPost(entry) {
      createPost(entry)
      updateWorkflowStatusInEditor(editorStatus.ready)
      publishInEditor()
    }

    function createPublishedPostAndExit(entry) {
      createPublishedPost(entry)
      exitEditor()
    }

    function validateObjectFieldsAndExit(setting) {
      validateObjectFields(setting)
      exitEditor()
    }

    function validateListFieldsAndExit(setting) {
      validateListFields(setting)
      exitEditor()
    }

    function goToWorkflow() {
      cy.contains('a', 'Workflow').click()
    }

    function goToCollections() {
      cy.contains('a', 'Content').click()
    }

    function updateWorkflowStatus({ title }, fromColumnHeading, toColumnHeading) {
      cy.contains('h2', fromColumnHeading)
        .parent()
        .contains('a', title)
        .trigger('dragstart', {
          dataTransfer: {},
          force: true,
        })
      cy.contains('h2', toColumnHeading)
        .parent()
        .trigger('drop', {
          dataTransfer: {},
          force: true,
        })
      assertNotification(notifications.updated)
    }

    function assertWorkflowStatus({ title }, status) {
      cy.contains('h2', status)
        .parent()
        .contains('a', title)
    }

    function updateWorkflowStatusInEditor(newStatus) {
      cy.contains('[role="button"]', 'Set status').as('setStatusButton')
      cy.get('@setStatusButton').parent().within(() => {
        cy.get('@setStatusButton').click()
        cy.contains('[role="menuitem"] span', newStatus).click()
      })
      assertNotification(notifications.updated)
    }

    function assertWorkflowStatusInEditor(status) {
      cy.contains('[role="button"]', 'Set status').as('setStatusButton')
      cy.get('@setStatusButton').parent().within(() => {
        cy.get('@setStatusButton').click()
        cy.contains('[role="menuitem"] span', status).parent().within(() => {
          cy.get('svg')
        })
        cy.get('@setStatusButton').click()
      })
    }

    function publishWorkflowEntry({ title }) {
      cy.contains('h2', workflowStatus.ready).parent().within(() => {
        cy.contains('a', title).parent().within(() => {
          cy.contains('button', 'Publish new entry').click({ force: true })
        })
      })
      assertNotification(notifications.published)
    }

    function assertPublishedEntry(entry) {
      if (Array.isArray(entry)) {
        const entries = entry.reverse()
        cy.get('a h2').then(els => {
          cy.wrap(els.slice(0, entries.length)).each((el, idx) => {
            cy.wrap(el).contains(entries[idx].title)
          })
        })
      } else {
        cy.get('a h2').first().contains(entry.title)
      }
    }

    function assertNotification(message) {
      if (Array.isArray(message)) {
        const messages = message.reverse()
        cy.get('.notif__container div')
          .should('have.length.of', messages.length, )
          .each((el, idx) => {
            cy.wrap(el).contains(messages[idx]).invoke('hide')
          })
      } else {
        cy.get('.notif__container').within(() => {
          cy.contains(message).invoke('hide')
        })
      }
    }

    function assertOnCollectionsPage() {
      cy.url().should('contain', '/#/collections/posts')
      cy.contains('h2', 'Collections')
    }

    it('successfully loads', () => {
      login()
    })

    it('can create an entry', () => {
      login()
      createPostAndExit(entry1)
    })

    it('can validate object fields', () => {
      login()
      validateObjectFieldsAndExit(setting1)
    })

    it('can validate list fields', () => {
      login()
      validateListFieldsAndExit(setting2)
    })

    it('can publish an editorial workflow entry', () => {
      login()
      createPostAndExit(entry1)
      goToWorkflow()
      updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready)
      publishWorkflowEntry(entry1)
    })

    it('can change workflow status', () => {
      login()
      createPostAndExit(entry1)
      goToWorkflow()
      updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.review)
      updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.ready)
      updateWorkflowStatus(entry1, workflowStatus.ready, workflowStatus.review)
      updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.draft)
      updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready)
    })

    it('can change status on and publish multiple entries', () => {
      login()
      createPostAndExit(entry1)
      createPostAndExit(entry2)
      createPostAndExit(entry3)
      goToWorkflow()
      updateWorkflowStatus(entry3, workflowStatus.draft, workflowStatus.ready)
      updateWorkflowStatus(entry2, workflowStatus.draft, workflowStatus.ready)
      updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready)
      publishWorkflowEntry(entry3)
      publishWorkflowEntry(entry2)
      publishWorkflowEntry(entry1)
      goToCollections()
      assertPublishedEntry([entry3, entry2, entry1])
    })

    it('can delete an entry', () => {
      login()
      createPost(entry1)
      deleteEntryInEditor()
      assertOnCollectionsPage()
      assertEntryDeleted(entry1)
    })

    it('can update workflow status from within the editor', () => {
      login()
      createPost(entry1)
      assertWorkflowStatusInEditor(editorStatus.draft)
      updateWorkflowStatusInEditor(editorStatus.review)
      assertWorkflowStatusInEditor(editorStatus.review)
      updateWorkflowStatusInEditor(editorStatus.ready)
      assertWorkflowStatusInEditor(editorStatus.ready)
      exitEditor()
      goToWorkflow()
      assertWorkflowStatus(entry1, workflowStatus.ready)
    })
  })
})