/**
 * Context Injector Service
 * 动态上下文注入系统 — 参考 Claude Code 的 Attachment 系统
 *
 * 在每轮 API 调用前，自动注入最新的上下文信息：
 * 1. 用户历史记忆
 * 2. 已收集的用户信息
 * 3. 对话统计
 * 4. 评估结果
 * 5. 相关知识预检索
 * 6. 系统经验
 * 7. 设备上下文
 * 8. Agent 调用历史
 */

import { Injectable, Logger } from '@nestjs/common';
import Anthropic from '@anthropic-ai/sdk';
import {
  ContextType,
  ContextInjectionBlock,
  ContextInjectionResult,
  ContextInjectorConfig,
  ConversationContext,
  CONTEXT_PRIORITY,
  CONTEXT_CACHE_TTL,
  DEFAULT_CONTEXT_CONFIG,
} from '../types/context.types';
import { ClaudeMessage } from '../types/agent.types';
import { KnowledgeClientService } from '../../knowledge/knowledge-client.service';

@Injectable()
export class ContextInjectorService {
  private readonly logger = new Logger(ContextInjectorService.name);
  private cache = new Map<string, { data: any; timestamp: number }>();
  private config: ContextInjectorConfig = DEFAULT_CONTEXT_CONFIG;

  constructor(
    private readonly knowledgeClient: KnowledgeClientService,
  ) {}

  /**
   * Main entry: inject context into messages before API call
   * 在 API 调用前注入上下文到消息中
   */
  async inject(
    context: ConversationContext,
    existingMessages: ClaudeMessage[],
  ): Promise<ClaudeMessage[]> {
    const blocks = await this.gatherContextBlocks(context);
    const injectionResult = this.prioritizeAndTrim(blocks);

    if (injectionResult.blocks.length === 0) {
      return existingMessages;
    }

    this.logger.debug(
      `Injecting ${injectionResult.blocks.length} context blocks ` +
      `(~${injectionResult.totalEstimatedTokens} tokens). ` +
      `Dropped: ${injectionResult.droppedContexts.join(', ') || 'none'}`,
    );

    // Inject as a system-reminder in the last user message
    // (similar to Claude Code's <system-reminder> pattern)
    return this.injectIntoMessages(existingMessages, injectionResult.injectionText);
  }

  /**
   * Gather all context blocks
   */
  private async gatherContextBlocks(
    context: ConversationContext,
  ): Promise<ContextInjectionBlock[]> {
    const blocks: ContextInjectionBlock[] = [];
    const enabledTypes = this.config.enabledContextTypes;

    // Parallel fetch all enabled context types
    const promises: Array<Promise<ContextInjectionBlock | null>> = [];

    if (enabledTypes.includes(ContextType.COLLECTED_INFO)) {
      promises.push(this.buildCollectedInfoBlock(context));
    }
    if (enabledTypes.includes(ContextType.CONVERSATION_STATS)) {
      promises.push(this.buildConversationStatsBlock(context));
    }
    if (enabledTypes.includes(ContextType.ASSESSMENT_RESULT)) {
      promises.push(this.buildAssessmentResultBlock(context));
    }
    if (enabledTypes.includes(ContextType.USER_MEMORY)) {
      promises.push(this.buildUserMemoryBlock(context));
    }
    if (enabledTypes.includes(ContextType.DEVICE_CONTEXT)) {
      promises.push(this.buildDeviceContextBlock(context));
    }
    if (enabledTypes.includes(ContextType.RELEVANT_KNOWLEDGE)) {
      promises.push(this.buildRelevantKnowledgeBlock(context));
    }
    if (enabledTypes.includes(ContextType.SIMILAR_EXPERIENCES)) {
      promises.push(this.buildSimilarExperiencesBlock(context));
    }
    if (enabledTypes.includes(ContextType.AGENT_HISTORY)) {
      promises.push(Promise.resolve(this.buildAgentHistoryBlock(context)));
    }

    const results = await Promise.allSettled(promises);

    for (const result of results) {
      if (result.status === 'fulfilled' && result.value) {
        blocks.push(result.value);
      } else if (result.status === 'rejected') {
        this.logger.warn(`Context block failed: ${result.reason}`);
      }
    }

    return blocks;
  }

  // ============================================================
  // Individual Context Block Builders
  // ============================================================

