import {
  CanActivate,
  ExecutionContext,
  Injectable,
  NotFoundException,
  ServiceUnavailableException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { PolicyService, ActionType, FeatureState } from './PolicyService';
import { FEATURE_AVAILABILITY_METADATA_KEY, FeatureAvailabilityMetadata } from './RequireCapability';

type Evaluation = { allow: true } | { allow: false; reason: 'maintenance' | FeatureState | 'not_configured' };

@Injectable()
export class FeatureAvailabilityGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    private readonly policyService: PolicyService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const handler = context.getHandler();
    const controllerClass = context.getClass();

    const metadata =
      this.reflector.getAllAndOverride<FeatureAvailabilityMetadata | undefined>(
        FEATURE_AVAILABILITY_METADATA_KEY,
        [handler, controllerClass],
      ) ?? null;

    if (!metadata) {
      return true;
    }

    const snapshot = await this.policyService.getSnapshot();
    const decision = evaluate(snapshot, metadata);

    if (decision.allow) {
      return true;
    }

    if (decision.reason === 'maintenance') {
      throw new ServiceUnavailableException('Service temporarily unavailable');
    }

    throw new NotFoundException('Not Found');
  }
}

function evaluate(
  snapshot: Awaited<ReturnType<PolicyService['getSnapshot']>>,
  metadata: FeatureAvailabilityMetadata,
): Evaluation {
  if (snapshot.operationalMode === 'maintenance') {
    const allowlist = metadata.actionType === 'mutate'
      ? snapshot.maintenanceAllowlist.mutate
      : snapshot.maintenanceAllowlist.view;

    if (!allowlist.includes(metadata.capabilityKey)) {
      return { allow: false, reason: 'maintenance' };
    }
  }

  const state = snapshot.capabilities[metadata.capabilityKey] ?? 'hidden';

  if (state === 'enabled') {
    return { allow: true };
  }

  // Coming soon is treated as "not found" on the public API for now (no disclosure).
  if (state === 'coming_soon') {
    return { allow: false, reason: 'coming_soon' };
  }

  if (state === 'disabled' || state === 'hidden') {
    return { allow: false, reason: state };
  }

  return { allow: false, reason: 'not_configured' };
}

export function inferActionTypeFromHttpMethod(method: string): ActionType {
  switch (method.toUpperCase()) {
    case 'GET':
    case 'HEAD':
    case 'OPTIONS':
      return 'view';
    default:
      return 'mutate';
  }
}