import { omit } from '@peertube/peertube-core-utils'
import { HttpStatusCode, HttpStatusCodeType, UserRole } from '@peertube/peertube-models'
import { checkBadCountPagination, checkBadSort, checkBadStartPagination } from '@tests/shared/checks.js'
import {
  cleanupTests,
  createSingleServer,
  makePostBodyRequest,
  PeerTubeServer,
  setAccessTokensToServers,
  setDefaultAccountAvatar,
  setDefaultChannelAvatar
} from '@peertube/peertube-server-commands'

describe('Test registrations API validators', function () {
  let server: PeerTubeServer
  let userToken: string
  let moderatorToken: string

  // ---------------------------------------------------------------

  before(async function () {
    this.timeout(30000)

    server = await createSingleServer(1)

    await setAccessTokensToServers([ server ])
    await setDefaultAccountAvatar([ server ])
    await setDefaultChannelAvatar([ server ])

    await server.config.enableSignup(false)
    ;({ token: moderatorToken } = await server.users.generate('moderator', UserRole.MODERATOR))
    ;({ token: userToken } = await server.users.generate('user', UserRole.USER))
  })

  describe('Register', function () {
    const registrationPath = '/api/v1/users/register'
    const registrationRequestPath = '/api/v1/users/registrations/request'

    const baseCorrectParams = {
      username: 'user3',
      displayName: 'super user',
      email: 'test3@example.com',
      password: 'my super password',
      registrationReason: 'my super registration reason'
    }

    describe('When registering a new user or requesting user registration', function () {
      async function check (fields: any, expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400) {
        await server.config.enableSignup(false)
        await makePostBodyRequest({ url: server.url, path: registrationPath, fields, expectedStatus })

        await server.config.enableSignup(true)
        await makePostBodyRequest({ url: server.url, path: registrationRequestPath, fields, expectedStatus })
      }

      it('Should fail with a too small username', async function () {
        const fields = { ...baseCorrectParams, username: '' }

        await check(fields)
      })

      it('Should fail with a too long username', async function () {
        const fields = { ...baseCorrectParams, username: 'super'.repeat(50) }

        await check(fields)
      })

      it('Should fail with an incorrect username', async function () {
        const fields = { ...baseCorrectParams, username: 'my username' }

        await check(fields)
      })

      it('Should fail with a missing email', async function () {
        const fields = omit(baseCorrectParams, [ 'email' ])

        await check(fields)
      })

      it('Should fail with an invalid email', async function () {
        const fields = { ...baseCorrectParams, email: 'test_example.com' }

        await check(fields)
      })

      it('Should fail with a too small password', async function () {
        const fields = { ...baseCorrectParams, password: 'bla' }

        await check(fields)
      })

      it('Should fail with a too long password', async function () {
        const fields = { ...baseCorrectParams, password: 'super'.repeat(61) }

        await check(fields)
      })

      it('Should fail if we register a user with the same username', async function () {
        const fields = { ...baseCorrectParams, username: 'root' }

        await check(fields, HttpStatusCode.CONFLICT_409)
      })

      it('Should fail with a "peertube" username', async function () {
        const fields = { ...baseCorrectParams, username: 'peertube' }

        await check(fields, HttpStatusCode.CONFLICT_409)
      })

      it('Should fail if we register a user with the same email', async function () {
        const emails = [
          'admin' + server.internalServerNumber + '@example.com',
          'Admin' + server.internalServerNumber + '@example.com'
        ]

        for (const email of emails) {
          const fields = { ...baseCorrectParams, email }

          await check(fields, HttpStatusCode.CONFLICT_409)
        }
      })

      it('Should fail with a bad display name', async function () {
        const fields = { ...baseCorrectParams, displayName: 'a'.repeat(150) }

        await check(fields)
      })

      it('Should fail with a bad channel name', async function () {
        const fields = { ...baseCorrectParams, channel: { name: '[]azf', displayName: 'toto' } }

        await check(fields)
      })

      it('Should fail with a bad channel display name', async function () {
        const fields = { ...baseCorrectParams, channel: { name: 'toto', displayName: '' } }

        await check(fields)
      })

      it('Should fail with a channel name that is the same as username', async function () {
        const source = { username: 'super_user', channel: { name: 'super_user', displayName: 'display name' } }
        const fields = { ...baseCorrectParams, ...source }

        await check(fields)
      })

      it('Should fail with an existing channel', async function () {
        const attributes = { name: 'existing_channel', displayName: 'hello', description: 'super description' }
        await server.channels.create({ attributes })

        const fields = { ...baseCorrectParams, channel: { name: 'existing_channel', displayName: 'toto' } }

        await check(fields, HttpStatusCode.CONFLICT_409)
      })

      it('Should fail on a server with registration disabled', async function () {
        this.timeout(60000)

        await server.config.updateExistingConfig({
          newConfig: {
            signup: {
              enabled: false
            }
          }
        })

        await server.registrations.register({ username: 'user4', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
        await server.registrations.requestRegistration({
          username: 'user4',
          registrationReason: 'reason',
          expectedStatus: HttpStatusCode.FORBIDDEN_403
        })
      })

      it('Should fail if the user limit is reached', async function () {
        this.timeout(60000)

        const { total } = await server.users.list()

        await server.config.enableSignup(false, total)
        await server.registrations.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 })

        await server.config.enableSignup(true, total)
        await server.registrations.requestRegistration({
          username: 'user42',
          registrationReason: 'reason',
          expectedStatus: HttpStatusCode.FORBIDDEN_403
        })
      })

      it('Should succeed if the user limit is not reached', async function () {
        this.timeout(60000)

        const { total } = await server.users.list()

        await server.config.enableSignup(false, total + 1)
        await server.registrations.register({ username: 'user43', expectedStatus: HttpStatusCode.NO_CONTENT_204 })

        await server.config.enableSignup(true, total + 2)
        await server.registrations.requestRegistration({
          username: 'user44',
          registrationReason: 'reason',
          expectedStatus: HttpStatusCode.OK_200
        })
      })
    })

    describe('On direct registration', function () {
      it('Should succeed with the correct params', async function () {
        await server.config.enableSignup(false)

        const fields = {
          username: 'user_direct_1',
          displayName: 'super user direct 1',
          email: 'user_direct_1@example.com',
          password: 'my super password',
          channel: { name: 'super_user_direct_1_channel', displayName: 'super user direct 1 channel' }
        }

        await makePostBodyRequest({ url: server.url, path: registrationPath, fields, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
      })

      it('Should fail if the instance requires approval', async function () {
        this.timeout(60000)

        await server.config.enableSignup(true)
        await server.registrations.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
      })
    })

    describe('On registration request', function () {
      before(async function () {
        this.timeout(60000)

        await server.config.enableSignup(true)
      })

      it('Should fail with an invalid registration reason', async function () {
        for (const registrationReason of [ '', 't', 't'.repeat(5000) ]) {
          await server.registrations.requestRegistration({
            username: 'user_request_1',
            registrationReason,
            expectedStatus: HttpStatusCode.BAD_REQUEST_400
          })
        }
      })

      it('Should succeed with the correct params', async function () {
        await server.registrations.requestRegistration({
          username: 'user_request_2',
          registrationReason: 'tt',
          channel: {
            displayName: 'my user request 2 channel',
            name: 'user_request_2_channel'
          }
        })
      })

      it('Should fail if the username is already awaiting registration approval', async function () {
        await server.registrations.requestRegistration({
          username: 'user_request_2',
          registrationReason: 'tt',
          channel: {
            displayName: 'my user request 42 channel',
            name: 'user_request_42_channel'
          },
          expectedStatus: HttpStatusCode.CONFLICT_409
        })
      })

      it('Should fail if the email is already awaiting registration approval', async function () {
        for (const email of [ 'user_request_2@example.com', 'user_requesT_2@example.com' ]) {
          await server.registrations.requestRegistration({
            username: 'user42',
            email,
            registrationReason: 'tt',
            channel: {
              displayName: 'my user request 42 channel',
              name: 'user_request_42_channel'
            },
            expectedStatus: HttpStatusCode.CONFLICT_409
          })
        }
      })

      it('Should fail if the channel is already awaiting registration approval', async function () {
        await server.registrations.requestRegistration({
          username: 'user42',
          registrationReason: 'tt',
          channel: {
            displayName: 'my user request 2 channel',
            name: 'user_request_2_channel'
          },
          expectedStatus: HttpStatusCode.CONFLICT_409
        })
      })

      it('Should fail if the instance does not require approval', async function () {
        this.timeout(60000)

        await server.config.enableSignup(false)

        await server.registrations.requestRegistration({
          username: 'user42',
          registrationReason: 'toto',
          expectedStatus: HttpStatusCode.BAD_REQUEST_400
        })
      })
    })
  })

  describe('Registrations accept/reject', function () {
    let id1: number
    let id2: number

    before(async function () {
      this.timeout(60000)

      await server.config.enableSignup(true)
      ;({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_2', registrationReason: 'toto' }))
      ;({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_3', registrationReason: 'toto' }))
    })

    it('Should fail to accept/reject registration without token', async function () {
      const options = { id: id1, moderationResponse: 'tt', token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }
      await server.registrations.accept(options)
      await server.registrations.reject(options)
    })

    it('Should fail to accept/reject registration with a non moderator user', async function () {
      const options = { id: id1, moderationResponse: 'tt', token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }
      await server.registrations.accept(options)
      await server.registrations.reject(options)
    })

    it('Should fail to accept/reject registration with a bad registration id', async function () {
      {
        const options = { id: 't' as any, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
        await server.registrations.accept(options)
        await server.registrations.reject(options)
      }

      {
        const options = { id: 42, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
        await server.registrations.accept(options)
        await server.registrations.reject(options)
      }
    })

    it('Should fail to accept/reject registration with a bad moderation resposne', async function () {
      for (const moderationResponse of [ '', 't', 't'.repeat(5000) ]) {
        const options = { id: id1, moderationResponse, token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
        await server.registrations.accept(options)
        await server.registrations.reject(options)
      }
    })

    it('Should succeed to accept a registration', async function () {
      await server.registrations.accept({ id: id1, moderationResponse: 'tt', token: moderatorToken })
    })

    it('Should succeed to reject a registration', async function () {
      await server.registrations.reject({ id: id2, moderationResponse: 'tt', token: moderatorToken })
    })

    it('Should fail to accept/reject a registration that was already accepted/rejected', async function () {
      for (const id of [ id1, id2 ]) {
        const options = { id, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.CONFLICT_409 }
        await server.registrations.accept(options)
        await server.registrations.reject(options)
      }
    })
  })

  describe('Registrations deletion', function () {
    let id1: number
    let id2: number
    let id3: number

    before(async function () {
      ;({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_4', registrationReason: 'toto' }))
      ;({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_5', registrationReason: 'toto' }))
      ;({ id: id3 } = await server.registrations.requestRegistration({ username: 'request_6', registrationReason: 'toto' }))

      await server.registrations.accept({ id: id2, moderationResponse: 'tt' })
      await server.registrations.reject({ id: id3, moderationResponse: 'tt' })
    })

    it('Should fail to delete registration without token', async function () {
      await server.registrations.delete({ id: id1, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
    })

    it('Should fail to delete registration with a non moderator user', async function () {
      await server.registrations.delete({ id: id1, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
    })

    it('Should fail to delete registration with a bad registration id', async function () {
      await server.registrations.delete({ id: 't' as any, token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
      await server.registrations.delete({ id: 42, token: moderatorToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
    })

    it('Should succeed with the correct params', async function () {
      await server.registrations.delete({ id: id1, token: moderatorToken })
      await server.registrations.delete({ id: id2, token: moderatorToken })
      await server.registrations.delete({ id: id3, token: moderatorToken })
    })
  })

  describe('Listing registrations', function () {
    const path = '/api/v1/users/registrations'

    it('Should fail with a bad start pagination', async function () {
      await checkBadStartPagination(server.url, path, server.accessToken)
    })

    it('Should fail with a bad count pagination', async function () {
      await checkBadCountPagination(server.url, path, server.accessToken)
    })

    it('Should fail with an incorrect sort', async function () {
      await checkBadSort(server.url, path, server.accessToken)
    })

    it('Should fail with a non authenticated user', async function () {
      await server.registrations.list({
        token: null,
        expectedStatus: HttpStatusCode.UNAUTHORIZED_401
      })
    })

    it('Should fail with a non admin user', async function () {
      await server.registrations.list({
        token: userToken,
        expectedStatus: HttpStatusCode.FORBIDDEN_403
      })
    })

    it('Should succeed with the correct params', async function () {
      await server.registrations.list({
        token: moderatorToken,
        search: 'toto'
      })
    })
  })

  after(async function () {
    await cleanupTests([ server ])
  })
})
