import { Injectable, Logger } from '@nestjs/common';
import type { FeynmanEvaluationResult } from '../ai/prompts/schemas/feynman-evaluation.schema';

// ═══════════════════════════════════════════════════════════════════════════
// 验证错误类型（与 ActiveRecall 共享错误类）
// ═══════════════════════════════════════════════════════════════════════════

import {
  BusinessValidationError,
  ReferenceValidationError,
} from './active-recall-validator';

export { BusinessValidationError, ReferenceValidationError };

// ═══════════════════════════════════════════════════════════════════════════
// Feynman Business Validator
// ═══════════════════════════════════════════════════════════════════════════

/**
 * M-AI-05-03: Feynman Business Validator
 *
 * 验证 AI 输出符合业务约束（基于 M-AI-05-01 冻结的输出 Schema）。
 *
 * 契约依据：docs/architecture/m-ai-05-feynman-migration-contract.md §4.3
 *
 * 检查项：
 * - score: 整数, [0, 100]
 * - clarityLevel: 合法枚举值
 * - summary: 非空, 1–2000 字符
 * - strengths/weaknesses/blindSpots/suggestions: 数组长度 ≤ 10, 每项 ≤ 500 字符
 * - isBeginnerFriendly: boolean
 * - analogyQuality: 可选合法枚举
 * - jargonUsage: 合法枚举
 * - 禁止空对象冒充成功
 * - 禁止异常大文本（单项 > 500 字符）
 * - 禁止模型指令或代码块进入结构化字段
 */

const VALID_CLARITY_LEVELS = [
  'crystal_clear', 'clear', 'mostly_clear', 'confusing', 'very_confusing',
] as const;

const VALID_ANALOGY_QUALITIES = [
  'excellent', 'good', 'acceptable', 'poor', 'none',
] as const;

const VALID_JARGON_USAGE = [
  'none', 'minimal', 'moderate', 'heavy',
] as const;

