import * as http from 'http';
import { createHmac, randomBytes } from 'crypto';
import { WebSocketServer, WebSocket } from 'ws';
import { IVoiceEngine } from '../types';
import { handleTwilioCall } from './bridge';

import { childLogger } from '@server/logger';
const log = childLogger('engine.twilio');

const WS_HMAC_SECRET = process.env.HMAC_WS_SECRET || randomBytes(32).toString('hex');

function timeBucket(): number {
  return Math.floor(Date.now() / 1000 / 300);
}

function computeWsToken(sessionId: string, bucket: number): string {
  return createHmac('sha256', WS_HMAC_SECRET)
    .update(`${sessionId}:${bucket}`)
    .digest('hex');
}

export function generateWsToken(sessionId: string): string {
  return computeWsToken(sessionId, timeBucket());
}

function verifyWsToken(sessionId: string, token: string): boolean {
  const cur = timeBucket();
  return (
    computeWsToken(sessionId, cur) === token ||
    computeWsToken(sessionId, cur - 1) === token
  );
}

export class TwilioRealtimeEngine implements IVoiceEngine {
  attach(server: http.Server): void {
    const wss = new WebSocketServer({ noServer: true });

    server.on('upgrade', (request, socket, head) => {
      const url = new URL(request.url || '/', `http://${request.headers.host}`);

      if (!url.pathname.startsWith('/stream/')) {
        return;
      }

      let sessionId: string;
      try {
        sessionId = decodeURIComponent(url.pathname.slice('/stream/'.length));
      } catch {
        log.warn('malformed session ID encoding in path — rejecting');
        socket.destroy();
        return;
      }
      if (!sessionId) {
        log.warn('upgrade request missing session ID in path — rejecting');
        socket.destroy();
        return;
      }

      const tok = url.searchParams.get('tok') || '';
      if (!tok || !verifyWsToken(sessionId, tok)) {
        log.warn({ sessionId }, 'WS upgrade rejected: missing or invalid HMAC token');
        socket.destroy();
        return;
      }

      wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {
        wss.emit('connection', ws, request);
        handleTwilioCall(ws, sessionId).catch((err) => {
          log.error({ err }, 'unhandled error in handleTwilioCall');
          ws.close();
        });
      });
    });

    log.info('TwilioRealtimeEngine attached — listening for /stream upgrades');
  }
}
