import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import WebSocket from 'ws';
import type { AppConfig } from '../../config/configuration';
import type { CoachProxyHandle, ProxyHandlers, RealtimeProvider } from './realtime-provider.types';

/**
 * GeminiLiveProxy — Gemini Live(BidiGenerateContent)实时会话代理。
 *
 * 协议与 Qwen 不同(POC 已实测):
 *   - WS: .../BidiGenerateContent?key=KEY(key 走 query,只服务端)
 *   - setup: responseModalities=['AUDIO'](native-audio 模型不支持 TEXT 输出),
 *     开 outputAudioTranscription → 取模型"说"的提示的【文字转写】;关自动 VAD,手动 activity 控轮
 *   - 每句:activityStart → realtimeInput.audio 逐帧 → activityEnd(触发本轮)
 *   - 输出:serverContent.outputTranscription.text(流式);generationComplete=一句完成
 */
@Injectable()
export class GeminiLiveProxy implements RealtimeProvider {
  private readonly logger = new Logger(GeminiLiveProxy.name);
  private static readonly TURN_SILENCE_MS = 600;
  private static readonly MAX_UTTERANCE_MS = 15000;
  private static readonly TICK_MS = 200;

  constructor(private readonly config: ConfigService<AppConfig, true>) {}

  async open(instructions: string, handlers: ProxyHandlers): Promise<CoachProxyHandle> {
    const ai = this.config.get('ai', { infer: true });
    if (!ai.geminiApiKey) throw new Error('GEMINI_API_KEY 未设置 — Gemini Live 不可用');

    const url =
      `wss://generativelanguage.googleapis.com/ws/` +
      `google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent?key=${ai.geminiApiKey}`;
    const ws = new WebSocket(url);

    let sentence = '';
    let inUtterance = false; // 当前在一句话内(已 activityStart,未 activityEnd)
    let lastAppendAt = 0;
    let utteranceStartAt = 0;
    let tickTimer: ReturnType<typeof setInterval> | null = null;

    const endUtterance = () => {
      if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ realtimeInput: { activityEnd: {} } }));
      inUtterance = false;
    };

    // 等 setupComplete 才算就绪
    await new Promise<void>((resolve, reject) => {
      const onSetup = (data: WebSocket.RawData) => {
        let m: { setupComplete?: unknown };
        try {
          m = JSON.parse(data.toString());
        } catch {
          return;
        }
        if (m.setupComplete) {
          ws.off('message', onSetup);
          resolve();
        }
      };
      ws.once('open', () => {
        ws.send(
          JSON.stringify({
            setup: {
              model: `models/${ai.geminiLiveModel}`,
              generationConfig: { responseModalities: ['AUDIO'] },
              systemInstruction: { parts: [{ text: instructions }] },
              outputAudioTranscription: {},
              inputAudioTranscription: {},
              realtimeInputConfig: { automaticActivityDetection: { disabled: true } },
            },
          }),
        );
        tickTimer = setInterval(() => {
          if (!inUtterance) return;
          const now = Date.now();
          if (
            now - lastAppendAt >= GeminiLiveProxy.TURN_SILENCE_MS ||
            now - utteranceStartAt >= GeminiLiveProxy.MAX_UTTERANCE_MS
          ) {
            endUtterance();
          }
        }, GeminiLiveProxy.TICK_MS);
        ws.on('message', onSetup);
      });
      ws.once('error', (e) => reject(e instanceof Error ? e : new Error(String(e))));
    });

    // 业务消息处理
    ws.on('message', (data: WebSocket.RawData) => {
      let m: {
        serverContent?: {
          outputTranscription?: { text?: string };
          generationComplete?: boolean;
          turnComplete?: boolean;
        };
        sessionResumptionUpdate?: unknown;
      };
      try {
        m = JSON.parse(data.toString());
      } catch {
        return;
      }
      const sc = m.serverContent;
      if (!sc) return; // sessionResumptionUpdate 等忽略
      if (sc.outputTranscription?.text) {
        sentence += sc.outputTranscription.text;
        handlers.onDelta(sc.outputTranscription.text);
      }
      if (sc.generationComplete || sc.turnComplete) {
        if (sentence.trim()) handlers.onDone(sentence);
        sentence = '';
      }
    });
    ws.on('close', () => {
      if (tickTimer) clearInterval(tickTimer);
    });
    ws.on('error', (e) => this.logger.warn(`gemini live ws error: ${(e as Error).message}`));

    return {
      appendAudio: (base64Frame) => {
        if (ws.readyState !== WebSocket.OPEN) return;
        if (!inUtterance) {
          ws.send(JSON.stringify({ realtimeInput: { activityStart: {} } }));
          inUtterance = true;
          utteranceStartAt = Date.now();
        }
        ws.send(
          JSON.stringify({
            realtimeInput: { audio: { data: base64Frame, mimeType: 'audio/pcm;rate=16000' } },
          }),
        );
        lastAppendAt = Date.now();
      },
      close: () => {
        if (tickTimer) clearInterval(tickTimer);
        try {
          ws.close();
        } catch {
          /* ignore */
        }
      },
    };
  }
}
