import {
	applyDecorators,
	CanActivate,
	ExecutionContext,
	Injectable,
	SetMetadata,
	UnauthorizedException,
	UseGuards,
} from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { FastifyRequest } from 'fastify'
import { AuthStrategyFactory } from '../strategy'
import { AUTH_STRATEGY_KEY, AuthMethod } from '../types'

/**
 * Decorator to specify which authentication method is supported for a route/controller.
 * @param method Authentication method
 *
 * @example
 * @UseAuth(AuthMethod.JWT)
 */
export function UseAuth(method: AuthMethod): MethodDecorator & ClassDecorator {
	return applyDecorators(SetMetadata(AUTH_STRATEGY_KEY, method), UseGuards(AuthGuard))
}

/**
 * This guard is strategy-agnostic and delegates authentication to registered strategies.
 * Use the @UseAuth() decorator on controllers/routes to specify supported strategies.
 */
@Injectable()
export class AuthGuard implements CanActivate {
	constructor(
		private readonly _reflector: Reflector,
		private readonly _strategyFactory: AuthStrategyFactory,
	) {}

	async canActivate(context: ExecutionContext): Promise<boolean> {
		const request = context.switchToHttp().getRequest<FastifyRequest>()

		// Get supported auth method from decorator metadata
		const authMethod =
			this._reflector.get<AuthMethod>(AUTH_STRATEGY_KEY, context.getHandler()) ||
			this._reflector.get<AuthMethod>(AUTH_STRATEGY_KEY, context.getClass())

		if (!authMethod) throw new UnauthorizedException()

		const authStrategy = this._strategyFactory.createStrategy(authMethod)
		const payload = await authStrategy.validate(request)

		if (payload) return true

		throw new UnauthorizedException()
	}
}
