import {
  Injectable,
  CanActivate,
  ExecutionContext,
  Scope,
  Logger,
  UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { SessionService, UsersSession } from 'src/core/sessions';
import { UNPROTECTED_URL } from '../constants';
import { PrivilegeService } from './services/privilege.service';
import { AuthService } from './services/auth.service';

@Injectable({ scope: Scope.REQUEST })
export class JWTGuard implements CanActivate {
  constructor(
    protected readonly session: SessionService,
    protected readonly reflector: Reflector,
    protected readonly privilege: PrivilegeService,
    protected readonly authService: AuthService,
  ) {}

  protected isPublic = false;
  protected userSession: UsersSession;

  async canActivate(context: ExecutionContext) {
    /**
     * Check if access url is protected or not
     * By default `isUnprotected` equals `false`
     */
    const isUnprotected = this.reflector.getAllAndOverride<boolean>(
      UNPROTECTED_URL,
      [context.getHandler(), context.getClass()],
    );
    this.isPublic = isUnprotected;
    this.session.setPublic(isUnprotected);
    if (isUnprotected) return true;

    /**
     * Check if request give the token or not
     * If there is not token in request, the transaction is failed
     * and return 401 `Unauthorize`
     */
    const request = context.switchToHttp().getRequest();
    const authorization = request.headers['authorization'];
    if (!authorization)
      throw new UnauthorizedException({
        code: 10002,
        message: 'Access denied, please login first!',
        error: 'TOKEN_NOT_PROVIDE',
      });

    const [, token] = authorization.split(' ');

    /**
     * Verify if token is valid token from login
     * if the token is'nt valid token, the transaction is failed
     * and return 401
     */
    try {
      this.userSession = this.session.verifyToken(token);
      await this.authService.verifyRegisteredLoginToken(token);

      Logger.log(`Access from ${this.userSession.name}`, 'AuthGuard');
      return true;
    } catch (error) {
      const expiredError = error.message;
      if (expiredError === 'jwt expired') {
        const [, body] = token.split('.');
        const bodyToken = JSON.parse(atob(body));

        const user = {
          role: bodyToken.role,
          user_id: bodyToken.id,
          username: bodyToken.username,
          user_privilege_id: bodyToken.user_privilege_id,
          item_id: bodyToken.item_id,
          item_name: bodyToken.item_name,
          source: bodyToken.source,
        };

        this.authService.logoutUser(user, token);
      }

      throw new UnauthorizedException({
        code: 10001,
        message:
          'You cant access this endpoint, because authentication inst valid',
        error: 'INVALID_TOKEN',
      });
    }
  }
}
