/**
 * Evaluation Gate Service
 * 评估门控引擎 — 加载管理员配置的规则，在 agent loop 终止前执行检查
 *
 * 设计原则：
 * - 零规则 = 零检查 = 当前行为完全保留
 * - 规则从数据库加载，内存缓存 5 分钟
 * - 每条规则的评估器是纯函数，不访问数据库
 * - 多规则失败时取最高严重性动作
 */

import { Injectable, Inject, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Anthropic from '@anthropic-ai/sdk';
import {
  IEvaluationRuleRepository,
  EVALUATION_RULE_REPOSITORY,
} from '../../../domain/repositories/evaluation-rule.repository.interface';
import {
  EvaluationRuleEntity,
  EvaluationRuleType,
  EvaluationFailureAction,
  EvaluationFailureActionValue,
  getHighestSeverityAction,
} from '../../../domain/entities/evaluation-rule.entity';

// ============================================================
// Types
// ============================================================

/** Context available for rule evaluation */
export interface EvaluationContext {
  stage: string | null;
  collectedInfo: Record<string, unknown> | null;
  assessmentResult: {
    topRecommended: string[];
    suitabilityScore: number;
  } | null;
  responseText: string;
  turnCount: number;
  messageCount: number;
  hasConverted: boolean;
  agentsUsed: string[];
  /** 用户原始消息 — LLM_JUDGE 需要用来评估回复的相关性 */
  userMessage?: string;
}

/** Result of a single rule check */
export interface RuleCheckResult {
  ruleId: string;
  ruleName: string;
  ruleType: string;
  passed: boolean;
  failureAction: string;
  message?: string;
}

/** Aggregate gate result */
export interface GateResult {
  passed: boolean;
  results: RuleCheckResult[];
  action: 'PASS' | 'RETRY' | 'SUPPLEMENT' | 'WARN_AND_PASS' | 'ESCALATE';
  feedbackForModel?: string;
}

// ============================================================
// Service
// ============================================================

@Injectable()
export class EvaluationGateService {
  private readonly logger = new Logger(EvaluationGateService.name);
  private cache = new Map<string, { rules: EvaluationRuleEntity[]; expiresAt: number }>();
  private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
  private anthropicClient: Anthropic;

  constructor(
    @Inject(EVALUATION_RULE_REPOSITORY)
    private readonly repo: IEvaluationRuleRepository,
    private readonly configService: ConfigService,
  ) {
    this.anthropicClient = new Anthropic({
      apiKey: this.configService.get<string>('ANTHROPIC_API_KEY'),
      baseURL: this.configService.get<string>('ANTHROPIC_BASE_URL') || undefined,
    });
  }

  /**
   * Main entry: evaluate all applicable rules
   * Returns passed=true if no rules exist or all pass
   */
  async evaluate(tenantId: string | null, context: EvaluationContext): Promise<GateResult> {
    const stage = context.stage || '*';
    const rules = await this.loadRules(tenantId, stage);

    // Zero rules = zero checks
    if (rules.length === 0) {
      return { passed: true, results: [], action: 'PASS' };
    }

    const results: RuleCheckResult[] = [];

    for (const rule of rules) {
      const check = await this.evaluateRule(rule, context);
      results.push({
        ruleId: rule.id,
        ruleName: rule.name,
        ruleType: rule.ruleType,
        passed: check.passed,
        failureAction: rule.failureAction,
        message: check.message,
      });
    }

    const failedResults = results.filter(r => !r.passed);

    if (failedResults.length === 0) {
      return { passed: true, results, action: 'PASS' };
    }

    const failedActions = failedResults.map(
      r => r.failureAction as EvaluationFailureActionValue,
    );
    const highestAction = getHighestSeverityAction(failedActions);

    const feedbackForModel = this.buildFeedback(failedResults, highestAction);

    this.logger.warn(
      `Evaluation gate: ${failedResults.length}/${results.length} rules failed, action=${highestAction}, stage=${stage}`,
    );

    return {
      passed: false,
      results,
      action: highestAction,
      feedbackForModel,
    };
  }

  /** Clear cached rules (e.g., after admin updates) */
  clearCache(tenantId?: string): void {
    if (tenantId) {
      for (const key of this.cache.keys()) {
        if (key.startsWith(tenantId)) {
          this.cache.delete(key);
        }
      }
    } else {
      this.cache.clear();
    }
  }

  // ============================================================
  // Rule Loading (with cache)
  // ============================================================

  private async loadRules(
    tenantId: string | null,
    stage: string,
  ): Promise<EvaluationRuleEntity[]> {
    const cacheKey = `${tenantId || 'global'}:${stage}`;
    const cached = this.cache.get(cacheKey);

    if (cached && cached.expiresAt > Date.now()) {
      return cached.rules;
    }

    try {
      const rules = await this.repo.findByTenantAndStage(tenantId, stage);
      this.cache.set(cacheKey, {
        rules,
        expiresAt: Date.now() + this.CACHE_TTL,
      });
      return rules;
    } catch (error) {
      this.logger.error(`Failed to load evaluation rules: ${error}`);
      // On error, return empty = no checks = safe fallback
      return [];
    }
  }

  // ============================================================
  // Rule Evaluation (pure functions)
  // ============================================================

  private async evaluateRule(
    rule: EvaluationRuleEntity,
    context: EvaluationContext,
  ): Promise<{ passed: boolean; message?: string }> {
    switch (rule.ruleType) {
      case EvaluationRuleType.FIELD_COMPLETENESS:
        return this.checkFieldCompleteness(rule.config, context);
      case EvaluationRuleType.ASSESSMENT_QUALITY:
        return this.checkAssessmentQuality(rule.config, context);
      case EvaluationRuleType.RESPONSE_LENGTH:
        return this.checkResponseLength(rule.config, context);
      case EvaluationRuleType.MUST_CONTAIN:
        return this.checkMustContain(rule.config, context);
      case EvaluationRuleType.STAGE_MIN_TURNS:
        return this.checkStageMinTurns(rule.config, context);
      case EvaluationRuleType.CONVERSION_SIGNAL:
        return this.checkConversionSignal(rule.config, context);
      case EvaluationRuleType.TOPIC_BOUNDARY:
        return this.checkTopicBoundary(rule.config, context);
      case EvaluationRuleType.NO_FABRICATION:
        return this.checkNoFabrication(rule.config, context);
      case EvaluationRuleType.LLM_JUDGE:
        return this.checkLlmJudge(rule.config, context);
      default:
        this.logger.warn(`Unknown rule type: ${rule.ruleType}`);
        return { passed: true };
    }
  }

  /**
   * FIELD_COMPLETENESS: Check if required fields exist in collectedInfo
   * config: { fields: string[], minRatio: number }
   */
  private checkFieldCompleteness(
    config: Record<string, unknown>,
    context: EvaluationContext,
  ): { passed: boolean; message?: string } {
    const fields = (config.fields as string[]) || [];
    const minRatio = (config.minRatio as number) ?? 1.0;

    if (fields.length === 0) return { passed: true };

    const info = context.collectedInfo || {};
    const presentFields: string[] = [];
    const missingFields: string[] = [];

    for (const field of fields) {
      const value = info[field];
      if (value !== undefined && value !== null && value !== '') {
        presentFields.push(field);
      } else {
        missingFields.push(field);
      }
    }

    const ratio = presentFields.length / fields.length;
    const passed = ratio >= minRatio;

    return {
      passed,
      message: passed
        ? undefined
        : `信息完整度不足（${presentFields.length}/${fields.length}=${(ratio * 100).toFixed(0)}%，要求≥${(minRatio * 100).toFixed(0)}%），缺少: ${missingFields.join(', ')}`,
    };
  }

  /**
   * ASSESSMENT_QUALITY: Check assessment result quality
   * config: { minCategories?: number, minConfidence?: number, minScore?: number }
   */
  private checkAssessmentQuality(
    config: Record<string, unknown>,
    context: EvaluationContext,
  ): { passed: boolean; message?: string } {
    const minCategories = (config.minCategories as number) ?? 1;
    const minScore = (config.minScore as number) ?? 0;

    if (!context.assessmentResult) {
      return {
        passed: false,
        message: `尚未完成评估（要求至少评估 ${minCategories} 个类别）`,
      };
    }

    const categoryCount = context.assessmentResult.topRecommended?.length || 0;
    if (categoryCount < minCategories) {
      return {
        passed: false,
        message: `推荐类别数不足（${categoryCount}/${minCategories}）`,
      };
    }

    if (context.assessmentResult.suitabilityScore < minScore) {
      return {
        passed: false,
        message: `适配分数过低（${context.assessmentResult.suitabilityScore}/${minScore}）`,
      };
    }

    return { passed: true };
  }

  /**
   * RESPONSE_LENGTH: Check response text length
   * config: { min?: number, max?: number }
   */
  private checkResponseLength(
    config: Record<string, unknown>,
    context: EvaluationContext,
  ): { passed: boolean; message?: string } {
    const min = (config.min as number) ?? 0;
    const max = (config.max as number) ?? Infinity;
    const length = context.responseText.length;

    if (length < min) {
      return {
        passed: false,
        message: `回复过短（${length}字，最少${min}字）`,
      };
    }

    if (length > max) {
      return {
        passed: false,
        message: `回复过长（${length}字，最多${max}字）`,
      };
    }

    return { passed: true };
  }

  /**
   * MUST_CONTAIN: Check if response contains required keywords
   * config: { keywords: string[], mode: 'any' | 'all' }
   */
  private checkMustContain(
    config: Record<string, unknown>,
    context: EvaluationContext,
  ): { passed: boolean; message?: string } {
    const keywords = (config.keywords as string[]) || [];
    const mode = (config.mode as string) || 'any';

    if (keywords.length === 0) return { passed: true };

    const text = context.responseText;
    const foundKeywords = keywords.filter(kw => text.includes(kw));
    const missingKeywords = keywords.filter(kw => !text.includes(kw));

    if (mode === 'all') {
      const passed = missingKeywords.length === 0;
      return {
        passed,
        message: passed
          ? undefined
          : `回复缺少必需关键词: ${missingKeywords.join(', ')}`,
      };
    }

    // mode === 'any'
    const passed = foundKeywords.length > 0;
    return {
      passed,
      message: passed
        ? undefined
        : `回复未包含任何期望关键词: ${keywords.join(', ')}`,
    };
  }

  /**
   * STAGE_MIN_TURNS: Check minimum conversation turns
   * config: { minTurns: number }
   */
  private checkStageMinTurns(
    config: Record<string, unknown>,
    context: EvaluationContext,
  ): { passed: boolean; message?: string } {
    const minTurns = (config.minTurns as number) ?? 1;
    const passed = context.turnCount >= minTurns;

    return {
      passed,
      message: passed
        ? undefined
        : `当前轮次不足（${context.turnCount}/${minTurns}轮）`,
    };
  }

  /**
   * CONVERSION_SIGNAL: Check for conversion-related content
   * config: { keywords?: string[] }
   */
  private checkConversionSignal(
    config: Record<string, unknown>,
    context: EvaluationContext,
  ): { passed: boolean; message?: string } {
    const defaultKeywords = ['下一步', '预约', '联系', '咨询', '费用', '付款', '签约'];
    const keywords = (config.keywords as string[]) || defaultKeywords;

    const text = context.responseText;
    const found = keywords.some(kw => text.includes(kw));

    return {
      passed: found,
      message: found
        ? undefined
        : '回复中缺少转化引导内容（如下一步行动、预约、联系方式等）',
    };
  }

  /**
   * TOPIC_BOUNDARY: 检测回复是否偏离移民咨询范围
   * config: { offTopicPatterns?: string[], maxOffTopicSentences?: number }
   */
  private checkTopicBoundary(
    config: Record<string, unknown>,
    context: EvaluationContext,
  ): { passed: boolean; message?: string } {
    const defaultPatterns = [
      '```[\\s\\S]*?```',              // 代码块
      'def |function |class |import ', // 编程关键字
      '我是一个AI|我是人工智能|作为AI助手|作为语言模型', // AI身份暴露
      '这个问题超出了我的范围|我无法回答这个问题',      // 自认偏题但仍长篇回答
    ];
    const patterns = (config.offTopicPatterns as string[]) || defaultPatterns;
    const maxOffTopic = (config.maxOffTopicSentences as number) ?? 0;

    const text = context.responseText;
    const matchedPatterns: string[] = [];

    for (const pattern of patterns) {
      try {
        const regex = new RegExp(pattern, 'i');
        if (regex.test(text)) {
          matchedPatterns.push(pattern);
        }
      } catch {
        // Invalid regex pattern — skip
      }
    }

    if (matchedPatterns.length <= maxOffTopic) {
      return { passed: true };
    }

    return {
      passed: false,
      message: `回复可能偏离移民咨询范围（检测到 ${matchedPatterns.length} 个偏题特征）。请确保回复聚焦于香港移民相关内容。`,
    };
  }

  /**
   * NO_FABRICATION: 检测回复是否包含无来源的政策断言
   * config: { policyClaimPatterns?: string[], requiredSourceMarkers?: string[], agentsMustBeUsed?: string[] }
   */
  private checkNoFabrication(
    config: Record<string, unknown>,
    context: EvaluationContext,
  ): { passed: boolean; message?: string } {
    const defaultClaimPatterns = [
      '根据.*规定',
      '按照.*政策',
      '入境处要求',
      '最低年薪.*万',
      '需要.*年工作经验',
      '评分.*分',
      '配额.*名',
    ];
    const claimPatterns = (config.policyClaimPatterns as string[]) || defaultClaimPatterns;
    const sourceMarkers = (config.requiredSourceMarkers as string[]) || [
      '[来源:', '知识库', '官方信息', '入境处', 'immd.gov.hk',
      '基于AI', '仅供参考', '内置知识',
    ];
    const requiredAgents = (config.agentsMustBeUsed as string[]) || ['policy_expert', 'invoke_policy_expert'];

    const text = context.responseText;

    // 检测是否包含政策性断言
    let hasPolicyClaim = false;
    for (const pattern of claimPatterns) {
      try {
        const regex = new RegExp(pattern, 'i');
        if (regex.test(text)) {
          hasPolicyClaim = true;
          break;
        }
      } catch {
        // Invalid regex — skip
      }
    }

    if (!hasPolicyClaim) {
      return { passed: true }; // 没有政策断言，无需检查来源
    }

    // 有政策断言 → 检查是否调用了 policy_expert
    const usedRequiredAgent = requiredAgents.some(agent =>
      context.agentsUsed.includes(agent),
    );
    if (usedRequiredAgent) {
      return { passed: true }; // 调用了政策专家，说明有知识库来源
    }

    // 也没调用 policy_expert → 检查回复中是否有来源标记
    const hasSourceMarker = sourceMarkers.some(marker => text.includes(marker));
    if (hasSourceMarker) {
      return { passed: true }; // 有来源标记，说明已标注
    }

    return {
      passed: false,
      message: '回复中包含政策性断言但未调用政策专家且未标注信息来源。请通过 invoke_policy_expert 查询知识库，或明确标注信息来源。',
    };
  }

  /**
   * LLM_JUDGE: 使用 Haiku 4.5 作为评审员，语义评估回复质量
   * config: { minRelevance?: number, minConciseness?: number, maxNoise?: number, timeoutMs?: number }
   */
  private async checkLlmJudge(
    config: Record<string, unknown>,
    context: EvaluationContext,
  ): Promise<{ passed: boolean; message?: string }> {
    const minRelevance = (config.minRelevance as number) ?? 7;
    const minConciseness = (config.minConciseness as number) ?? 6;
    const maxNoise = (config.maxNoise as number) ?? 3;
    const timeoutMs = (config.timeoutMs as number) ?? 5000;

    if (!context.userMessage || !context.responseText) {
      return { passed: true }; // 缺少信息时跳过
    }

    const judgePrompt = `评估AI回复质量。打分0-10：
- relevance: 是否直接回答了用户的问题（0=完全无关，10=精准回答）
- conciseness: 是否足够简洁（0=极度冗长，10=言简意赅）
- noise: 是否包含用户没问的多余信息（0=无噪音，10=全是噪音）

用户消息: ${context.userMessage}
AI回复: ${context.responseText}

以JSON输出: {"relevance":N,"conciseness":N,"noise":N,"reason":"一句话原因"}`;

    try {
      const response = await Promise.race([
        this.anthropicClient.messages.create({
          model: 'claude-haiku-4-5-20251001',
          max_tokens: 200,
          messages: [{ role: 'user', content: judgePrompt }],
        }),
        new Promise<never>((_, reject) =>
          setTimeout(() => reject(new Error('LLM Judge timeout')), timeoutMs),
        ),
      ]);

      const text = response.content[0]?.type === 'text' ? response.content[0].text : '';
      // 提取 JSON（Haiku 可能在 JSON 前后包裹 markdown 代码块）
      const jsonMatch = text.match(/\{[\s\S]*\}/);
      if (!jsonMatch) {
        this.logger.warn(`LLM Judge returned non-JSON: ${text.substring(0, 100)}`);
        return { passed: true };
      }
      const scores = JSON.parse(jsonMatch[0]);

      const passed =
        scores.relevance >= minRelevance &&
        scores.conciseness >= minConciseness &&
        scores.noise <= maxNoise;

      if (!passed) {
        this.logger.debug(
          `LLM Judge failed: relevance=${scores.relevance}, conciseness=${scores.conciseness}, noise=${scores.noise}. ${scores.reason || ''}`,
        );
      }

      return {
        passed,
        message: passed
          ? undefined
          : `LLM评审未通过: 相关性=${scores.relevance}/10, 简洁度=${scores.conciseness}/10, 噪音=${scores.noise}/10. ${scores.reason || ''}`,
      };
    } catch (error) {
      this.logger.warn(`LLM Judge error (non-fatal): ${error}`);
      return { passed: true }; // 失败时默认通过（非阻塞）
    }
  }

  // ============================================================
  // Feedback Builder
  // ============================================================

  private buildFeedback(
    failedResults: RuleCheckResult[],
    action: EvaluationFailureActionValue,
  ): string {
    const lines = ['[系统评估] 回复未通过质量检查：'];

    for (const result of failedResults) {
      lines.push(`- [${result.ruleName}] ${result.message || '未通过'}`);
    }

    if (action === EvaluationFailureAction.RETRY) {
      lines.push('请根据以上反馈重新生成回复。');
    } else if (action === EvaluationFailureAction.SUPPLEMENT) {
      lines.push('请在回复中补充缺失的信息后再回复用户。');
    }

    return lines.join('\n');
  }
}
