import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import twilio from 'twilio';
import type { AppConfig } from '../../config/configuration';
import { PrismaService } from '../../prisma/prisma.service';

/**
 * TelephonyService — Twilio 网页点击拨号(Voice JS SDK / WebRTC click-to-call)。
 *
 * 链路:
 *   浏览器 @twilio/voice-sdk Device(用 AccessToken 注册)
 *     → device.connect({ params:{ planId } })
 *     → Twilio 回调 POST /pac/v1/twilio/voice(本服务返回 <Dial> TwiML)
 *     → Twilio 桥接到患者手机(PSTN)
 *
 * 安全:API Key Secret 只服务端持有(签 AccessToken),绝不下发浏览器。
 *      患者真实号码不进浏览器 —— 前端只传 planId,这里查库解析真号再 Dial。
 *
 * ⚠️ Trial 账号只能拨"已验证"号码:设 TWILIO_FORCE_TO=已验证号 → 所有呼叫都打它(联调用);
 *    生产置空 → 走 planId 解析患者真实号(需 Upgrade 解除 trial 限制 + 开 China geo permissions)。
 */
@Injectable()
export class TelephonyService {
  private readonly logger = new Logger(TelephonyService.name);

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

  private cfg() {
    return this.config.get('twilio', { infer: true });
  }

  /** 网页拨号是否已配置(凭证齐全) */
  isEnabled(): boolean {
    const c = this.cfg();
    return Boolean(c.accountSid && c.apiKeySid && c.apiKeySecret && c.twimlAppSid && c.callerId);
  }

  /**
   * 签发 Voice AccessToken(短期凭证,可安全下发浏览器)。
   * identity = 客服 user id(用于 Twilio 端标识这通客户端)。
   */
  createVoiceToken(identity: string): { token: string; identity: string } {
    const c = this.cfg();
    const AccessToken = twilio.jwt.AccessToken;
    const VoiceGrant = AccessToken.VoiceGrant;

    const token = new AccessToken(c.accountSid, c.apiKeySid, c.apiKeySecret, {
      identity,
      ttl: 3600,
    });
    token.addGrant(
      new VoiceGrant({
        outgoingApplicationSid: c.twimlAppSid,
        incomingAllow: false,
      }),
    );
    return { token: token.toJwt(), identity };
  }

  /**
   * 生成出站呼叫的 TwiML(Twilio 回调本端点时返回)。
   * params.planId → 查库解析患者真实手机号;TWILIO_FORCE_TO 设了则强制拨它(trial 联调)。
   */
  async buildDialTwiml(params: { planId?: string }): Promise<string> {
    const c = this.cfg();
    const VoiceResponse = twilio.twiml.VoiceResponse;
    const resp = new VoiceResponse();

    const to = c.forceTo || (params.planId ? await this.resolvePhone(params.planId) : '');
    if (!to) {
      resp.say({ language: 'zh-CN' }, '未找到可拨打的号码');
      return resp.toString();
    }

    const dial = resp.dial({ callerId: c.callerId, answerOnBridge: true });
    dial.number(to);
    this.logger.log(`dial → ${maskPhone(to)} (callerId=${c.callerId}, planId=${params.planId ?? '-'})`);
    return resp.toString();
  }

  /** planId → 患者手机号(真号,只服务端用) */
  private async resolvePhone(planId: string): Promise<string> {
    const plan = await this.prisma.followupPlan.findUnique({
      where: { id: planId },
      select: { patient: { select: { phone: true } } },
    });
    return plan?.patient?.phone ?? '';
  }
}

function maskPhone(p: string): string {
  if (p.length < 7) return '***';
  return `${p.slice(0, 3)}****${p.slice(-4)}`;
}
