import { Injectable, Logger } from '@nestjs/common';
import type { ActiveRecallAnalysisResult } from '../ai/prompts/schemas/active-recall-analysis.schema';

// ═══════════════════════════════════════════════════════════════════════════
// 验证错误类型
// ═══════════════════════════════════════════════════════════════════════════

export class BusinessValidationError extends Error {
  public readonly code = 'business_validation_failed';
  constructor(
    message: string,
    public readonly violations: string[],
  ) {
    super(message);
    this.name = 'BusinessValidationError';
  }
}

export class ReferenceValidationError extends Error {
  public readonly code = 'reference_validation_failed';
  constructor(
    message: string,
    public readonly violations: string[],
  ) {
    super(message);
    this.name = 'ReferenceValidationError';
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// BusinessValidator
// ═══════════════════════════════════════════════════════════════════════════

/**
 * M-AI-04-03: Active Recall Business Validator
 *
 * 验证 AI 输出符合业务约束（基于 M-AI-04-01 冻结的输出 Schema）。
 *
 * 检查项：
 * - score: 整数, [0, 100]
 * - masteryLevel: 合法枚举值
 * - summary: 非空, 1–2000 字符
 * - strengths/weaknesses: 数组长度 ≤ 10, 每项 ≤ 500 字符
 * - missingKeyPoints: 数组长度 ≤ 20, 每项 ≤ 500 字符
 * - misconceptions: 数组长度 ≤ 10, 每项 ≤ 500 字符
 * - weaknessTypes: 合法枚举值
 * - focusItems: 数组长度 ≤ 10, 每项 title/reason 非空
 * - reviewSuggestion: 必填, intervalDays [1, 365]
 * - 拒绝空对象冒充成功结果
 */

const VALID_MASTERY_LEVELS = ['excellent', 'good', 'partial', 'weak', 'none'] as const;
const VALID_WEAKNESS_TYPES = [
  'missing_detail', 'missing_application', 'misconception',
  'vague_expression', 'incomplete_structure', 'wrong_emphasis',
] as const;
const VALID_PRIORITIES = ['high', 'normal', 'low'] as const;

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

  /**
   * 验证业务规则。
   *
   * @param output - AiGatewayService 解析后的输出（已通过 Zod schema.parse）
   * @throws BusinessValidationError 业务规则违反
   */
  validate(output: ActiveRecallAnalysisResult): 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]`);
    }

    // ── masteryLevel ──
    if (!VALID_MASTERY_LEVELS.includes(output.masteryLevel as any)) {
      violations.push(
        `masteryLevel "${output.masteryLevel}" invalid, must be one of: ${VALID_MASTERY_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 ──
    if (!Array.isArray(output.strengths)) {
      violations.push('strengths must be an array');
    } else {
      if (output.strengths.length > 10) violations.push('strengths max 10 items');
      for (let i = 0; i < output.strengths.length; i++) {
        if (typeof output.strengths[i] !== 'string' || output.strengths[i].length > 500) {
          violations.push(`strengths[${i}] must be string ≤ 500 chars`);
        }
      }
    }

    // ── weaknesses ──
    if (!Array.isArray(output.weaknesses)) {
      violations.push('weaknesses must be an array');
    } else {
      if (output.weaknesses.length > 10) violations.push('weaknesses max 10 items');
      for (let i = 0; i < output.weaknesses.length; i++) {
        if (typeof output.weaknesses[i] !== 'string' || output.weaknesses[i].length > 500) {
          violations.push(`weaknesses[${i}] must be string ≤ 500 chars`);
        }
      }
    }

    // ── missingKeyPoints ──
    if (!Array.isArray(output.missingKeyPoints)) {
      violations.push('missingKeyPoints must be an array');
    } else {
      if (output.missingKeyPoints.length > 20) violations.push('missingKeyPoints max 20 items');
      for (let i = 0; i < output.missingKeyPoints.length; i++) {
        if (typeof output.missingKeyPoints[i] !== 'string' || output.missingKeyPoints[i].length > 500) {
          violations.push(`missingKeyPoints[${i}] must be string ≤ 500 chars`);
        }
      }
    }

    // ── misconceptions ──
    if (!Array.isArray(output.misconceptions)) {
      violations.push('misconceptions must be an array');
    } else {
      if (output.misconceptions.length > 10) violations.push('misconceptions max 10 items');
      for (let i = 0; i < output.misconceptions.length; i++) {
        if (typeof output.misconceptions[i] !== 'string' || output.misconceptions[i].length > 500) {
          violations.push(`misconceptions[${i}] must be string ≤ 500 chars`);
        }
      }
    }

    // ── weaknessTypes ──
    if (!Array.isArray(output.weaknessTypes)) {
      violations.push('weaknessTypes must be an array');
    } else {
      if (output.weaknessTypes.length > 10) violations.push('weaknessTypes max 10 items');
      for (let i = 0; i < output.weaknessTypes.length; i++) {
        if (!VALID_WEAKNESS_TYPES.includes(output.weaknessTypes[i] as any)) {
          violations.push(
            `weaknessTypes[${i}] "${output.weaknessTypes[i]}" invalid, must be one of: ${VALID_WEAKNESS_TYPES.join(', ')}`,
          );
        }
      }
    }

    // ── focusItems ──
    if (!Array.isArray(output.focusItems)) {
      violations.push('focusItems must be an array');
    } else {
      if (output.focusItems.length > 10) violations.push('focusItems max 10 items');
      for (let i = 0; i < output.focusItems.length; i++) {
        const fi = output.focusItems[i];
        if (!fi || typeof fi !== 'object') {
          violations.push(`focusItems[${i}] must be an object`);
          continue;
        }
        if (!fi.title || typeof fi.title !== 'string' || fi.title.trim().length === 0 || fi.title.length > 255) {
          violations.push(`focusItems[${i}].title must be non-empty string ≤ 255 chars`);
        }
        if (!fi.reason || typeof fi.reason !== 'string' || fi.reason.trim().length === 0 || fi.reason.length > 1000) {
          violations.push(`focusItems[${i}].reason must be non-empty string ≤ 1000 chars`);
        }
        if (fi.priority && !VALID_PRIORITIES.includes(fi.priority as any)) {
          violations.push(`focusItems[${i}].priority "${fi.priority}" invalid`);
        }
      }
    }

    // ── reviewSuggestion ──
    if (!output.reviewSuggestion || typeof output.reviewSuggestion !== 'object') {
      violations.push('reviewSuggestion is required');
    } else {
      const rs = output.reviewSuggestion;
      if (typeof rs.shouldReview !== 'boolean') {
        violations.push('reviewSuggestion.shouldReview must be boolean');
      }
      if (typeof rs.intervalDays !== 'number' || !Number.isInteger(rs.intervalDays) ||
          rs.intervalDays < 1 || rs.intervalDays > 365) {
        violations.push(`reviewSuggestion.intervalDays must be integer in [1, 365]`);
      }
    }

    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('Business validation passed');
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// ReferenceValidator
// ═══════════════════════════════════════════════════════════════════════════

/**
 * M-AI-04-03: Active Recall Reference Validator
 *
 * 验证 AI 输出引用的完整性。
 *
 * 当前输出 Schema 不含显式引用 ID 字段，因此参考验证聚焦于：
 * 1. 输出不得包含跨用户数据引用（检查字段无外部 userId/foreign key 痕迹）
 * 2. focusItems 的 title 不包含 URL/路径格式的引用
 * 3. missingKeyPoints 不包含其他用户标识
 *
 * 待未来输出 Schema 增加显式引用字段（如 sourceReferences）后扩展。
 */

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

  /**
   * 验证输出不包含跨用户/无效引用。
   *
   * @param output - 已验证业务规则的输出
   * @param snapshot - 输入快照（用于验证引用范围）
   * @throws ReferenceValidationError 引用验证失败
   */
  validate(output: ActiveRecallAnalysisResult, snapshot: { userId: string; activeRecallId: string }): void {
    const violations: string[] = [];

    // 检查输出文本字段不包含跨用户数据泄露
    const textFields = [
      ...(output.strengths || []),
      ...(output.weaknesses || []),
      ...(output.missingKeyPoints || []),
      ...(output.misconceptions || []),
    ];

    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)}..."`,
        );
      }
    }

    // 检查 focusItems 的 title 不包含 URL
    for (let i = 0; i < (output.focusItems || []).length; i++) {
      const fi = output.focusItems[i];
      const title = fi?.title || '';
      if (title.match(/https?:\/\//)) {
        violations.push(`focusItems[${i}].title contains URL reference`);
      }
    }

    // 检查 summary 不包含 URL/email（AI 输出不应引用外部资源）
    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('Reference validation passed');
  }
}
