import { Injectable, Logger } from '@nestjs/common';

/**
 * SafetyGate — LLM 输出后置校验(harness 必备)。
 *
 * 检查项(随 call数量增长扩展):
 *   - PII 泄漏(姓名 / 手机号 / 身份证残留)
 *   - 合规违规(承诺疗效 / 绝对化用语 / 缺少 opt-out)
 *   - 业务边界(牙位号 11-48、评分值域、字数上限)
 *   - 模板槽位完整(必填字段缺失)
 *
 * 设计:
 *   - 每个 rule 返回 {pass, severity, message}
 *   - severity='block' → 直接抛 SafetyGateRejectError(让 ai-call-runner 走 fallback)
 *   - severity='warn'  → 记日志不阻断
 *   - 全部 rule 通过返回 ok
 *
 * v1 框架先建好,具体 rule 随 call上线逐个加(YAGNI)。
 */
export interface SafetyRule<T> {
  name: string;
  severity: 'block' | 'warn';
  check(output: T, context: SafetyContext): SafetyCheckResult;
}

export interface SafetyContext {
  callKey: string;
  promptVersion: string;
  tenantId: string;
}

export interface SafetyCheckResult {
  pass: boolean;
  message?: string;
}

export class SafetyGateRejectError extends Error {
  constructor(
    public readonly callKey: string,
    public readonly failures: Array<{ rule: string; message: string }>,
  ) {
    super(
      `SafetyGate rejected call=${callKey}: ${failures.map((f) => `${f.rule}(${f.message})`).join('; ')}`,
    );
  }
}

@Injectable()
export class SafetyGateService {
  private readonly logger = new Logger(SafetyGateService.name);

  /**
   * 跑一组 rules;block 失败抛错,warn 失败只记日志。
   * call在自身定义里挂自己的 rules,runner 调本方法。
   */
  check<T>(output: T, rules: ReadonlyArray<SafetyRule<T>>, context: SafetyContext): void {
    const blockFailures: Array<{ rule: string; message: string }> = [];

    for (const rule of rules) {
      const result = rule.check(output, context);
      if (result.pass) continue;

      const message = result.message ?? '(no message)';
      if (rule.severity === 'block') {
        blockFailures.push({ rule: rule.name, message });
      } else {
        this.logger.warn(
          `SafetyGate warn: call=${context.callKey} rule=${rule.name} msg=${message}`,
        );
      }
    }

    if (blockFailures.length > 0) {
      throw new SafetyGateRejectError(context.callKey, blockFailures);
    }
  }
}