  private async buildCollectedInfoBlock(
    ctx: ConversationContext,
  ): Promise<ContextInjectionBlock | null> {
    const info = ctx.consultingState?.collectedInfo;
    if (!info || Object.keys(info).length === 0) return null;

    const allFields = [
      'age', 'education', 'university', 'major', 'workYears',
      'industry', 'jobTitle', 'annualIncome', 'immigrationPurpose',
      'timeline', 'hasHKConnection',
    ];
    const collected = Object.keys(info);
    const missing = allFields.filter(f => !collected.includes(f));
    const completionRate = collected.length / allFields.length;

    const content = [
      `<collected_info>`,
      `已收集的用户信息 (完成度: ${Math.round(completionRate * 100)}%):`,
      ...Object.entries(info).map(([k, v]) => `  - ${k}: ${v}`),
      missing.length > 0 ? `\n尚未收集: ${missing.join(', ')}` : '',
      `</collected_info>`,
    ].filter(Boolean).join('\n');

    return {
      contextType: ContextType.COLLECTED_INFO,
      priority: CONTEXT_PRIORITY[ContextType.COLLECTED_INFO],
      content,
      estimatedTokens: Math.ceil(content.length / 3),
      fromCache: false,
    };
  }

  private async buildConversationStatsBlock(
    ctx: ConversationContext,
  ): Promise<ContextInjectionBlock | null> {
    const msgs = ctx.messages;
    if (msgs.length < 2) return null;

    const userMsgs = msgs.filter(m => m.role === 'user').length;
    const assistantMsgs = msgs.filter(m => m.role === 'assistant').length;
    const firstMsgTime = msgs[0]?.createdAt;
    const durationMin = firstMsgTime
      ? Math.round((Date.now() - new Date(firstMsgTime).getTime()) / 60000)
      : 0;

    const content = [
      `<conversation_stats>`,
      `对话统计: ${userMsgs} 条用户消息, ${assistantMsgs} 条助手消息, 持续 ${durationMin} 分钟`,
      `当前阶段: ${ctx.consultingState?.currentStage || '未知'}`,
      `</conversation_stats>`,
    ].join('\n');

    return {
      contextType: ContextType.CONVERSATION_STATS,
      priority: CONTEXT_PRIORITY[ContextType.CONVERSATION_STATS],
      content,
      estimatedTokens: Math.ceil(content.length / 3),
      fromCache: false,
    };
  }

  private async buildAssessmentResultBlock(
    ctx: ConversationContext,
  ): Promise<ContextInjectionBlock | null> {
    const result = ctx.consultingState?.assessmentResult;
    if (!result) return null;

    const content = [
      `<assessment_result>`,
      `已完成的资格评估:`,
      `  推荐类别: ${(result as any).topRecommended?.join(', ') || '无'}`,
      `  综合评分: ${(result as any).suitabilityScore || 'N/A'}/100`,
      `  摘要: ${(result as any).summary || '无'}`,
      `</assessment_result>`,
    ].join('\n');

    return {
      contextType: ContextType.ASSESSMENT_RESULT,
      priority: CONTEXT_PRIORITY[ContextType.ASSESSMENT_RESULT],
      content,
      estimatedTokens: Math.ceil(content.length / 3),
      fromCache: false,
    };
  }

  private async buildUserMemoryBlock(
    ctx: ConversationContext,
  ): Promise<ContextInjectionBlock | null> {
    const cacheKey = `user_memory_${ctx.userId}`;
    const cached = this.getFromCache(cacheKey, ContextType.USER_MEMORY);
    if (cached) {
      return { ...cached, fromCache: true };
    }

    try {
      const memories = await this.knowledgeClient.getUserTopMemories(ctx.userId, 10);
      if (!memories || memories.length === 0) return null;

      const content = [
        `<user_memory>`,
        `用户历史记忆 (${memories.length} 条):`,
        ...memories.map(m =>
          `  - [${m.memoryType}] ${m.content} (重要度: ${m.importance})`
        ),
        `</user_memory>`,
      ].join('\n');

      const block: ContextInjectionBlock = {
        contextType: ContextType.USER_MEMORY,
        priority: CONTEXT_PRIORITY[ContextType.USER_MEMORY],
        content,
        estimatedTokens: Math.ceil(content.length / 3),
        fromCache: false,
      };

      this.setCache(cacheKey, block, ContextType.USER_MEMORY);
      return block;
    } catch (error) {
      this.logger.warn(`Failed to load user memory: ${error}`);
      return null;
    }
  }

  private async buildDeviceContextBlock(
    ctx: ConversationContext,
  ): Promise<ContextInjectionBlock | null> {
    if (!ctx.deviceInfo) return null;

    // Only inject for first few messages
    if (ctx.messages.length > 4) return null;

    const content = [
      `<device_context>`,
      `用户设备信息:`,
      ctx.deviceInfo.region ? `  - 地区: ${ctx.deviceInfo.region}` : '',
      ctx.isNewConversation ? '  - 新用户首次对话' : '  - 老用户回访',
      `</device_context>`,
    ].filter(Boolean).join('\n');

    return {
      contextType: ContextType.DEVICE_CONTEXT,
      priority: CONTEXT_PRIORITY[ContextType.DEVICE_CONTEXT],
      content,
      estimatedTokens: Math.ceil(content.length / 3),
      fromCache: false,
    };
  }

