import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, LessThan } from 'typeorm';
import {
  AIConversation,
  ConversationMessage,
  MessageRole,
} from '../../../database/entities';
import { EmbeddingsService } from '../embeddings/embeddings.service';

/**
 * Conversation Memory Service
 *
 * Manages conversation history with summarization and context window management
 */

export interface ConversationSummary {
  summary: string;
  keyTopics: string[];
  messageCount: number;
  tokenCount: number;
}

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

  // Memory configuration
  private readonly MAX_MESSAGES_IN_MEMORY = 20;
  private readonly SUMMARIZATION_THRESHOLD = 10; // Summarize after 10 messages
  private readonly TOKEN_BUDGET = 4000;

  constructor(
    @InjectRepository(AIConversation)
    private conversationRepository: Repository<AIConversation>,
    private embeddingsService: EmbeddingsService,
  ) {}

  /**
   * Get conversation with managed memory
   *
   * Returns conversation with summarized old messages and recent messages in full
   */
  async getConversationWithMemory(conversationId: string): Promise<{
    conversation: AIConversation;
    context: ConversationMessage[];
    summary?: ConversationSummary;
  }> {
    const conversation = await this.conversationRepository.findOne({
      where: { id: conversationId },
    });

    if (!conversation) {
      throw new Error('Conversation not found');
    }

    const messages = conversation.messages || [];

    // If conversation is short, return all messages
    if (messages.length <= this.MAX_MESSAGES_IN_MEMORY) {
      return {
        conversation,
        context: messages,
      };
    }

    // Split into old (to be summarized) and recent (kept in full)
    const recentMessages = messages.slice(-this.MAX_MESSAGES_IN_MEMORY);
    const oldMessages = messages.slice(0, -this.MAX_MESSAGES_IN_MEMORY);

    // Generate summary of old messages
    const summary = this.summarizeMessages(oldMessages);

    // Create summary context message
    const summaryMessage: ConversationMessage = {
      role: MessageRole.SYSTEM,
      content: `Previous conversation summary:\n${summary.summary}\n\nKey topics discussed: ${summary.keyTopics.join(', ')}`,
      timestamp: oldMessages[oldMessages.length - 1]?.timestamp || new Date(),
    };

    return {
      conversation,
      context: [summaryMessage, ...recentMessages],
      summary,
    };
  }

  /**
   * Summarize old messages to save context window
   */
  private summarizeMessages(
    messages: ConversationMessage[],
  ): ConversationSummary {
    // Extract user questions and key topics
    const userMessages = messages.filter((m) => m.role === MessageRole.USER);
    const assistantMessages = messages.filter(
      (m) => m.role === MessageRole.ASSISTANT,
    );

    // Extract key topics using simple keyword extraction
    const topics = this.extractKeyTopics(messages);

    // Build concise summary
    const summary = this.buildSummary(userMessages, assistantMessages, topics);

    // Estimate token count
    const tokenCount = Math.ceil(summary.length / 4);

    return {
      summary,
      keyTopics: topics,
      messageCount: messages.length,
      tokenCount,
    };
  }

  /**
   * Extract key topics from messages
   */
  private extractKeyTopics(messages: ConversationMessage[]): string[] {
    const text = messages
      .map((m) => m.content)
      .join(' ')
      .toLowerCase();

    // Parenting-related keywords to look for
    const topicKeywords = {
      feeding: [
        'feed',
        'feeding',
        'bottle',
        'breast',
        'nursing',
        'formula',
        'milk',
      ],
      sleep: ['sleep', 'nap', 'bedtime', 'sleeping', 'wake', 'waking'],
      diaper: ['diaper', 'poop', 'pee', 'changing', 'wet', 'dirty'],
      development: [
        'milestone',
        'development',
        'crawling',
        'walking',
        'talking',
        'growth',
      ],
      health: [
        'fever',
        'sick',
        'doctor',
        'medicine',
        'vaccine',
        'rash',
        'cough',
      ],
      behavior: ['cry', 'fussy', 'tantrum', 'discipline', 'behavior'],
      routine: ['schedule', 'routine', 'timing', 'pattern'],
    };

    const detectedTopics: string[] = [];

    for (const [topic, keywords] of Object.entries(topicKeywords)) {
      if (keywords.some((keyword) => text.includes(keyword))) {
        detectedTopics.push(topic);
      }
    }

    return detectedTopics.length > 0 ? detectedTopics : ['general parenting'];
  }

  /**
   * Build concise summary of conversation
   */
  private buildSummary(
    userMessages: ConversationMessage[],
    assistantMessages: ConversationMessage[],
    topics: string[],
  ): string {
    if (userMessages.length === 0) {
      return 'No previous conversation.';
    }

    const topicSummary = topics.length > 0 ? ` about ${topics.join(', ')}` : '';

    return `User asked ${userMessages.length} question(s)${topicSummary}. Discussion covered parenting topics and received guidance on childcare practices.`;
  }

  /**
   * Clean up old conversations (data retention)
   */
  async cleanupOldConversations(daysToKeep: number = 90): Promise<number> {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);

    const result = await this.conversationRepository.delete({
      updatedAt: LessThan(cutoffDate),
    });

    const deletedCount = result.affected || 0;

    if (deletedCount > 0) {
      this.logger.log(
        `Cleaned up ${deletedCount} conversations older than ${daysToKeep} days`,
      );
    }

    return deletedCount;
  }

  /**
   * Get conversation statistics
   */
  async getConversationStats(conversationId: string): Promise<{
    messageCount: number;
    totalTokens: number;
    userMessageCount: number;
    assistantMessageCount: number;
    topicsDiscussed: string[];
  }> {
    const conversation = await this.conversationRepository.findOne({
      where: { id: conversationId },
    });

    if (!conversation) {
      throw new Error('Conversation not found');
    }

    const messages = conversation.messages || [];
    const userMessages = messages.filter((m) => m.role === MessageRole.USER);
    const assistantMessages = messages.filter(
      (m) => m.role === MessageRole.ASSISTANT,
    );

    const topics = this.extractKeyTopics(messages);

    return {
      messageCount: messages.length,
      totalTokens: conversation.totalTokens,
      userMessageCount: userMessages.length,
      assistantMessageCount: assistantMessages.length,
      topicsDiscussed: topics,
    };
  }

  /**
   * Prune conversation to fit within token budget
   */
  pruneConversation(
    messages: ConversationMessage[],
    maxTokens: number = this.TOKEN_BUDGET,
  ): ConversationMessage[] {
    // Always keep system messages
    const systemMessages = messages.filter(
      (m) => m.role === MessageRole.SYSTEM,
    );
    const otherMessages = messages.filter((m) => m.role !== MessageRole.SYSTEM);

    // Estimate tokens for system messages
    let currentTokens = systemMessages.reduce(
      (sum, msg) => sum + this.estimateTokens(msg.content),
      0,
    );

    // Add messages from most recent backwards until budget is reached
    const prunedMessages: ConversationMessage[] = [];

    for (let i = otherMessages.length - 1; i >= 0; i--) {
      const msg = otherMessages[i];
      const msgTokens = this.estimateTokens(msg.content);

      if (currentTokens + msgTokens <= maxTokens) {
        prunedMessages.unshift(msg);
        currentTokens += msgTokens;
      } else {
        this.logger.debug(
          `Pruned ${i + 1} older messages to stay within ${maxTokens} token budget`,
        );
        break;
      }
    }

    return [...systemMessages, ...prunedMessages];
  }

  /**
   * Estimate token count for text
   */
  private estimateTokens(text: string): number {
    // Rough estimate: 1 token ≈ 4 characters
    return Math.ceil(text.length / 4);
  }

  /**
   * Archive long conversations (move to cold storage)
   */
  async archiveConversation(conversationId: string): Promise<void> {
    const conversation = await this.conversationRepository.findOne({
      where: { id: conversationId },
    });

    if (!conversation) {
      throw new Error('Conversation not found');
    }

    // Create summary for archived conversation
    const summary = this.summarizeMessages(conversation.messages || []);

    // Store summary in metadata
    conversation.metadata = {
      ...conversation.metadata,
      archived: true,
      archivedAt: new Date().toISOString(),
      archivedSummary: summary.summary,
      archivedTopics: summary.keyTopics,
    };

    // Keep only recent messages (last 5)
    conversation.messages = conversation.messages?.slice(-5) || [];

    await this.conversationRepository.save(conversation);

    this.logger.log(`Archived conversation ${conversationId}`);
  }

  /**
   * Get user's conversation history summary
   */
  async getUserConversationSummary(userId: string): Promise<{
    totalConversations: number;
    totalMessages: number;
    totalTokens: number;
    mostDiscussedTopics: string[];
  }> {
    const conversations = await this.conversationRepository.find({
      where: { userId },
    });

    let totalMessages = 0;
    let totalTokens = 0;
    const allTopics: string[] = [];

    for (const conv of conversations) {
      totalMessages += (conv.messages || []).length;
      totalTokens += conv.totalTokens;

      const topics = this.extractKeyTopics(conv.messages || []);
      allTopics.push(...topics);
    }

    // Count topic frequency
    const topicCounts = new Map<string, number>();
    for (const topic of allTopics) {
      topicCounts.set(topic, (topicCounts.get(topic) || 0) + 1);
    }

    // Get top 5 most discussed topics
    const mostDiscussedTopics = Array.from(topicCounts.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 5)
      .map(([topic]) => topic);

    return {
      totalConversations: conversations.length,
      totalMessages,
      totalTokens,
      mostDiscussedTopics,
    };
  }

  /**
   * Get semantic context using embeddings-based search
   *
   * Searches for semantically similar past conversations to provide relevant context
   */
  async getSemanticContext(
    userId: string,
    currentQuery: string,
    options: {
      similarityThreshold?: number;
      maxResults?: number;
      topicFilter?: string;
    } = {},
  ): Promise<ConversationMessage[]> {
    const { similarityThreshold = 0.7, maxResults = 5, topicFilter } = options;

    try {
      // Search for similar conversations
      const similarConversations =
        await this.embeddingsService.searchSimilarConversations(
          currentQuery,
          userId,
          {
            similarityThreshold,
            limit: maxResults,
            topicFilter,
          },
        );

      if (similarConversations.length === 0) {
        this.logger.debug(
          `No similar conversations found for user ${userId} (threshold: ${similarityThreshold})`,
        );
        return [];
      }

      // Convert to context messages
      const contextMessages: ConversationMessage[] = similarConversations.map(
        (similar) => ({
          role: MessageRole.SYSTEM,
          content: `[Relevant past conversation (similarity: ${(similar.similarity * 100).toFixed(1)}%)]\nTopics: ${similar.topics.join(', ')}\nContent: ${similar.messageContent}`,
          timestamp: similar.createdAt,
        }),
      );

      this.logger.debug(
        `Found ${contextMessages.length} semantically similar conversations for context`,
      );

      return contextMessages;
    } catch (error) {
      this.logger.error('Failed to get semantic context', error.stack);
      // Return empty context on error rather than failing the entire request
      return [];
    }
  }

  /**
   * Store embeddings for a new conversation message
   *
   * Called after adding a message to a conversation to generate and store its embedding
   */
  async storeMessageEmbedding(
    conversationId: string,
    userId: string,
    messageIndex: number,
    messageRole: MessageRole,
    messageContent: string,
  ): Promise<void> {
    try {
      // Extract topics from the message
      const topics = this.extractKeyTopics([
        { role: messageRole, content: messageContent, timestamp: new Date() },
      ]);

      // Store embedding
      await this.embeddingsService.storeEmbedding(
        conversationId,
        userId,
        messageIndex,
        messageRole,
        messageContent,
        topics,
      );

      this.logger.debug(
        `Stored embedding for conversation ${conversationId}, message ${messageIndex}`,
      );
    } catch (error) {
      this.logger.error('Failed to store message embedding', error.stack);
      // Don't throw - embedding storage is a nice-to-have, not critical
    }
  }

  /**
   * Backfill embeddings for existing conversations
   *
   * Used for migrating existing conversations to use embeddings
   */
  async backfillConversationEmbeddings(
    conversationId: string,
  ): Promise<number> {
    try {
      const conversation = await this.conversationRepository.findOne({
        where: { id: conversationId },
      });

      if (!conversation) {
        throw new Error('Conversation not found');
      }

      const messages = conversation.messages || [];
      if (messages.length === 0) {
        return 0;
      }

      // Extract topics
      const topics = this.extractKeyTopics(messages);

      // Prepare message data
      const messageData = messages.map((msg, index) => ({
        index,
        role: msg.role,
        content: msg.content,
      }));

      // Backfill embeddings
      const count = await this.embeddingsService.backfillEmbeddings(
        conversationId,
        conversation.userId,
        messageData,
        topics,
      );

      this.logger.log(
        `Backfilled ${count} embeddings for conversation ${conversationId}`,
      );

      return count;
    } catch (error) {
      this.logger.error(
        `Failed to backfill embeddings for conversation ${conversationId}`,
        error.stack,
      );
      throw error;
    }
  }

  /**
   * Get enhanced conversation with semantic context
   *
   * Combines traditional memory management with semantic search for richer context
   */
  async getConversationWithSemanticMemory(
    conversationId: string,
    currentQuery?: string,
  ): Promise<{
    conversation: AIConversation;
    context: ConversationMessage[];
    summary?: ConversationSummary;
    semanticContext?: ConversationMessage[];
  }> {
    // Get conversation with traditional memory management
    const memoryResult = await this.getConversationWithMemory(conversationId);

    // Only use semantic context if conversation has existing messages
    // This prevents pulling in irrelevant context for new conversations
    const existingMessages = memoryResult.conversation.messages?.length || 0;

    // If no current query, or this is a new conversation, return traditional memory only
    if (!currentQuery || existingMessages === 0) {
      return memoryResult;
    }

    // TEMPORARILY DISABLED: Get semantic context based on current query with higher threshold
    // to ensure only highly relevant past conversations are included
    // const semanticContext = await this.getSemanticContext(
    //   memoryResult.conversation.userId,
    //   currentQuery,
    //   {
    //     similarityThreshold: 0.85, // Increased from 0.7 to reduce false matches
    //     maxResults: 2, // Reduced from 3 to limit context pollution
    //   },
    // );
    const semanticContext: ConversationMessage[] = []; // Temporarily return empty array

    // Only add semantic context if we found highly relevant matches
    if (semanticContext.length === 0) {
      return memoryResult;
    }

    // Combine contexts: semantic context first, then conversation context
    const combinedContext = [...semanticContext, ...memoryResult.context];

    // Prune combined context to fit token budget
    const prunedContext = this.pruneConversation(
      combinedContext,
      this.TOKEN_BUDGET,
    );

    return {
      ...memoryResult,
      context: prunedContext,
      semanticContext,
    };
  }
}
