import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import type { FastifyRequest } from 'fastify';
import type Redis from 'ioredis';

import {
  INTEGRATION_HEADERS,
  canonicalQuery,
  isIntegrationMtlsEnabled,
  verifyRequestSignature,
} from '@hexahost/integration-auth';

import { PrismaService } from '../../prisma/prisma.service';

import { assertIntegrationMtls } from './integration-mtls.util';
import { INTEGRATIONS_REDIS } from './whmcs-integration.constants';

export type WhmcsInstallationContext = {
  id: string;
  integrationId: string;
  apiSecret: string;
};

export type WhmcsAuthenticatedRequest = FastifyRequest & {
  whmcsInstallation?: WhmcsInstallationContext;
  rawBody?: string;
};

@Injectable()
export class WhmcsIntegrationGuard implements CanActivate {
  constructor(
    private readonly prisma: PrismaService,
    @Inject(INTEGRATIONS_REDIS) private readonly redis: Redis,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<WhmcsAuthenticatedRequest>();
    const integrationId = headerValue(request, INTEGRATION_HEADERS.integrationId);
    const timestamp = headerValue(request, INTEGRATION_HEADERS.timestamp);
    const nonce = headerValue(request, INTEGRATION_HEADERS.nonce);
    const idempotencyKey = headerValue(request, INTEGRATION_HEADERS.idempotencyKey);
    const signature = headerValue(request, INTEGRATION_HEADERS.signature);

    if (!integrationId || !timestamp || !nonce || !idempotencyKey || !signature) {
      throw new UnauthorizedException('Missing integration authentication headers');
    }

    const installation = await this.prisma.whmcsInstallation.findFirst({
      where: { integrationId, isActive: true },
      select: {
        id: true,
        integrationId: true,
        apiSecret: true,
        mtlsClientFingerprint: true,
      },
    });

    if (!installation) {
      throw new UnauthorizedException('Unknown integration installation');
    }

    const nonceKey = `integration:nonce:${installation.id}:${nonce}`;
    const nonceSet = await this.redis.set(nonceKey, '1', 'EX', 600, 'NX');
    if (nonceSet !== 'OK') {
      throw new UnauthorizedException('Replayed integration nonce');
    }

    const body = typeof request.rawBody === 'string' ? request.rawBody : JSON.stringify(request.body ?? {});
    const query = canonicalQuery(
      request.query as Record<string, string | string[] | undefined>,
    );

    const valid = verifyRequestSignature(
      installation.apiSecret,
      {
        method: request.method,
        path: request.url.split('?')[0] ?? request.url,
        query,
        body,
        timestamp,
        nonce,
        idempotencyKey,
      },
      signature,
    );

    if (!valid) {
      throw new UnauthorizedException('Invalid integration signature');
    }

    if (isIntegrationMtlsEnabled()) {
      try {
        assertIntegrationMtls(request, installation.mtlsClientFingerprint);
      } catch (error) {
        const code = error instanceof Error ? error.message : 'INTEGRATION_MTLS_FAILED';
        throw new UnauthorizedException(code);
      }
    }

    request.whmcsInstallation = {
      id: installation.id,
      integrationId: installation.integrationId,
      apiSecret: installation.apiSecret,
    };
    return true;
  }
}

function headerValue(request: FastifyRequest, header: string): string | undefined {
  const value = request.headers[header];
  return typeof value === 'string' ? value : undefined;
}
