import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { Request } from 'express';
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
import { AuthService } from '../auth.service';
import { UsersService } from 'src/users/users.service';
import { RolesService } from 'src/roles/roles.service';

interface AuthTokens {
  access_token: string,
  refresh_token: string
}

@Injectable()
export class RefreshTokenStrategy extends PassportStrategy(Strategy, 'refreshToken') {
  constructor(
    private authService: AuthService,
    private moduleRef: ModuleRef
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromExtractors([
        // For API client requests (server-server)
        // https://docs.nestjs.com/recipes/passport#implementing-passport-jwt
        //ExtractJwt.fromAuthHeaderAsBearerToken(),
        // For web client requests (client-server)
        // https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie
        // https://github.com/mikenicholson/passport-jwt#writing-a-custom-extractor-function
        // https://medium.com/lightrail/getting-token-authentication-right-in-a-stateless-single-page-application-57d0c6474e3
        // https://security.stackexchange.com/questions/226906/double-jwt-submit-method
        function doubleSubmitCookieWithBearerExtractor(req) {
          return this.authService.parseReqToken(req, true);
        }
      ]),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_REFRESH_TOKEN_SECRET,
      // to access req from validate()
      passReqToCallback: true,
    });
  }

  async validate(
    req: Request,
    payload: {
      sub: number;
      username: string;
      user_class_id: number;
    }
  ): Promise<AuthTokens | UnauthorizedException> {
    // "AuthService" is a request-scoped provider
    // https://docs.nestjs.com/recipes/passport#request-scoped-strategies
    const contextId = ContextIdFactory.getByRequest(req);
    const authService = await this.moduleRef.resolve(AuthService, contextId);
    const rolesService = await this.moduleRef.resolve(RolesService, contextId);

    // Get the refresh token
    const refresh_token =  this.authService.parseReqToken(req, true);
    // console.log('accessToken refresh_token', refresh_token);
    if (!refresh_token) throw new UnauthorizedException();

    // Validate the existing token, obtain a new refresh token and client cookies
    const { sub: user_id, username, user_class_id } = payload;
    const roles = await rolesService.findNamesByUserId(user_id);
    const tokens = await authService.refreshTokens(user_id, username, user_class_id, roles, refresh_token);
    // console.log('accessToken tokens', tokens);
    if (!tokens) throw new UnauthorizedException();

    // Add the tokens to req.user
    return { access_token: tokens.access_token, refresh_token: tokens.refresh_token };
  }
}
