import {
  Controller,
  Post,
  Headers,
  Body,
  RawBodyRequest,
  Req,
  HttpCode,
  HttpStatus,
  Logger,
  ForbiddenException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { createHmac, timingSafeEqual } from 'crypto';
import { Request } from 'express';
import { TwitchEventService } from './twitch-event.service';

@Controller('twitch')
export class TwitchWebhookController {
  private readonly logger = new Logger(TwitchWebhookController.name);

  constructor(
    private readonly configService: ConfigService,
    private readonly twitchEventService: TwitchEventService,
  ) {}

  @Post('webhook')
  @HttpCode(HttpStatus.OK)
  async handleWebhook(
    @Req() req: RawBodyRequest<Request>,
    @Headers('twitch-eventsub-message-id') messageId: string,
    @Headers('twitch-eventsub-message-timestamp') timestamp: string,
    @Headers('twitch-eventsub-message-signature') signature: string,
    @Headers('twitch-eventsub-message-type') messageType: string,
    @Body() body: Record<string, unknown>,
  ): Promise<{ status: string }> {
    // Validate HMAC-SHA256 signature — security: validate-then-verify pattern
    this.verifySignature(req, messageId, timestamp, signature);

    // Twitch webhook verification challenge
    if (messageType === 'webhook_callback_verification') {
      return { status: body['challenge'] as string };
    }

    if (messageType === 'revocation') {
      this.logger.warn(`Subscription revoked for messageId=${messageId}`);
      return { status: 'ok' };
    }

    if (messageType !== 'notification') {
      return { status: 'ok' };
    }

    const subscription = body['subscription'] as Record<string, unknown>;
    const event = body['event'] as Record<string, unknown>;
    const eventType = (subscription?.['type'] as string) ?? '';

    switch (eventType) {
      case 'channel.cheer':
        await this.handleCheer(messageId, event);
        break;

      case 'channel.subscribe':
        await this.handleSubscribe(messageId, event);
        break;

      case 'channel.subscription.gift':
        await this.handleGiftSub(messageId, event);
        break;

      default:
        this.logger.debug(`Unhandled event type: ${eventType}`);
    }

    return { status: 'ok' };
  }

  // ─── Private helpers ──────────────────────────────────────────────────────

  private verifySignature(
    req: RawBodyRequest<Request>,
    messageId: string,
    timestamp: string,
    signature: string,
  ): void {
    const secret = this.configService.get<string>('TWITCH_WEBHOOK_SECRET');
    if (!secret) throw new ForbiddenException('Webhook secret not configured');

    const rawBody = req.rawBody;
    if (!rawBody) throw new ForbiddenException('Raw body unavailable');

    const hmacMessage = `${messageId}${timestamp}${rawBody.toString('utf8')}`;
    const expected = `sha256=${createHmac('sha256', secret).update(hmacMessage).digest('hex')}`;

    // timingSafeEqual prevents timing attacks
    const sigBuf = Buffer.from(signature ?? '');
    const expBuf = Buffer.from(expected);

    if (sigBuf.length !== expBuf.length || !timingSafeEqual(sigBuf, expBuf)) {
      this.logger.warn(`Invalid signature for messageId=${messageId}`);
      throw new ForbiddenException('Invalid webhook signature');
    }
  }

  private async handleCheer(messageId: string, event: Record<string, unknown>): Promise<void> {
    const userId = event['user_id'] as string;
    const bits = Number(event['bits_used'] ?? 0);
    await this.twitchEventService.handleCheer(messageId, userId, bits);
  }

  private async handleSubscribe(messageId: string, event: Record<string, unknown>): Promise<void> {
    const userId = event['user_id'] as string;
    const tier = event['tier'] as string;
    await this.twitchEventService.handleSubscription(messageId, userId, tier, false);
  }

  private async handleGiftSub(messageId: string, event: Record<string, unknown>): Promise<void> {
    const gifterUserId = event['user_id'] as string;
    const tier = event['tier'] as string;
    // For gifts, gifter earns TC
    await this.twitchEventService.handleSubscription(messageId, gifterUserId, tier, true, gifterUserId);
  }
}
