import {
  Controller,
  Post,
  Body,
  Req,
  HttpCode,
  HttpStatus,
  InternalServerErrorException,
  UnauthorizedException,
} from '@nestjs/common';
import { timingSafeEqual } from 'crypto';
import { BillingService } from './billing.service';
import { Logger } from 'pino-nestjs';

type WebhookSource = 'apple' | 'google';

function readWebhookSecret(source: WebhookSource): string {
  if (source === 'apple') {
    return process.env.APPLE_WEBHOOK_SECRET || process.env.BILLING_WEBHOOK_SECRET || '';
  }
  return process.env.GOOGLE_WEBHOOK_SECRET || process.env.BILLING_WEBHOOK_SECRET || '';
}

function extractProvidedSecret(req: any): string {
  const querySecret = req.query?.secret;
  if (typeof querySecret === 'string') {
    const value = querySecret.trim();
    if (value) return value;
  }
  if (Array.isArray(querySecret)) {
    const firstString = querySecret.find((item): item is string => typeof item === 'string');
    if (firstString) {
      const value = firstString.trim();
      if (value) return value;
    }
  }

  const auth = req.headers.authorization || '';
  if (auth.toLowerCase().startsWith('bearer ')) {
    return auth.slice(7).trim();
  }

  const byGenericHeader = req.headers['x-webhook-secret'];
  if (typeof byGenericHeader === 'string') {
    return byGenericHeader.trim();
  }
  if (Array.isArray(byGenericHeader) && byGenericHeader[0]) {
    return byGenericHeader[0].trim();
  }

  return '';
}

function secretsMatch(expected: string, provided: string): boolean {
  const expectedBuf = Buffer.from(expected, 'utf8');
  const providedBuf = Buffer.from(provided, 'utf8');
  if (expectedBuf.length !== providedBuf.length) {
    return false;
  }
  return timingSafeEqual(expectedBuf, providedBuf);
}

@Controller('webhooks')
export class WebhooksController {
  constructor(
    private readonly billingService: BillingService,
    private readonly logger: Logger,
  ) {}

  private assertWebhookSecret(source: WebhookSource, req: any): void {
    const expectedSecret = readWebhookSecret(source);
    if (!expectedSecret) {
      this.logger.error(`[Billing Webhook] Missing ${source.toUpperCase()} webhook secret configuration`);
      throw new InternalServerErrorException('Webhook secret is not configured');
    }

    const providedSecret = extractProvidedSecret(req);
    if (!providedSecret || !secretsMatch(expectedSecret, providedSecret)) {
      throw new UnauthorizedException('Invalid webhook secret');
    }
  }

  @Post('apple')
  @HttpCode(HttpStatus.OK)
  async handleAppleWebhook(@Req() req: any, @Body() body: any) {
    this.assertWebhookSecret('apple', req);
    try {
      this.logger.log('Apple webhook payload received', body);
      return await this.billingService.processWebhook({
        source: 'apple',
        payload: body,
      });
    } catch (error: any) {
      this.logger.error('Apple webhook error', error);
      throw new InternalServerErrorException(error?.message || 'Apple webhook failed');
    }
  }

  @Post('google')
  @HttpCode(HttpStatus.OK)
  async handleGoogleWebhook(@Req() req: any, @Body() body: any) {
    this.assertWebhookSecret('google', req);
    try {
      this.logger.log('Google webhook payload received', body);
      return await this.billingService.processWebhook({
        source: 'google',
        payload: body,
      });
    } catch (error: any) {
      this.logger.error('Google webhook error', error);
      throw new InternalServerErrorException(error?.message || 'Google webhook failed');
    }
  }
}
