import { createTestClient } from 'apollo-server-testing'
import Factory, { cleanDatabase } from '../../db/factories'
import gql from 'graphql-tag'
import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server'

const neode = getNeode()
const driver = getDriver()

let mutate,
  authenticatedUser,
  disableVariables,
  enableVariables,
  moderator,
  nonModerator,
  closeReportVariables

const reviewMutation = gql`
  mutation ($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
    review(resourceId: $resourceId, disable: $disable, closed: $closed) {
      createdAt
      updatedAt
      resource {
        __typename
        ... on User {
          id
          disabled
        }
        ... on Post {
          id
          disabled
        }
        ... on Comment {
          id
          disabled
        }
      }
      report {
        id
        createdAt
        updatedAt
        closed
        reviewed {
          createdAt
          moderator {
            id
          }
        }
      }
    }
  }
`

describe('moderate resources', () => {
  beforeAll(async () => {
    await cleanDatabase()

    authenticatedUser = undefined
    const { server } = createServer({
      context: () => {
        return {
          driver,
          neode,
          user: authenticatedUser,
        }
      },
    })
    mutate = createTestClient(server).mutate
  })

  afterAll(async () => {
    await cleanDatabase()
    driver.close()
  })

  beforeEach(async () => {
    disableVariables = {
      resourceId: 'undefined-resource',
      disable: true,
      closed: false,
    }
    enableVariables = {
      resourceId: 'undefined-resource',
      disable: false,
      closed: false,
    }
    authenticatedUser = null
    moderator = await Factory.build(
      'user',
      {
        id: 'moderator-id',
        name: 'Moderator',
        role: 'moderator',
      },
      {
        email: 'moderator@example.org',
        password: '1234',
      },
    )
    nonModerator = await Factory.build(
      'user',
      {
        id: 'non-moderator',
        name: 'Non Moderator',
      },
      {
        email: 'non.moderator@example.org',
        password: '1234',
      },
    )
  })

  // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
  afterEach(async () => {
    await cleanDatabase()
  })

  describe('review to close report, leaving resource enabled', () => {
    describe('unauthenticated', () => {
      it('throws authorization error', async () => {
        await expect(
          mutate({ mutation: reviewMutation, variables: disableVariables }),
        ).resolves.toMatchObject({
          errors: [{ message: 'Not Authorized!' }],
        })
      })
    })

    describe('authenticated', () => {
      beforeEach(async () => {
        authenticatedUser = await nonModerator.toJson()
      })

      it('non-moderator receives an authorization error', async () => {
        await expect(
          mutate({ mutation: reviewMutation, variables: disableVariables }),
        ).resolves.toMatchObject({
          errors: [{ message: 'Not Authorized!' }],
        })
      })
    })

    describe('moderator', () => {
      beforeEach(async () => {
        authenticatedUser = await moderator.toJson()
        const questionablePost = await Factory.build('post', {
          id: 'should-i-be-disabled',
        })
        const reportAgainstQuestionablePost = await Factory.build('report')
        await Promise.all([
          reportAgainstQuestionablePost.relateTo(nonModerator, 'filed', {
            resourceId: 'should-i-be-disabled',
            reasonCategory: 'doxing',
            reasonDescription: "This shouldn't be shown to anybody else! It's my private thing!",
          }),
          reportAgainstQuestionablePost.relateTo(questionablePost, 'belongsTo'),
        ])
        closeReportVariables = {
          resourceId: 'should-i-be-disabled',
          disable: false,
          closed: true,
        }
      })

      it('report can be closed without disabling resource', async () => {
        await expect(
          mutate({ mutation: reviewMutation, variables: closeReportVariables }),
        ).resolves.toMatchObject({
          data: {
            review: {
              resource: { __typename: 'Post', id: 'should-i-be-disabled', disabled: false },
              report: { id: expect.any(String), closed: true },
            },
          },
          errors: undefined,
        })
      })

      it('creates only one review for multiple reviews by the same moderator on same resource', async () => {
        await Promise.all([
          mutate({
            mutation: reviewMutation,
            variables: { ...disableVariables, resourceId: 'should-i-be-disabled' },
          }),
          mutate({
            mutation: reviewMutation,
            variables: { ...enableVariables, resourceId: 'should-i-be-disabled' },
          }),
        ])
        const cypher =
          'MATCH (:Report)<-[review:REVIEWED]-(moderator:User {id: "moderator-id"}) RETURN review'
        const reviews = await neode.cypher(cypher)
        expect(reviews.records).toHaveLength(1)
      })

      it('updates the updatedAt attribute', async () => {
        const [firstReview, secondReview] = await Promise.all([
          mutate({
            mutation: reviewMutation,
            variables: { ...disableVariables, resourceId: 'should-i-be-disabled' },
          }),
          mutate({
            mutation: reviewMutation,
            variables: { ...enableVariables, resourceId: 'should-i-be-disabled' },
          }),
        ])
        expect(firstReview.data.review.updatedAt).toBeTruthy()
        expect(Date.parse(firstReview.data.review.updatedAt)).toEqual(expect.any(Number))
        expect(secondReview.data.review.updatedAt).toBeTruthy()
        expect(Date.parse(secondReview.data.review.updatedAt)).toEqual(expect.any(Number))
        expect(firstReview.data.review.updatedAt).not.toEqual(secondReview.data.review.updatedAt)
      })
    })
  })

  describe('review to disable', () => {
    describe('unauthenticated', () => {
      it('throws authorization error', async () => {
        await expect(
          mutate({ mutation: reviewMutation, variables: disableVariables }),
        ).resolves.toMatchObject({
          errors: [{ message: 'Not Authorized!' }],
        })
      })
    })

    describe('authenticated', () => {
      beforeEach(async () => {
        authenticatedUser = await nonModerator.toJson()
      })

      it('non-moderator receives an authorization error', async () => {
        await expect(
          mutate({ mutation: reviewMutation, variables: disableVariables }),
        ).resolves.toMatchObject({
          errors: [{ message: 'Not Authorized!' }],
        })
      })
    })

    describe('moderator', () => {
      beforeEach(async () => {
        authenticatedUser = await moderator.toJson()
      })

      describe('moderate a comment', () => {
        beforeEach(async () => {
          const trollingComment = await Factory.build('comment', {
            id: 'comment-id',
          })
          const reportAgainstTrollingComment = await Factory.build('report')
          await Promise.all([
            reportAgainstTrollingComment.relateTo(nonModerator, 'filed', {
              resourceId: 'comment-id',
              reasonCategory: 'other',
              reasonDescription: 'This comment is bigoted',
            }),
            reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'),
          ])
          disableVariables = {
            ...disableVariables,
            resourceId: 'comment-id',
          }
        })

        it('returns disabled resource id', async () => {
          await expect(
            mutate({ mutation: reviewMutation, variables: disableVariables }),
          ).resolves.toMatchObject({
            data: { review: { resource: { __typename: 'Comment', id: 'comment-id' } } },
            errors: undefined,
          })
        })

        it('returns .reviewed', async () => {
          await expect(
            mutate({ mutation: reviewMutation, variables: disableVariables }),
          ).resolves.toMatchObject({
            data: {
              review: {
                resource: { __typename: 'Comment', id: 'comment-id' },
                report: {
                  id: expect.any(String),
                  reviewed: expect.arrayContaining([
                    { createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
                  ]),
                },
              },
            },
            errors: undefined,
          })
        })

        it('updates .disabled on comment', async () => {
          await expect(
            mutate({ mutation: reviewMutation, variables: disableVariables }),
          ).resolves.toMatchObject({
            data: {
              review: { resource: { __typename: 'Comment', id: 'comment-id', disabled: true } },
            },
            errors: undefined,
          })
        })

        it('can be closed with one review', async () => {
          closeReportVariables = {
            ...disableVariables,
            closed: true,
          }
          await expect(
            mutate({ mutation: reviewMutation, variables: closeReportVariables }),
          ).resolves.toMatchObject({
            data: {
              review: {
                resource: { __typename: 'Comment', id: 'comment-id' },
                report: { id: expect.any(String), closed: true },
              },
            },
            errors: undefined,
          })
        })
      })

      describe('moderate a post', () => {
        beforeEach(async () => {
          const trollingPost = await Factory.build('post', {
            id: 'post-id',
          })
          const reportAgainstTrollingPost = await Factory.build('report')
          await Promise.all([
            reportAgainstTrollingPost.relateTo(nonModerator, 'filed', {
              resourceId: 'post-id',
              reasonCategory: 'doxing',
              reasonDescription: "This shouldn't be shown to anybody else! It's my private thing!",
            }),
            reportAgainstTrollingPost.relateTo(trollingPost, 'belongsTo'),
          ])
          disableVariables = {
            ...disableVariables,
            resourceId: 'post-id',
          }
        })

        it('returns disabled resource id', async () => {
          await expect(
            mutate({ mutation: reviewMutation, variables: disableVariables }),
          ).resolves.toMatchObject({
            data: {
              review: {
                resource: { __typename: 'Post', id: 'post-id' },
              },
            },
            errors: undefined,
          })
        })

        it('returns .reviewed', async () => {
          await expect(
            mutate({ mutation: reviewMutation, variables: disableVariables }),
          ).resolves.toMatchObject({
            data: {
              review: {
                resource: { __typename: 'Post', id: 'post-id' },
                report: {
                  id: expect.any(String),
                  reviewed: expect.arrayContaining([
                    { createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
                  ]),
                },
              },
            },
            errors: undefined,
          })
        })

        it('updates .disabled on post', async () => {
          await expect(
            mutate({ mutation: reviewMutation, variables: disableVariables }),
          ).resolves.toMatchObject({
            data: { review: { resource: { __typename: 'Post', id: 'post-id', disabled: true } } },
            errors: undefined,
          })
        })

        it('can be closed with one review', async () => {
          closeReportVariables = {
            ...disableVariables,
            closed: true,
          }
          await expect(
            mutate({ mutation: reviewMutation, variables: closeReportVariables }),
          ).resolves.toMatchObject({
            data: {
              review: {
                resource: { __typename: 'Post', id: 'post-id' },
                report: { id: expect.any(String), closed: true },
              },
            },
            errors: undefined,
          })
        })
      })

      describe('moderate a user', () => {
        beforeEach(async () => {
          const troll = await Factory.build('user', {
            id: 'user-id',
          })
          const reportAgainstTroll = await Factory.build('report')
          await Promise.all([
            reportAgainstTroll.relateTo(nonModerator, 'filed', {
              resourceId: 'user-id',
              reasonCategory: 'discrimination_etc',
              reasonDescription: 'This user is harassing me with bigoted remarks!',
            }),
            reportAgainstTroll.relateTo(troll, 'belongsTo'),
          ])
          disableVariables = {
            ...disableVariables,
            resourceId: 'user-id',
          }
        })

        it('returns disabled resource id', async () => {
          await expect(
            mutate({ mutation: reviewMutation, variables: disableVariables }),
          ).resolves.toMatchObject({
            data: { review: { resource: { __typename: 'User', id: 'user-id' } } },
            errors: undefined,
          })
        })

        it('returns .reviewed', async () => {
          await expect(
            mutate({ mutation: reviewMutation, variables: disableVariables }),
          ).resolves.toMatchObject({
            data: {
              review: {
                resource: { __typename: 'User', id: 'user-id' },
                report: {
                  id: expect.any(String),
                  reviewed: expect.arrayContaining([
                    { createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
                  ]),
                },
              },
            },
            errors: undefined,
          })
        })

        it('updates .disabled on user', async () => {
          await expect(
            mutate({ mutation: reviewMutation, variables: disableVariables }),
          ).resolves.toMatchObject({
            data: { review: { resource: { __typename: 'User', id: 'user-id', disabled: true } } },
            errors: undefined,
          })
        })

        it('can be closed with one review', async () => {
          closeReportVariables = {
            ...disableVariables,
            closed: true,
          }
          await expect(
            mutate({ mutation: reviewMutation, variables: closeReportVariables }),
          ).resolves.toMatchObject({
            data: {
              review: {
                resource: { __typename: 'User', id: 'user-id' },
                report: { id: expect.any(String), closed: true },
              },
            },
            errors: undefined,
          })
        })
      })
    })
  })

  describe('review to re-enable after disabled', () => {
    describe('unautenticated user', () => {
      it('throws authorization error', async () => {
        enableVariables = {
          ...enableVariables,
          resourceId: 'post-id',
        }
        await expect(
          mutate({ mutation: reviewMutation, variables: enableVariables }),
        ).resolves.toMatchObject({
          errors: [{ message: 'Not Authorized!' }],
        })
      })
    })

    describe('authenticated user', () => {
      describe('non moderator', () => {
        beforeEach(async () => {
          authenticatedUser = await nonModerator.toJson()
        })

        it('throws authorization error', async () => {
          enableVariables = {
            ...enableVariables,
            resourceId: 'post-id',
          }
          await expect(
            mutate({ mutation: reviewMutation, variables: enableVariables }),
          ).resolves.toMatchObject({
            errors: [{ message: 'Not Authorized!' }],
          })
        })
      })

      describe('moderator', () => {
        beforeEach(async () => {
          authenticatedUser = await moderator.toJson()
        })

        describe('moderate a comment', () => {
          beforeEach(async () => {
            const trollingComment = await Factory.build('comment', {
              id: 'comment-id',
            })
            const reportAgainstTrollingComment = await Factory.build('report')
            await Promise.all([
              reportAgainstTrollingComment.relateTo(nonModerator, 'filed', {
                resourceId: 'comment-id',
                reasonCategory: 'other',
                reasonDescription: 'This comment is bigoted',
              }),
              reportAgainstTrollingComment.relateTo(trollingComment, 'belongsTo'),
            ])
            await Promise.all([
              reportAgainstTrollingComment.relateTo(moderator, 'reviewed', {
                ...disableVariables,
                resourceId: 'comment-id',
              }),
              trollingComment.update({ disabled: true, updatedAt: new Date().toISOString() }),
            ])
            enableVariables = {
              ...enableVariables,
              resourceId: 'comment-id',
            }
          })

          it('returns enabled resource id', async () => {
            await expect(
              mutate({ mutation: reviewMutation, variables: enableVariables }),
            ).resolves.toMatchObject({
              data: { review: { resource: { __typename: 'Comment', id: 'comment-id' } } },
            })
          })

          it('returns .reviewed', async () => {
            await expect(
              mutate({ mutation: reviewMutation, variables: enableVariables }),
            ).resolves.toMatchObject({
              data: {
                review: {
                  resource: { __typename: 'Comment', id: 'comment-id' },
                  report: {
                    id: expect.any(String),
                    reviewed: expect.arrayContaining([
                      { createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
                    ]),
                  },
                },
              },
            })
          })

          it('updates .disabled on comment', async () => {
            await expect(
              mutate({ mutation: reviewMutation, variables: enableVariables }),
            ).resolves.toMatchObject({
              data: {
                review: { resource: { __typename: 'Comment', id: 'comment-id', disabled: false } },
              },
            })
          })
        })

        describe('moderate a post', () => {
          beforeEach(async () => {
            const trollingPost = await Factory.build('post', {
              id: 'post-id',
            })
            const reportAgainstTrollingPost = await Factory.build('report')
            await Promise.all([
              reportAgainstTrollingPost.relateTo(nonModerator, 'filed', {
                resourceId: 'post-id',
                reasonCategory: 'doxing',
                reasonDescription:
                  "This shouldn't be shown to anybody else! It's my private thing!",
              }),
              reportAgainstTrollingPost.relateTo(trollingPost, 'belongsTo'),
            ])
            await Promise.all([
              reportAgainstTrollingPost.relateTo(moderator, 'reviewed', {
                ...disableVariables,
                resourceId: 'comment-id',
              }),
              trollingPost.update({ disabled: true, updatedAt: new Date().toISOString() }),
            ])
            enableVariables = {
              ...enableVariables,
              resourceId: 'post-id',
            }
          })

          it('returns enabled resource id', async () => {
            await expect(
              mutate({ mutation: reviewMutation, variables: enableVariables }),
            ).resolves.toMatchObject({
              data: { review: { resource: { __typename: 'Post', id: 'post-id' } } },
            })
          })

          it('returns .reviewed', async () => {
            await expect(
              mutate({ mutation: reviewMutation, variables: enableVariables }),
            ).resolves.toMatchObject({
              data: {
                review: {
                  resource: { __typename: 'Post', id: 'post-id' },
                  report: {
                    id: expect.any(String),
                    reviewed: expect.arrayContaining([
                      { createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
                    ]),
                  },
                },
              },
            })
          })

          it('updates .disabled on post', async () => {
            await expect(
              mutate({ mutation: reviewMutation, variables: enableVariables }),
            ).resolves.toMatchObject({
              data: {
                review: { resource: { __typename: 'Post', id: 'post-id', disabled: false } },
              },
            })
          })
        })

        describe('moderate a user', () => {
          beforeEach(async () => {
            const troll = await Factory.build('user', {
              id: 'user-id',
            })
            const reportAgainstTroll = await Factory.build('report')
            await Promise.all([
              reportAgainstTroll.relateTo(nonModerator, 'filed', {
                resourceId: 'user-id',
                reasonCategory: 'discrimination_etc',
                reasonDescription: 'This user is harassing me with bigoted remarks!',
              }),
              reportAgainstTroll.relateTo(troll, 'belongsTo'),
            ])
            await Promise.all([
              reportAgainstTroll.relateTo(moderator, 'reviewed', {
                ...disableVariables,
                resourceId: 'comment-id',
              }),
              troll.update({ disabled: true, updatedAt: new Date().toISOString() }),
            ])
            enableVariables = {
              ...enableVariables,
              resourceId: 'user-id',
            }
          })

          it('returns enabled resource id', async () => {
            await expect(
              mutate({ mutation: reviewMutation, variables: enableVariables }),
            ).resolves.toMatchObject({
              data: { review: { resource: { __typename: 'User', id: 'user-id' } } },
            })
          })

          it('returns .reviewed', async () => {
            await expect(
              mutate({ mutation: reviewMutation, variables: enableVariables }),
            ).resolves.toMatchObject({
              data: {
                review: {
                  resource: { __typename: 'User', id: 'user-id' },
                  report: {
                    id: expect.any(String),
                    reviewed: expect.arrayContaining([
                      { createdAt: expect.any(String), moderator: { id: 'moderator-id' } },
                    ]),
                  },
                },
              },
            })
          })

          it('updates .disabled on user', async () => {
            await expect(
              mutate({ mutation: reviewMutation, variables: enableVariables }),
            ).resolves.toMatchObject({
              data: {
                review: { resource: { __typename: 'User', id: 'user-id', disabled: false } },
              },
            })
          })
        })
      })
    })
  })
})
