/*
 * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
 *
 * SPDX-License-Identifier: AGPL-3.0-only
 */

/* eslint-disable
@typescript-eslint/no-unsafe-assignment,
@typescript-eslint/no-unsafe-member-access
*/
import request from 'supertest';

import { NotInDBError } from '../../src/errors/errors';
import { LoginDto } from '../../src/identity/local/login.dto';
import { RegisterDto } from '../../src/identity/local/register.dto';
import { UpdatePasswordDto } from '../../src/identity/local/update-password.dto';
import { UserRelationEnum } from '../../src/users/user-relation.enum';
import { checkPassword } from '../../src/utils/password';
import { Username } from '../../src/utils/username';
import { TestSetup, TestSetupBuilder } from '../test-setup';

describe('Auth', () => {
  let testSetup: TestSetup;

  let username: Username;
  let displayName: string;
  let password: string;

  beforeAll(async () => {
    testSetup = await TestSetupBuilder.create().build();
    await testSetup.app.init();

    username = 'hardcoded';
    displayName = 'Testy';
    password = 'test_password';
  });

  afterAll(async () => {
    // Yes, this is a bad hack, but there is a race somewhere and I have
    // no idea how to fix it.
    await new Promise((resolve) => {
      setTimeout(resolve, 1000);
    });
    await testSetup.cleanup();
  });

  describe('POST /auth/local', () => {
    it('works', async () => {
      const registrationDto: RegisterDto = {
        displayName: displayName,
        password: password,
        username: username,
      };
      await request(testSetup.app.getHttpServer())
        .post('/api/private/auth/local')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(registrationDto))
        .expect(201);
      const newUser = await testSetup.userService.getUserByUsername(username, [
        UserRelationEnum.IDENTITIES,
      ]);
      expect(newUser.displayName).toEqual(displayName);
      await expect(newUser.identities).resolves.toHaveLength(1);
      await expect(
        checkPassword(
          password,
          (await newUser.identities)[0].passwordHash ?? '',
        ),
      ).resolves.toBeTruthy();
      await testSetup.userService.deleteUser(newUser);
    });
    describe('fails', () => {
      it('when the user already exits', async () => {
        const conflictingUserName = 'already_existing';
        const conflictingUser = await testSetup.userService.createUser(
          conflictingUserName,
          displayName,
        );
        const registrationDto: RegisterDto = {
          displayName: displayName,
          password: password,
          username: conflictingUserName,
        };
        await request(testSetup.app.getHttpServer())
          .post('/api/private/auth/local')
          .set('Content-Type', 'application/json')
          .send(JSON.stringify(registrationDto))
          .expect(409);
        await testSetup.userService.deleteUser(conflictingUser);
      });
      it('when registration is disabled', async () => {
        testSetup.configService.get('authConfig').local.enableRegister = false;
        const registrationDto: RegisterDto = {
          displayName: displayName,
          password: password,
          username: username,
        };
        await request(testSetup.app.getHttpServer())
          .post('/api/private/auth/local')
          .set('Content-Type', 'application/json')
          .send(JSON.stringify(registrationDto))
          .expect(403);
        testSetup.configService.get('authConfig').local.enableRegister = true;
      });
    });
    it('does not create a user if the PasswordTooWeakError is encountered', async () => {
      const registrationDto: RegisterDto = {
        displayName: displayName,
        password: 'test1234',
        username: username,
      };
      const response = await request(testSetup.app.getHttpServer())
        .post('/api/private/auth/local')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(registrationDto))
        .expect(400);
      expect(response.text).toContain('PasswordTooWeakError');
      await expect(() =>
        testSetup.userService.getUserByUsername(username, [
          UserRelationEnum.IDENTITIES,
        ]),
      ).rejects.toThrow(NotInDBError);
    });
  });

  describe('Already existing user', () => {
    beforeAll(async () => {
      const registrationDto: RegisterDto = {
        displayName: displayName,
        password: password,
        username: username,
      };
      await request(testSetup.app.getHttpServer())
        .post('/api/private/auth/local')
        .set('Content-Type', 'application/json')
        .send(JSON.stringify(registrationDto))
        .expect(201);
    });
    describe('PUT /auth/local', () => {
      const newPassword = 'new_password';
      let cookie = '';
      beforeEach(async () => {
        const loginDto: LoginDto = {
          password: password,
          username: username,
        };
        const response = await request(testSetup.app.getHttpServer())
          .post('/api/private/auth/local/login')
          .set('Content-Type', 'application/json')
          .send(JSON.stringify(loginDto))
          .expect(201);
        cookie = response.get('Set-Cookie')[0];
      });
      it('works', async () => {
        // Change password
        const changePasswordDto: UpdatePasswordDto = {
          currentPassword: password,
          newPassword: newPassword,
        };
        await request(testSetup.app.getHttpServer())
          .put('/api/private/auth/local')
          .set('Content-Type', 'application/json')
          .set('Cookie', cookie)
          .send(JSON.stringify(changePasswordDto))
          .expect(200);
        // Successfully login with new password
        const loginDto: LoginDto = {
          password: newPassword,
          username: username,
        };
        const response = await request(testSetup.app.getHttpServer())
          .post('/api/private/auth/local/login')
          .set('Content-Type', 'application/json')
          .send(JSON.stringify(loginDto))
          .expect(201);
        cookie = response.get('Set-Cookie')[0];
        // Reset password
        const changePasswordBackDto: UpdatePasswordDto = {
          currentPassword: newPassword,
          newPassword: password,
        };
        await request(testSetup.app.getHttpServer())
          .put('/api/private/auth/local')
          .set('Content-Type', 'application/json')
          .set('Cookie', cookie)
          .send(JSON.stringify(changePasswordBackDto))
          .expect(200);
      });
      it('fails, when registration is disabled', async () => {
        testSetup.configService.get('authConfig').local.enableLogin = false;
        // Try to change password
        const changePasswordDto: UpdatePasswordDto = {
          currentPassword: password,
          newPassword: newPassword,
        };
        await request(testSetup.app.getHttpServer())
          .put('/api/private/auth/local')
          .set('Content-Type', 'application/json')
          .set('Cookie', cookie)
          .send(JSON.stringify(changePasswordDto))
          .expect(403);
        // enable login again
        testSetup.configService.get('authConfig').local.enableLogin = true;
        // new password doesn't work for login
        const loginNewPasswordDto: LoginDto = {
          password: newPassword,
          username: username,
        };
        await request(testSetup.app.getHttpServer())
          .post('/api/private/auth/local/login')
          .set('Content-Type', 'application/json')
          .send(JSON.stringify(loginNewPasswordDto))
          .expect(401);
        // old password does work for login
        const loginOldPasswordDto: LoginDto = {
          password: password,
          username: username,
        };
        await request(testSetup.app.getHttpServer())
          .post('/api/private/auth/local/login')
          .set('Content-Type', 'application/json')
          .send(JSON.stringify(loginOldPasswordDto))
          .expect(201);
      });
      it('fails, when old password is wrong', async () => {
        // Try to change password
        const changePasswordDto: UpdatePasswordDto = {
          currentPassword: 'wrong',
          newPassword: newPassword,
        };
        await request(testSetup.app.getHttpServer())
          .put('/api/private/auth/local')
          .set('Content-Type', 'application/json')
          .set('Cookie', cookie)
          .send(JSON.stringify(changePasswordDto))
          .expect(401);
        // old password still does work for login
        const loginOldPasswordDto: LoginDto = {
          password: password,
          username: username,
        };
        await request(testSetup.app.getHttpServer())
          .post('/api/private/auth/local/login')
          .set('Content-Type', 'application/json')
          .send(JSON.stringify(loginOldPasswordDto))
          .expect(201);
      });
    });

    describe('POST /auth/local/login', () => {
      it('works', async () => {
        testSetup.configService.get('authConfig').local.enableLogin = true;
        const loginDto: LoginDto = {
          password: password,
          username: username,
        };
        await request(testSetup.app.getHttpServer())
          .post('/api/private/auth/local/login')
          .set('Content-Type', 'application/json')
          .send(JSON.stringify(loginDto))
          .expect(201);
      });
    });

    describe('DELETE /auth/logout', () => {
      it('works', async () => {
        testSetup.configService.get('authConfig').local.enableLogin = true;
        const loginDto: LoginDto = {
          password: password,
          username: username,
        };
        const response = await request(testSetup.app.getHttpServer())
          .post('/api/private/auth/local/login')
          .set('Content-Type', 'application/json')
          .send(JSON.stringify(loginDto))
          .expect(201);
        const cookie = response.get('Set-Cookie')[0];
        await request(testSetup.app.getHttpServer())
          .delete('/api/private/auth/logout')
          .set('Cookie', cookie)
          .expect(204);
      });
    });
  });
});