/** 检测 markdown 代码块包装（模型有时会将 JSON 输出包裹在 ```json 中） */
const CODE_BLOCK_PATTERN = /```(?:json|javascript|js|python)?[\s\S]*?```/;
/** 检测模型指令痕迹（如 "Here is the evaluation..." 等前缀） */
const MODEL_INSTRUCTION_PATTERNS = [
  /^here\s+(is|are)\s+(the|your)\s/i,
  /^the\s+(following|evaluation|analysis)\s/i,
  /^(certainly|sure|of course)[,;!\s]/i,
  /^(I|we)\s+(hope|trust|believe|think)\s/i,
];

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

  /**
   * 验证业务规则。
   *
   * @param output - AiGatewayService 解析后的输出（已通过 Zod schema.parse）
   * @throws BusinessValidationError 业务规则违反
   */
  validate(output: FeynmanEvaluationResult): void {
    const violations: string[] = [];

    // ── score ──
    if (typeof output.score !== 'number' || !Number.isInteger(output.score)) {
      violations.push('score must be an integer');
    } else if (output.score < 0 || output.score > 100) {
      violations.push(`score ${output.score} out of range [0, 100]`);
    }

    // ── clarityLevel ──
    if (!VALID_CLARITY_LEVELS.includes(output.clarityLevel as any)) {
      violations.push(
        `clarityLevel "${output.clarityLevel}" invalid, must be one of: ${VALID_CLARITY_LEVELS.join(', ')}`,
      );
    }

    // ── summary ──
    if (!output.summary || typeof output.summary !== 'string' || output.summary.trim().length === 0) {
      violations.push('summary is required and must be non-empty');
    } else if (output.summary.length > 2000) {
      violations.push(`summary length ${output.summary.length} exceeds 2000`);
    }

    // ── strengths ──
    this.validateStringArray(output.strengths, 'strengths', 10, 500, violations);

    // ── weaknesses ──
    this.validateStringArray(output.weaknesses, 'weaknesses', 10, 500, violations);

    // ── blindSpots ──
    this.validateStringArray(output.blindSpots, 'blindSpots', 10, 500, violations);

    // ── suggestions ──
    this.validateStringArray(output.suggestions, 'suggestions', 10, 500, violations);

    // ── isBeginnerFriendly ──
    if (typeof output.isBeginnerFriendly !== 'boolean') {
      violations.push('isBeginnerFriendly must be a boolean');
    }

    // ── analogyQuality (optional) ──
    if (output.analogyQuality !== undefined && output.analogyQuality !== null) {
      if (!VALID_ANALOGY_QUALITIES.includes(output.analogyQuality as any)) {
        violations.push(
          `analogyQuality "${output.analogyQuality}" invalid, must be one of: ${VALID_ANALOGY_QUALITIES.join(', ')}`,
        );
      }
    }

    // ── jargonUsage ──
    if (!VALID_JARGON_USAGE.includes(output.jargonUsage as any)) {
      violations.push(
        `jargonUsage "${output.jargonUsage}" invalid, must be one of: ${VALID_JARGON_USAGE.join(', ')}`,
      );
    }

    // ── 禁止空对象冒充成功 ──
    const scoreExists = typeof output.score === 'number';
    const summaryExists = typeof output.summary === 'string' && output.summary.trim().length > 0;
    if (!scoreExists && !summaryExists) {
      violations.push('output appears to be an empty/placeholder object');
    }

    // ── 禁止异常大文本（通过 Zod max 后二次检查） ──
    const allTextFields: string[] = [
      output.summary || '',
      ...(output.strengths || []),
      ...(output.weaknesses || []),
      ...(output.blindSpots || []),
      ...(output.suggestions || []),
    ];
    for (const text of allTextFields) {
      if (typeof text === 'string' && text.length > 2000) {
        violations.push(`text field exceeds 2000 characters: "${text.substring(0, 80)}..."`);
        break;
      }
    }

    // ── 禁止模型指令或代码块进入结构化字段 ──
    const allFields = { ...output };
    for (const [key, value] of Object.entries(allFields)) {
      if (typeof value === 'string') {
        if (CODE_BLOCK_PATTERN.test(value)) {
          violations.push(`field "${key}" contains code block — likely raw model output`);
        }
        for (const pattern of MODEL_INSTRUCTION_PATTERNS) {
          if (pattern.test(value.trim())) {
            violations.push(
              `field "${key}" contains model instruction pattern: "${value.substring(0, 60)}..."`,
            );
            break;
          }
        }
      }
      if (Array.isArray(value)) {
        for (const item of value) {
          if (typeof item === 'string' && CODE_BLOCK_PATTERN.test(item)) {
            violations.push(`field "${key}" array item contains code block — likely raw model output`);
            break;
          }
        }
      }
    }

    if (violations.length > 0) {
      this.logger.warn(
        `Business validation failed: ${violations.length} violation(s): ${violations.join('; ')}`,
      );
      throw new BusinessValidationError(
        `Business validation failed: ${violations.length} violation(s)`,
        violations,
      );
    }

    this.logger.log('Feynman business validation passed');
  }

  // ── Private Helpers ──

  private validateStringArray(
    arr: any,
    fieldName: string,
    maxItems: number,
    maxLength: number,
    violations: string[],
  ): void {
    if (!Array.isArray(arr)) {
      violations.push(`${fieldName} must be an array`);
      return;
    }
    if (arr.length > maxItems) {
      violations.push(`${fieldName} max ${maxItems} items, got ${arr.length}`);
    }
    for (let i = 0; i < arr.length; i++) {
      if (typeof arr[i] !== 'string') {
        violations.push(`${fieldName}[${i}] must be a string`);
      } else if (arr[i].length > maxLength) {
        violations.push(`${fieldName}[${i}] length ${arr[i].length} exceeds ${maxLength}`);
      }
    }
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// Feynman Reference Validator
// ═══════════════════════════════════════════════════════════════════════════

/**
 * M-AI-05-03: Feynman Reference Validator
 *
 * 契约依据：docs/architecture/m-ai-05-feynman-migration-contract.md §4.3
 *
 * 当前 Feynman 输出 Schema 不含显式引用字段（如 sourceReferences），
 * 因此参考验证聚焦于：
 * 1. 输出不得包含 URL/email（AI 不应生成外部引用）
 * 2. 输出文本字段不含跨用户标识
 *
 * 待未来输出 Schema 增加显式引用字段后扩展。
 */

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

  /**
   * 验证输出不包含跨用户/无效引用。
   *
   * @param output - 已验证业务规则的输出
   * @param _snapshot - 输入快照（当前未使用，保留接口兼容）
   * @throws ReferenceValidationError 引用验证失败
   */
  validate(
    output: FeynmanEvaluationResult,
    _snapshot?: { userId: string; knowledgeItemId: string },
  ): void {
    const violations: string[] = [];

    // 检查所有文本字段不包含 URL/email
    const textFields: string[] = [
      output.summary || '',
      ...(output.strengths || []),
      ...(output.weaknesses || []),
      ...(output.blindSpots || []),
      ...(output.suggestions || []),
    ];

    for (const text of textFields) {
      if (typeof text !== 'string') continue;

      // 检测 URL（可能指向外部资源或其他用户数据）
      if (text.match(/https?:\/\/[^\s]{4,}/)) {
        violations.push(
          `Output contains URL reference: "${text.substring(0, 80)}..."`,
        );
      }

      // 检测 email（明确的 PII 泄露）
      if (text.match(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/)) {
        violations.push(
          `Output contains email reference: "${text.substring(0, 80)}..."`,
        );
      }
    }

    // 检查 summary 不包含 URL/email
    if (output.summary && typeof output.summary === 'string') {
      if (output.summary.match(/https?:\/\/[^\s]{4,}/)) {
        violations.push('summary contains URL reference');
      }
      if (output.summary.match(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/)) {
        violations.push('summary contains email reference');
      }
    }

    if (violations.length > 0) {
      this.logger.warn(
        `Reference validation failed: ${violations.length} violation(s): ${violations.join('; ')}`,
      );
      throw new ReferenceValidationError(
        `Reference validation failed: ${violations.length} violation(s)`,
        violations,
      );
    }

    this.logger.log('Feynman reference validation passed');
  }
}
