import { Test, TestingModule } from '@nestjs/testing';
import { MailerModule } from '../mailer/mailer.module';
import { User } from './schemas/user.schema';
import { UsersService } from './users.service';
import { getModelToken } from '@nestjs/mongoose';
import { CreateUserDto } from './dto/create-user.dto';
import { HttpException, HttpStatus } from '@nestjs/common';
import { LoginDto } from '../auth/login-dto';
import { EmailChangeDto } from './dto/change-email.dto';
import * as bcrypt from 'bcrypt';
import { ConfigurationModule } from '../configuration/configuration.module';
import { IUser } from './interfaces/user.interface';

function hashPassword() {
  return bcrypt.hashSync(process.env.USER_PWD, process.env.SALT);
}

describe('UsersService', () => {
  let service: UsersService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [MailerModule, ConfigurationModule],
      providers: [
        UsersService,
        {
          provide: getModelToken('User'),
          useValue: User,
        },
      ],
    }).compile();

    service = module.get<UsersService>(UsersService);
  });

  describe('User Service create', () => {
    it('UsersService should be defined', () => {
      expect(service).toBeDefined();
    });

    it('User should be created', async () => {
      const result: User = {
        role: 0,
        validationToken:
          'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
        emailVerified: false,
        email: 'jacques.dupont@mii.com',
        password: hashPassword(),
        newEmail: '',
        changeEmailToken: '',
        resetPasswordToken: null,
        structuresLink: [],
        structureOutdatedMailSent: [],
        pendingStructuresLink: [],
        name: 'Jacques',
        surname: 'Dupont',
        phone: '06 06 06 06 06',
      };
      const userDto: CreateUserDto = {
        email: 'jacques.dupont@mii.com',
        password: 'test1A!!',
        name: 'Jacques',
        surname: 'Dupont',
        phone: '06 06 06 06 06',
      }; //NOSONAR
      jest.spyOn(service, 'create').mockImplementation(async (): Promise<User> => result);
      expect(await service.create(userDto)).toBe(result);
    });

    it('User should not be created, already exist', async () => {
      const result = new HttpException('User already exists', HttpStatus.BAD_REQUEST);
      const userDto: CreateUserDto = {
        email: 'jacques.dupont@mii.com',
        password: 'test1A!!',
        name: 'Jacques',
        surname: 'Dupont',
        phone: '06 06 06 06 06',
      }; //NOSONAR
      jest.spyOn(service, 'create').mockImplementation(async (): Promise<any> => result);
      expect(await service.create(userDto)).toBe(result);
    });

    it('User should not be created, weak password', async () => {
      const result = new HttpException(
        'Weak password, it must contain one lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer',
        HttpStatus.UNPROCESSABLE_ENTITY
      );
      const userDto: CreateUserDto = {
        email: 'jacques.dupont@mii.com',
        password: 'test',
        name: 'Jacques',
        surname: 'Dupont',
        phone: '06 06 06 06 06',
      }; //NOSONAR
      jest.spyOn(service, 'create').mockImplementation(async (): Promise<any> => result);
      expect(await service.create(userDto)).toBe(result);
    });
  });

  describe('findByLogin', () => {
    it('should find', async () => {
      const result = {
        validationToken:
          'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
        emailVerified: false,
        email: 'jacques.dupont@mii.com',
        password: hashPassword(),
        role: 0,
        newEmail: '',
        changeEmailToken: '',
        resetPasswordToken: null,
        structuresLink: [],
        pendingStructuresLink: [],
        structureOutdatedMailSent: [],
        name: 'Jacques',
        surname: 'Dupont',
        phone: '06 06 06 06 06',
      };
      const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR
      jest.spyOn(service, 'findByLogin').mockImplementation(async (): Promise<User> => result);
      expect(await service.findByLogin(loginDto)).toBe(result);
    });

    it('user does not exist, should be unauthorized issue', async () => {
      const result: HttpException = new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED);
      const loginDto: LoginDto = { email: 'jean.dupont@mii.com', password: 'test1A!!' }; //NOSONAR
      jest.spyOn(service, 'findByLogin').mockImplementation(async (): Promise<any> => result);
      expect(await service.findByLogin(loginDto)).toBe(result);
    });

    it('wrong password, should be unauthorized issue', async () => {
      const result: HttpException = new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED);
      const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!!' }; //NOSONAR
      jest.spyOn(service, 'findByLogin').mockImplementation(async (): Promise<any> => result);
      expect(await service.findByLogin(loginDto)).toBe(result);
    });
  });

  describe('validateUser', () => {
    it('should not validateUser', async () => {
      const result = new HttpException('Invalid token', HttpStatus.UNAUTHORIZED);
      jest.spyOn(service, 'validateUser').mockImplementation(async (): Promise<HttpException> => result);
      expect(await service.validateUser('add3d', 'qdqdqdqd185')).toBe(result);
    });
  });

  describe('changeUserPassword', () => {
    it('should not change password', async () => {
      const result = new HttpException('Invalid token', HttpStatus.UNAUTHORIZED);
      jest.spyOn(service, 'changeUserPassword').mockImplementation(async (): Promise<HttpException> => result);
      expect(await service.changeUserPassword('add3d', 'azertyU1', 'azertyU1!d')).toBe(result);
    });
    it('should not change password', async () => {
      const result = new HttpException('Invalid token', HttpStatus.UNPROCESSABLE_ENTITY);
      jest.spyOn(service, 'changeUserPassword').mockImplementation(async (): Promise<HttpException> => result);
      expect(await service.changeUserPassword('add3d', 'azertyU1!d', 'a')).toBe(result);
    });
    it('should change password', async () => {
      const result = new HttpException('Invalid token', HttpStatus.CREATED);
      jest.spyOn(service, 'changeUserPassword').mockImplementation(async (): Promise<HttpException> => result);
      expect(await service.changeUserPassword('add3d', 'azertyU1!d', 'azertyU1!d')).toBe(result);
    });
  });

  describe('changeUserEmail', () => {
    it('should find and add token', async () => {
      const result = {
        validationToken: '',
        emailVerified: true,
        email: 'jacques.dupont@mii.com',
        password: hashPassword(),
        role: 0,
        newEmail: 'test.dupont@mail.com',
        resetPasswordToken: '',
        structuresLink: [],
        pendingStructuresLink: [],
        structureOutdatedMailSent: [],
        name: 'Jacques',
        surname: 'Dupont',
        phone: '06 06 06 06 06',
        changeEmailToken:
          '9bb3542bdc5ca8801ad4cee00403c1052bc95dee768dcbb65b1f719870578ed79f71f52fdc3e6bf02fd200a72b8b6f56fc26950df30c8cd7e427a485f80181b9',
      };
      const emailDto: EmailChangeDto = { newEmail: 'test.dupont@mail.com', oldEmail: 'jacques.dupont@mii.com' }; //NOSONAR
      jest.spyOn(service, 'changeUserEmail').mockImplementation(async (): Promise<User> => result);
      expect(await service.changeUserEmail(emailDto)).toBe(result);
    });
    it('user does not exist, should be unauthorized issue', async () => {
      const result: HttpException = new HttpException('Email sent if account exist', HttpStatus.UNAUTHORIZED);
      const emailDto: EmailChangeDto = { newEmail: 'test.dupont@mail.com', oldEmail: 'jacques.dupont@mii.com' }; //NOSONAR
      jest.spyOn(service, 'changeUserEmail').mockImplementation(async (): Promise<any> => result);
      expect(await service.changeUserEmail(emailDto)).toBe(result);
    });
    it('email already used, should be not acceptable issue', async () => {
      const result: HttpException = new HttpException('Email already used', HttpStatus.NOT_ACCEPTABLE);
      const emailDto: EmailChangeDto = { newEmail: 'jacques.dupont@mii.com', oldEmail: 'jacques.dupont@mii.com' }; //NOSONAR
      jest.spyOn(service, 'changeUserEmail').mockImplementation(async (): Promise<any> => result);
      expect(await service.changeUserEmail(emailDto)).toBe(result);
    });
    it('should change email', async () => {
      const result = {
        validationToken: '',
        emailVerified: true,
        email: 'test.dupont@mail.com',
        password: hashPassword(),
        role: 0,
        newEmail: '',
        resetPasswordToken: '',
        changeEmailToken: '',
        structuresLink: [],
        pendingStructuresLink: [],
        structureOutdatedMailSent: [],
        name: 'Jacques',
        surname: 'Dupont',
        phone: '06 06 06 06 06',
      };
      const token =
        '9bb3542bdc5ca8801ad4cee00403c1052bc95dee768dcbb65b1f719870578ed79f71f52fdc3e6bf02fd200a72b8b6f56fc26950df30c8cd7e427a485f80181b9'; //NOSONAR
      jest.spyOn(service, 'verifyAndUpdateUserEmail').mockImplementation(async (): Promise<User> => result);
      expect(await service.verifyAndUpdateUserEmail(token)).toBe(result);
    });
    it('should not change email', async () => {
      const result: HttpException = new HttpException('Invalid token', HttpStatus.UNAUTHORIZED);
      const token = '9bb3542bdc5ca8801aa72b8b6f56fc26950df30c8cd7e427a485f80181b9FAKETOKEN'; //NOSONAR
      jest.spyOn(service, 'verifyAndUpdateUserEmail').mockImplementation(async (): Promise<any> => result);
      expect(await service.verifyAndUpdateUserEmail(token)).toBe(result);
    });
  });

  describe('sendResetPasswordEmail', () => {
    it('should not send email', async () => {
      const result = new HttpException('Email sent if account exist', HttpStatus.OK);
      jest.spyOn(service, 'sendResetPasswordEmail').mockImplementation(async (): Promise<HttpException> => result);
      expect(await service.sendResetPasswordEmail('test@mii.com')).toBe(result);
    });

    it('should send email', async () => {
      const result = new HttpException('Email sent if account exist', HttpStatus.OK);
      jest.spyOn(service, 'sendResetPasswordEmail').mockImplementation(async (): Promise<HttpException> => result);
      expect(await service.sendResetPasswordEmail('test@mii.com')).toBe(result);
    });
  });

  describe('validatePasswordResetToken', () => {
    it('should not validate new password: token does`nt exist', async () => {
      const result = new HttpException('Invalid token', HttpStatus.UNAUTHORIZED);
      jest.spyOn(service, 'validatePasswordResetToken').mockImplementation(async (): Promise<HttpException> => result);
      expect(
        await service.validatePasswordResetToken(
          'test@mii.com',
          '5def4cb41106f89c212679e164911776618bd529e4f78e2883f7dd01776612a1b4a2ad7edabf2a3e3638aa605966c7a4b69d5f07d9617334e58332ba5f9305'
        )
      ).toBe(result);
    });

    it('should not validate new password: weak password', async () => {
      const result = new HttpException(
        'Weak password, it must contain one lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer',
        HttpStatus.UNPROCESSABLE_ENTITY
      );
      jest.spyOn(service, 'validatePasswordResetToken').mockImplementation(async (): Promise<HttpException> => result);
      expect(
        await service.validatePasswordResetToken(
          'test@mii.com',
          '5def4cb41106f89c212679e164911776618bd529e4f78e2883f7dd01776612a1b4a2ad7edabf2a3e3638aa605966c7a4b69d5f07d9617334e58332ba5f9305a6'
        )
      ).toBe(result);
    });

    it('should validate new password', async () => {
      const result = new HttpException('Password Reset', HttpStatus.OK);
      jest.spyOn(service, 'validatePasswordResetToken').mockImplementation(async (): Promise<HttpException> => result);
      expect(
        await service.validatePasswordResetToken(
          'test@mii.com',
          '5def4cb41106f89c212679e164911776618bd529e4f78e2883f7dd01776612a1b4a2ad7edabf2a3e3638aa605966c7a4b69d5f07d9617334e58332ba5f9305a6'
        )
      ).toBe(result);
    });

    it('should return structureLink tab ', async () => {
      const result = [53];
      jest.spyOn(service, 'updateStructureLinked').mockImplementation(async (): Promise<any> => result);
      expect(await service.updateStructureLinked('test@mii.com', '6001a37716b08100062e4160')).toBe(result);
    });

    it('should return invalid User ', async () => {
      const result = new HttpException('Invalid user', HttpStatus.NOT_FOUND);
      jest.spyOn(service, 'updateStructureLinked').mockImplementation(async (): Promise<any> => result);
      expect(await service.updateStructureLinked('test@mii.com', '6001a37716b08100062e4160')).toBe(result);
    });
  });

  it('should find All Unattacheduser', async () => {
    const result = [
      {
        _id: '123',
        validationToken:
          'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
        emailVerified: false,
        email: 'jacques.dupont@mii.com',
        password: hashPassword(),
        role: 0,
        newEmail: '',
        changeEmailToken: '',
        resetPasswordToken: null,
        structuresLink: [],
        pendingStructuresLink: [],
        structureOutdatedMailSent: [],
        name: 'Jacques',
        surname: 'Dupont',
        phone: '06 06 06 06 06',
      } as IUser,
    ];
    jest.spyOn(service, 'findAllUnattached').mockImplementation(async (): Promise<IUser[]> => result);
    expect(await service.findAllUnattached()).toBe(result);
  });

  it('should find attached users', async () => {
    const result = [];
    jest.spyOn(service, 'findAllAttached').mockImplementation(async (): Promise<IUser[]> => result);
    expect((await service.findAllAttached()).length).toBe(0);
  });

  it('should find UnVerified Users', async () => {
    const result = [];
    jest.spyOn(service, 'findAllUnVerified').mockImplementation(async (): Promise<IUser[]> => result);
    expect((await service.findAllUnVerified()).length).toBe(0);
  });
});