import {
  CanActivate,
  ExecutionContext, Inject,
  Injectable,
  Logger,
  OnApplicationBootstrap,
} from '@nestjs/common';
import * as jwt from 'jsonwebtoken';
import { Client } from 'node-appwrite';
import { AppwriteUnauthorizedException } from '../exceptions/appwrite-unauthorized.exception';
import { CONFIG_PROVIDER_NAME, INVALID_TOKEN } from '../appwrite.constants';
import { SecretStoreService } from '../service/secret-store.service';
import { SignTokenPayload } from '../domain/sign-token-payload';
import { ClientWithToken } from '../domain/client-with-token';
import { Config } from '../config/config.interface';

const JWT_SECRET = 'jwt_secret';
const BEARER_PREFIX = 'Bearer ';

@Injectable()
export class AccessTokenGuard implements CanActivate, OnApplicationBootstrap {

  private readonly logger = new Logger(AccessTokenGuard.name);
  private jwtSecret: string | undefined;

  constructor(
    private readonly storeService: SecretStoreService,
    @Inject(CONFIG_PROVIDER_NAME) private readonly config: Config,
  ) {
  }

  public async onApplicationBootstrap(): Promise<void> {
    this.jwtSecret = await this.storeService.getSecretString(JWT_SECRET);
  }

  public async canActivate(context: ExecutionContext): Promise<boolean> {
    if (!this.jwtSecret) {
      this.logger.error(`Secret ${JWT_SECRET} is undefined, unable to verify tokens`);
      throw new AppwriteUnauthorizedException(INVALID_TOKEN, 'Unable to validate the token');
    }

    const req = context.switchToHttp().getRequest();
    const { authorization } = req.headers;

    if (!authorization) {
      throw new AppwriteUnauthorizedException(INVALID_TOKEN, 'Missing authorization header');
    }

    const formattedToken = authorization?.replace(BEARER_PREFIX, '') ?? '';
    try {
      const decodedToken = jwt.verify(formattedToken, this.jwtSecret) as SignTokenPayload;
      const client = new Client()
        .setEndpoint(this.config.APPWRITE_ENDPOINT)
        .setProject(this.config.APPWRITE_PROJECT_ID)
        .setJWT(formattedToken);
      (client as ClientWithToken).decodedToken = decodedToken;

      req.body.client = client;
      return true;
    } catch (err) {
      throw new AppwriteUnauthorizedException(INVALID_TOKEN, 'Invalid authorization token');
    }
  }
}
