import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
import { PublicApiCryptoService } from '../crypto/public-api-crypto.service';

type PublicApiRequest = Request & { publicApiEncrypted?: boolean };

@Injectable()
export class PublicApiKeyGuard implements CanActivate {
  constructor(
    private readonly config: ConfigService,
    private readonly crypto: PublicApiCryptoService,
    private readonly jwt: JwtService,
  ) {}

  canActivate(context: ExecutionContext): boolean {
    const expectedKey = this.config.get<string>('PUBLIC_API_KEY');
    if (!expectedKey) throw new UnauthorizedException('Public API key is not configured');

    const request = context.switchToHttp().getRequest<PublicApiRequest>();
    request.publicApiEncrypted = false;
    const headerKey = request.header('x-api-key');
    const encryptedHeaderKey = request.header('x-api-key-enc');
    const authHeader = request.header('authorization');
    if (authHeader?.startsWith('Bearer ') && this.isValidJwt(authHeader.slice('Bearer '.length))) return true;

    const bearerKey = authHeader?.startsWith('ApiKey ') ? authHeader.slice('ApiKey '.length) : undefined;
    const encryptedBearerKey = authHeader?.startsWith('EncryptedApiKey ') ? authHeader.slice('EncryptedApiKey '.length) : undefined;
    const decryptedHeaderKey = encryptedHeaderKey ? this.crypto.decrypt<string>(encryptedHeaderKey) : undefined;
    const decryptedApiKeyHeader = headerKey ? this.crypto.tryDecrypt(headerKey) : undefined;
    const decryptedBearerKey = encryptedBearerKey ? this.crypto.decrypt<string>(encryptedBearerKey) : undefined;

    if (
      decryptedHeaderKey === expectedKey ||
      decryptedApiKeyHeader === expectedKey ||
      decryptedBearerKey === expectedKey
    ) {
      request.publicApiEncrypted = true;
      return true;
    }

    if (this.config.get<string>('PUBLIC_API_PLAINTEXT_ALLOWED') === 'true' && (headerKey === expectedKey || bearerKey === expectedKey)) {
      return true;
    }
    throw new UnauthorizedException('Invalid API key');
  }

  private isValidJwt(token: string) {
    try {
      this.jwt.verify(token, { secret: this.config.getOrThrow<string>('JWT_ACCESS_SECRET') });
      return true;
    } catch {
      return false;
    }
  }
}