  private async buildRelevantKnowledgeBlock(
    ctx: ConversationContext,
  ): Promise<ContextInjectionBlock | null> {
    // Get the last user message as query
    const lastUserMsg = [...ctx.messages]
      .reverse()
      .find(m => m.role === 'user');
    if (!lastUserMsg) return null;

    const query = typeof lastUserMsg.content === 'string'
      ? lastUserMsg.content
      : '';
    if (!query || query.length < 5) return null;

    const cacheKey = `knowledge_${query.slice(0, 50)}`;
    const cached = this.getFromCache(cacheKey, ContextType.RELEVANT_KNOWLEDGE);
    if (cached) {
      return { ...cached, fromCache: true };
    }

    try {
      const knowledge = await this.knowledgeClient.retrieveForPrompt({
        query, userId: ctx.userId,
      });
      if (!knowledge) return null;

      const content = [
        `<relevant_knowledge>`,
        knowledge.slice(0, 2000), // 限制长度
        `</relevant_knowledge>`,
      ].join('\n');

      const block: ContextInjectionBlock = {
        contextType: ContextType.RELEVANT_KNOWLEDGE,
        priority: CONTEXT_PRIORITY[ContextType.RELEVANT_KNOWLEDGE],
        content,
        estimatedTokens: Math.ceil(content.length / 3),
        fromCache: false,
      };

      this.setCache(cacheKey, block, ContextType.RELEVANT_KNOWLEDGE);
      return block;
    } catch (error) {
      this.logger.warn(`Failed to load relevant knowledge: ${error}`);
      return null;
    }
  }

  private async buildSimilarExperiencesBlock(
    ctx: ConversationContext,
  ): Promise<ContextInjectionBlock | null> {
    const lastUserMsg = [...ctx.messages]
      .reverse()
      .find(m => m.role === 'user');
    if (!lastUserMsg) return null;

    const query = typeof lastUserMsg.content === 'string'
      ? lastUserMsg.content
      : '';
    if (!query) return null;

    try {
      const experiences = await this.knowledgeClient.searchExperiences({ query, limit: 3 });
      if (!experiences || experiences.length === 0) return null;

      const content = [
        `<system_experiences>`,
        `相关系统经验:`,
        ...experiences.map(e =>
          `  - [${e.experienceType}] ${e.content} (置信度: ${e.confidence}%)`
        ),
        `</system_experiences>`,
      ].join('\n');

      return {
        contextType: ContextType.SIMILAR_EXPERIENCES,
        priority: CONTEXT_PRIORITY[ContextType.SIMILAR_EXPERIENCES],
        content,
        estimatedTokens: Math.ceil(content.length / 3),
        fromCache: false,
      };
    } catch (error) {
      this.logger.warn(`Failed to load experiences: ${error}`);
      return null;
    }
  }

  private buildAgentHistoryBlock(
    ctx: ConversationContext,
  ): ContextInjectionBlock | null {
    const history = ctx.agentHistory;
    if (!history || history.length === 0) return null;

    const content = [
      `<agent_history>`,
      `本次对话已调用的 Agent 记录（最近 ${history.length} 次）:`,
      ...history.map(h =>
        `  - [${h.agentType}] ${h.input.substring(0, 60)}... → ${h.output.substring(0, 80)}... (${h.durationMs}ms, ${h.timestamp})`
      ),
      `</agent_history>`,
    ].join('\n');

    return {
      contextType: ContextType.AGENT_HISTORY,
      priority: CONTEXT_PRIORITY[ContextType.AGENT_HISTORY],
      content,
      estimatedTokens: Math.ceil(content.length / 3),
      fromCache: false,
    };
  }

  // ============================================================
  // Priority & Token Budget Management
  // ============================================================

  private prioritizeAndTrim(
    blocks: ContextInjectionBlock[],
  ): ContextInjectionResult {
    // Sort by priority (lower number = higher priority)
    const sorted = [...blocks].sort((a, b) => a.priority - b.priority);

    const included: ContextInjectionBlock[] = [];
    const dropped: ContextType[] = [];
    let totalTokens = 0;

    for (const block of sorted) {
      if (totalTokens + block.estimatedTokens <= this.config.maxContextTokens) {
        included.push(block);
        totalTokens += block.estimatedTokens;
      } else {
        dropped.push(block.contextType);
      }
    }

    return {
      blocks: included,
      totalEstimatedTokens: totalTokens,
      droppedContexts: dropped,
      injectionText: included.map(b => b.content).join('\n\n'),
    };
  }

