import {
  BadGatewayException,
  ForbiddenException,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { SignInDto, SignInResponseDto } from './dtos/sign-in.dto';
import * as argon2 from 'argon2';
import { JWTService } from '../services/authentication/jwt/jwt.service';
import { Payload } from '../services/authentication/jwt/interfaces/Payload';
import { SignInWithGoogleDto } from './dtos/sign-in-with-google.dto';
import { GoogleAuthService } from '../services/authentication/google-auth/google-auth.service';

@Injectable()
export class AuthService {
  constructor(
    private readonly _entityManager: EntityManager,
    private readonly _jwtService: JWTService,
    private readonly _googleAuthService: GoogleAuthService,
  ) {}

  private async _findUser(username: string, email: string) {
    const BASE_SQL = 'SELECT person_id AS id, username, password FROM user';

    // Query using only the username
    if (!email) {
      const SQL = `${BASE_SQL} WHERE username = ?`;

      return this._entityManager.query(SQL, [username]).catch(() => {
        throw new BadGatewayException();
      });
    }

    // Query using only the e-mail
    if (!username) {
      const SQL = `${BASE_SQL} WHERE email = ?`;

      return this._entityManager.query(SQL, [email]).catch(() => {
        throw new BadGatewayException();
      });
    }

    // Query using both

    const SQL = `${BASE_SQL} WHERE username = ? AND email = ?`;

    return this._entityManager.query(SQL, [username, email]).catch(() => {
      throw new BadGatewayException();
    });
  }

  async signIn(signInDto: SignInDto): Promise<SignInResponseDto> {
    const { username, email, password } = signInDto;

    const [user] = await this._findUser(username, email);

    if (!user || !user.password) throw new UnauthorizedException();

    const match = await argon2.verify(user.password, password);

    if (!match) throw new UnauthorizedException();

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

    const accessToken = await this._jwtService.generateToken(payload);

    return {
      data: { accessToken },
    };
  }

  async signInWithGoogle(
    signInWithGoogleDto: SignInWithGoogleDto,
  ): Promise<SignInResponseDto> {
    const { googleToken: idToken } = signInWithGoogleDto;

    const { googleId, email } = await this._googleAuthService.verify(idToken);

    const SQL =
      'SELECT person_id AS id, google_id AS googleId FROM user WHERE google_id = ?';

    let [user] = await this._entityManager.query(SQL, [googleId]).catch(() => {
      throw new BadGatewayException();
    });

    if (!user) {
      const SQL =
        'SELECT person_id AS id, google_id AS googleId FROM user WHERE email = ?';

      [user] = await this._entityManager.query(SQL, [email]).catch(() => {
        throw new BadGatewayException();
      });

      if (user) {
        const { id } = user;

        const UPDATE_GOOGLE_ID_SQL =
          'UPDATE user SET google_id = ? WHERE person_id = ?';

        await this._entityManager
          .query(UPDATE_GOOGLE_ID_SQL, [googleId, id])
          .catch(() => {
            throw new BadGatewayException();
          });
      }
    }

    if (!user) throw new ForbiddenException();

    const payload: Payload = {
      id: user.id,
      googleId: user.googleId,
    };

    const accessToken = await this._jwtService.generateToken(payload);

    return {
      data: { accessToken },
    };
  }
}
