import { Injectable, <%_ if (authenticationType === 'jwt') { _%> HttpException, HttpStatus,  <%_ } _%> Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { FindManyOptions, Repository } from 'typeorm';
<%_ if (authenticationType === 'jwt') { _%>
import { comparePassword } from '../security/password-util';
import { JwtService } from '@nestjs/jwt';
import { UserLoginDTO } from '../service/dto/user-login.dto';
import { Payload } from '../security/payload.interface';
<%_ } _%>
import { Authority } from '../domain/authority.entity';
import { UserService } from '../service/user.service';
import { <%= user.dtoClass%> } from './dto/user.dto';

<%_
const userIdType = user.primaryKey.tsType;
_%>


@Injectable()
export class AuthService {
    logger = new Logger('AuthService');
  constructor(
    <%_ if (authenticationType === 'jwt') { _%>
    private readonly jwtService: JwtService,
    <%_ } _%>
    @InjectRepository(Authority) private authorityRepository: Repository<Authority>,
    private userService: UserService,
  ) {}

  <%_ if (authenticationType === 'jwt') { _%>
  async login(userLogin: UserLoginDTO): Promise<any> {
    const loginUserName = userLogin.username;
    const loginPassword = userLogin.password;
    if (!loginUserName || !loginPassword) {
      throw new HttpException('Username and password are required!', HttpStatus.BAD_REQUEST);
    }

    const userFind = await this.userService.findByFields({ where: { login: loginUserName } });
    const validPassword = !!userFind && comparePassword(loginPassword, userFind.password);
    if (!userFind || !validPassword) {
      throw new HttpException('Invalid login name or password!', HttpStatus.UNAUTHORIZED);
    }

    if (userFind && !userFind.activated) {
      throw new HttpException('Your account is not been activated!', HttpStatus.UNAUTHORIZED);
    }

    const user = await this.findUserWithAuthById(userFind.id);

    const payload: Payload = { id: user.id, username: user.login, authorities: user.authorities };

    /* eslint-disable */
    return {
      id_token: this.jwtService.sign(payload),
    };
  }

  /* eslint-enable */
  async validateUser(payload: Payload): Promise<<%= user.dtoClass%> | undefined> {
    return await this.findUserWithAuthById(payload.id);
  }

  async findUserWithAuthById(userId: <%= userIdType %>): Promise<<%= user.dtoClass%> | undefined> {
    <%_ if (databaseType === 'mongodb') { _%>
    const userDTO: <%= user.dtoClass%> = await this.userService.findById(userId);
    <%_ } else { _%>
    const userDTO: <%= user.dtoClass%> = await this.userService.findByFields({ where: { id: userId } });
    <%_ } _%>
    return userDTO;
  }

  async getAccount(userId: <%= userIdType %>): Promise<<%= user.dtoClass%> | undefined> {
    const userDTO: <%= user.dtoClass%> = await this.findUserWithAuthById(userId);
    if (!userDTO) {
      return;
    }
    return userDTO;
  }

  async changePassword(userLogin: string, currentClearTextPassword: string, newPassword: string): Promise<void> {
    const userFind: <%= user.dtoClass%> = await this.userService.findByFields({ where: { login: userLogin } });
    if (!userFind) {
      throw new HttpException('Invalid login name!', HttpStatus.BAD_REQUEST);
    }

    if (!comparePassword(currentClearTextPassword, userFind.password)) {
      throw new HttpException('Invalid password!', HttpStatus.BAD_REQUEST);
    }
    userFind.password = newPassword;
    await this.userService.save(userFind, userLogin, true);
    return;
  }

  async registerNewUser (newUser: <%= user.dtoClass%>) : Promise<<%= user.dtoClass%>> {
    let userFind: <%= user.dtoClass%> = await this.userService.findByFields({ where: { login: newUser.login} });
    if (userFind) {
      throw new HttpException('Login name already used!', HttpStatus.BAD_REQUEST);
    }
    userFind = await this.userService.findByFields({ where: { email: newUser.email } });
    if (userFind) {
      throw new HttpException('Email is already in use!', HttpStatus.BAD_REQUEST);
    }
    newUser.authorities = ['ROLE_USER'];
    const user: <%= user.dtoClass%> = await this.userService.save(newUser, newUser.login, true);
    return user;
  }

  async updateUserSettings(userLogin: string, newUserInfo: <%= user.dtoClass%>) : Promise<<%= user.dtoClass%>> {
    const userFind: <%= user.dtoClass%> = await this.userService.findByFields({ where: { login: userLogin } });
    if (!userFind) {
      throw new HttpException('Invalid login name!', HttpStatus.BAD_REQUEST);
    }
    const userFindEmail: <%= user.dtoClass%> = await this.userService.findByFields({ where: { email: newUserInfo.email } });
    if (userFindEmail && newUserInfo.email !== userFind.email) {
      throw new HttpException('Email is already in use!', HttpStatus.BAD_REQUEST);
    }

    userFind.firstName = newUserInfo.firstName;
    userFind.lastName = newUserInfo.lastName;
    userFind.email = newUserInfo.email;
    userFind.langKey = newUserInfo.langKey;
    await this.userService.save(userFind, userLogin);
    return userFind;
  }

  <%_ } else if (authenticationType === 'oauth2') { _%>
    async findUserOrSave(loginUser: <%= user.dtoClass%>): Promise<<%= user.dtoClass%> | undefined> {
        if (loginUser.login && loginUser.password && !loginUser.email) {
            loginUser.email = loginUser.login + '@localhost.it';
        }
        let userFound: <%= user.dtoClass%> = await this.userService.findByFields({ where: { login: loginUser.login } });

        if (!userFound) {
          const authoritiesName = [];
          loginUser.authorities.forEach(authority => authoritiesName.push({ name: authority }));
          userFound = Object.assign({}, loginUser);
          userFound.authorities = authoritiesName;
          await this.userService.save(userFound, loginUser.login);
        }
        return loginUser;
      }

   getAccount(userDTO: <%= user.dtoClass%>): any {
    if (!userDTO) {
       return;
    }
    return userDTO;
   }

  <%_ } _%>

  async getAllUsers(options: FindManyOptions<<%= user.dtoClass%>>): Promise<[<%= user.dtoClass%>[], number]> {
    return await this.userService.findAndCount(options);
  }
}
