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';

/**
 * DashScopeRealtimeProxy — Qwen-Omni-Realtime(阿里云百炼)实时会话代理。
 *
 * 手动观察模式(POC 已实测):
 *   session.update { modalities:['text'], instructions, input_audio_format:'pcm16', turn_detection:null }
 *   逐帧 input_audio_buffer.append;患者停顿(无新音频 ≥600ms)→ commit + response.create;
 *   收 response.text.delta(流式)/ response.text.done。
 * DASHSCOPE key 只在这里出现(出站 header),浏览器永不接触。
 */
@Injectable()
export class DashScopeRealtimeProxy implements RealtimeProvider {
  private readonly logger = new Logger(DashScopeRealtimeProxy.name);
  /** 患者停顿这么久(无新音频)→ 算说完一句 → 触发一次提示(turn-end,而非固定时钟) */
  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.dashscopeApiKey) {
      throw new Error('DASHSCOPE_API_KEY 未设置 — 实时教练不可用');
    }
    const url = `wss://dashscope.aliyuncs.com/api-ws/v1/realtime?model=${ai.qwenOmniModel}`;
    const ws = new WebSocket(url, { headers: { Authorization: `bearer ${ai.dashscopeApiKey}` } });

    let sentence = '';
    let tickTimer: ReturnType<typeof setInterval> | null = null;
    let hasUncommitted = false; // 自上次 commit 以来有没有新音频(一句话在攒)
    let lastAppendAt = 0; // 最近一帧音频时间(测停顿)
    let utteranceStartAt = 0; // 当前这句开始时间(测超长)

    await new Promise<void>((resolve, reject) => {
      ws.once('open', () => {
        ws.send(
          JSON.stringify({
            type: 'session.update',
            session: {
              modalities: ['text'],
              instructions,
              input_audio_format: 'pcm16',
              turn_detection: null, // 关自动轮次,由本代理按"患者停顿"触发
            },
          }),
        );
        // turn-end 触发:攒着的音频,等患者停顿 ≥TURN_SILENCE_MS(或单句超长)→ commit + 一次 response
        tickTimer = setInterval(() => {
          if (ws.readyState !== WebSocket.OPEN || !hasUncommitted) return;
          const now = Date.now();
          const silenceGap = now - lastAppendAt;
          const uttDur = now - utteranceStartAt;
          if (
            silenceGap >= DashScopeRealtimeProxy.TURN_SILENCE_MS ||
            uttDur >= DashScopeRealtimeProxy.MAX_UTTERANCE_MS
          ) {
            ws.send(JSON.stringify({ type: 'input_audio_buffer.commit' }));
            ws.send(JSON.stringify({ type: 'response.create' }));
            hasUncommitted = false;
          }
        }, DashScopeRealtimeProxy.TICK_MS);
        resolve();
      });
      ws.once('error', (e) => reject(e instanceof Error ? e : new Error(String(e))));
    });

    ws.on('message', (data: WebSocket.RawData) => {
      let evt: { type?: string; delta?: string; error?: { message?: string } };
      try {
        evt = JSON.parse(data.toString());
      } catch {
        return;
      }
      switch (evt.type) {
        case 'response.text.delta':
          sentence += evt.delta ?? '';
          handlers.onDelta(evt.delta ?? '');
          break;
        case 'response.text.done':
          handlers.onDone(sentence);
          sentence = '';
          break;
        case 'error': {
          const m = evt.error?.message ?? 'dashscope error';
          // 末尾/静音时 commit 空缓冲会报 "buffer too small" — 瞬时无害,不当 fatal 上报(否则误杀会话)
          if (/buffer too small|have no audio|empty/i.test(m)) {
            this.logger.debug(`benign dashscope notice: ${m}`);
            break;
          }
          handlers.onError(m);
          break;
        }
        default:
          break; // session.updated / response.done 等忽略
      }
    });
    ws.on('close', () => {
      if (tickTimer) clearInterval(tickTimer);
    });
    ws.on('error', (e) => this.logger.warn(`dashscope ws error: ${(e as Error).message}`));

    return {
      appendAudio: (base64Frame) => {
        if (ws.readyState !== WebSocket.OPEN) return;
        ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: base64Frame }));
        const now = Date.now();
        if (!hasUncommitted) utteranceStartAt = now; // 一句话的起点
        hasUncommitted = true;
        lastAppendAt = now;
      },
      close: () => {
        if (tickTimer) clearInterval(tickTimer);
        try {
          ws.close();
        } catch {
          /* ignore */
        }
      },
    };
  }
}