  // ============================================================
  // Inject into Messages
  // ============================================================

  private injectIntoMessages(
    messages: ClaudeMessage[],
    injectionText: string,
  ): ClaudeMessage[] {
    if (!injectionText) return messages;

    // Inject as a system-reminder appended to the last user message
    // (similar to Claude Code's attachment injection pattern)
    const result = [...messages];
    let lastUserIndex = -1;
    for (let i = result.length - 1; i >= 0; i--) {
      if (result[i].role === 'user') { lastUserIndex = i; break; }
    }

    if (lastUserIndex >= 0) {
      const lastUser = result[lastUserIndex];
      const originalContent = typeof lastUser.content === 'string'
        ? lastUser.content
        : JSON.stringify(lastUser.content);

      result[lastUserIndex] = {
        ...lastUser,
        content: `${originalContent}\n\n<system-context>\n${injectionText}\n</system-context>`,
      };
    }

    return result;
  }

  // ============================================================
  // Auto Compaction (上下文自动压缩)
  // ============================================================

  /**
   * Check if messages need compaction and perform it
   * 检查是否需要压缩历史消息
   */
  async autoCompactIfNeeded(
    messages: ClaudeMessage[],
    anthropicClient: Anthropic,
  ): Promise<{ messages: ClaudeMessage[]; wasCompacted: boolean }> {
    if (!this.config.enableAutoCompaction) {
      return { messages, wasCompacted: false };
    }

    // Rough token estimation: ~3 chars per token for Chinese
    const estimatedTokens = JSON.stringify(messages).length / 3;

    if (estimatedTokens < this.config.compactionThreshold) {
      return { messages, wasCompacted: false };
    }

    this.logger.log(
      `Auto-compacting: ~${Math.round(estimatedTokens)} tokens exceeds threshold ${this.config.compactionThreshold}`,
    );

    try {
      // Keep the most recent messages, summarize older ones
      const keepCount = 6; // Keep last 6 messages (3 turns)
      const toSummarize = messages.slice(0, -keepCount);
      const toKeep = messages.slice(-keepCount);

      if (toSummarize.length < 4) {
        return { messages, wasCompacted: false };
      }

      // Use Claude to summarize the older messages
      const summaryResponse = await anthropicClient.messages.create({
        model: 'claude-haiku-4-5-20251001', // Use Haiku for cost efficiency
        system: '你是对话摘要专家。请将以下对话历史压缩为简洁的摘要，保留所有关键信息（用户个人信息、评估结果、重要决策）。',
        messages: [
          {
            role: 'user',
            content: `请总结以下对话历史:\n\n${toSummarize.map(m =>
              `${m.role === 'user' ? '用户' : '助手'}: ${typeof m.content === 'string' ? m.content : JSON.stringify(m.content)}`
            ).join('\n\n')}`,
          },
        ],
        max_tokens: 1000,
      });

      const summaryText = (summaryResponse.content[0] as any)?.text || '';

      const compactedMessages: ClaudeMessage[] = [
        {
          role: 'user',
          content: `<conversation_summary>\n以下是之前对话的摘要:\n${summaryText}\n</conversation_summary>\n\n请基于以上背景继续对话。`,
        },
        {
          role: 'assistant',
          content: '好的，我已了解之前的对话内容。请继续。',
        },
        ...toKeep,
      ];

      this.logger.log(
        `Compacted: ${messages.length} messages → ${compactedMessages.length} messages`,
      );

      return { messages: compactedMessages, wasCompacted: true };
    } catch (error) {
      this.logger.error(`Auto-compaction failed: ${error}`);
      return { messages, wasCompacted: false };
    }
  }

  // ============================================================
  // Cache Helpers
  // ============================================================

  private getFromCache(
    key: string,
    contextType: ContextType,
  ): ContextInjectionBlock | null {
    const ttl = CONTEXT_CACHE_TTL[contextType];
    if (ttl === 0) return null; // No caching

    const cached = this.cache.get(key);
    if (!cached) return null;

    if (Date.now() - cached.timestamp > ttl) {
      this.cache.delete(key);
      return null;
    }

    return cached.data;
  }

  private setCache(
    key: string,
    data: ContextInjectionBlock,
    contextType: ContextType,
  ): void {
    const ttl = CONTEXT_CACHE_TTL[contextType];
    if (ttl === 0 || ttl === Infinity) return;

    this.cache.set(key, { data, timestamp: Date.now() });
  }

  /** Clear all cache (call on conversation end) */
  clearCache(): void {
    this.cache.clear();
  }
}
