import { EventEmitter } from 'events';
import { Logger } from '../utils/logger.js';
import { EscalationRule, EscalationCondition, EscalationAction } from '../config/config.js';

export interface ConversationThread {
  id: string;
  topic: string;
  participants: string[];
  creator: string;
  status: 'active' | 'paused' | 'completed' | 'escalated';
  createdAt: string;
  lastActivity: string;
  messages: ThreadMessage[];
  metadata: Record<string, any>;
  escalationHistory: EscalationEvent[];
  summary?: string;
}

export interface ThreadMessage {
  id: string;
  threadId: string;
  sender: string;
  content: string;
  messageType: 'text' | 'code' | 'file' | 'decision' | 'question';
  timestamp: string;
  replyTo?: string;
  metadata: Record<string, any>;
}

export interface EscalationEvent {
  id: string;
  threadId: string;
  rule: string;
  reason: string;
  triggeredAt: string;
  actions: EscalationAction[];
  resolved: boolean;
  resolution?: string;
}

export interface CreateThreadOptions {
  topic: string;
  participants: string[];
  creator: string;
  metadata?: Record<string, any>;
}

export interface ConversationManagerConfig {
  maxActiveThreads: number;
  defaultTimeout: number;
  escalationRules: EscalationRule[];
}

export class ConversationManager extends EventEmitter {
  private logger: Logger;
  private config: ConversationManagerConfig;
  private threads: Map<string, ConversationThread> = new Map();
  private participantThreads: Map<string, Set<string>> = new Map();
  private escalationTimer?: NodeJS.Timeout;
  private monitoringActive: boolean = false;

  constructor(config: ConversationManagerConfig) {
    super();
    this.config = config;
    this.logger = new Logger('ConversationManager');

    this.logger.info('Conversation Manager initialized', {
      maxActiveThreads: config.maxActiveThreads,
      escalationRules: config.escalationRules.length,
    });
  }

  public async start(): Promise<void> {
    this.startEscalationMonitoring();
    this.monitoringActive = true;
    this.logger.info('Conversation Manager started');
  }

  public async stop(): Promise<void> {
    this.stopEscalationMonitoring();
    this.monitoringActive = false;
    this.logger.info('Conversation Manager stopped');
  }

  public async createThread(options: CreateThreadOptions): Promise<ConversationThread> {
    if (this.threads.size >= this.config.maxActiveThreads) {
      throw new Error('Maximum active thread limit reached');
    }

    if (options.participants.length === 0) {
      throw new Error('Thread must have at least one participant');
    }

    const threadId = this.generateThreadId();
    const thread: ConversationThread = {
      id: threadId,
      topic: options.topic,
      participants: [...options.participants, options.creator],
      creator: options.creator,
      status: 'active',
      createdAt: new Date().toISOString(),
      lastActivity: new Date().toISOString(),
      messages: [],
      metadata: options.metadata || {},
      escalationHistory: [],
    };

    this.threads.set(threadId, thread);

    // Track participants
    for (const participant of thread.participants) {
      if (!this.participantThreads.has(participant)) {
        this.participantThreads.set(participant, new Set());
      }
      this.participantThreads.get(participant)!.add(threadId);
    }

    this.logger.info('Thread created', {
      threadId,
      topic: options.topic,
      participants: options.participants.length,
      creator: options.creator,
    });

    this.emit('thread_created', thread);
    return thread;
  }

  public async joinThread(threadId: string, participantId: string): Promise<ConversationThread> {
    const thread = this.threads.get(threadId);
    if (!thread) {
      throw new Error(`Thread ${threadId} not found`);
    }

    if (thread.participants.includes(participantId)) {
      throw new Error(`Participant ${participantId} is already in thread ${threadId}`);
    }

    thread.participants.push(participantId);
    thread.lastActivity = new Date().toISOString();

    // Track participant
    if (!this.participantThreads.has(participantId)) {
      this.participantThreads.set(participantId, new Set());
    }
    this.participantThreads.get(participantId)!.add(threadId);

    // Add system message
    await this.addMessage(threadId, 'system', `${participantId} joined the conversation`, 'text');

    this.logger.info('Participant joined thread', {
      threadId,
      participantId,
      totalParticipants: thread.participants.length,
    });

    this.emit('participant_joined', { thread, participantId });
    return thread;
  }

  public async leaveThread(threadId: string, participantId: string): Promise<ConversationThread> {
    const thread = this.threads.get(threadId);
    if (!thread) {
      throw new Error(`Thread ${threadId} not found`);
    }

    const participantIndex = thread.participants.indexOf(participantId);
    if (participantIndex === -1) {
      throw new Error(`Participant ${participantId} is not in thread ${threadId}`);
    }

    thread.participants.splice(participantIndex, 1);
    thread.lastActivity = new Date().toISOString();

    // Remove from participant tracking
    const participantThreads = this.participantThreads.get(participantId);
    if (participantThreads) {
      participantThreads.delete(threadId);
      if (participantThreads.size === 0) {
        this.participantThreads.delete(participantId);
      }
    }

    // Add system message
    await this.addMessage(threadId, 'system', `${participantId} left the conversation`, 'text');

    // If no participants left, mark thread as completed
    if (thread.participants.length === 0) {
      thread.status = 'completed';
    }

    this.logger.info('Participant left thread', {
      threadId,
      participantId,
      remainingParticipants: thread.participants.length,
    });

    this.emit('participant_left', { thread, participantId });
    return thread;
  }

  public async addMessage(
    threadId: string,
    sender: string,
    content: string,
    messageType: ThreadMessage['messageType'] = 'text',
    replyTo?: string,
    metadata: Record<string, any> = {}
  ): Promise<ThreadMessage> {
    const thread = this.threads.get(threadId);
    if (!thread) {
      throw new Error(`Thread ${threadId} not found`);
    }

    const message: ThreadMessage = {
      id: this.generateMessageId(),
      threadId,
      sender,
      content,
      messageType,
      timestamp: new Date().toISOString(),
      replyTo,
      metadata,
    };

    thread.messages.push(message);
    thread.lastActivity = new Date().toISOString();

    this.logger.debug('Message added to thread', {
      threadId,
      messageId: message.id,
      sender,
      messageType,
    });

    this.emit('message_added', { thread, message });
    return message;
  }

  public async handleIncomingMessage(message: any): Promise<void> {
    try {
      if (message.threadId && this.threads.has(message.threadId)) {
        await this.addMessage(
          message.threadId,
          message.sender,
          message.content,
          message.messageType || 'text',
          message.replyTo,
          message.metadata || {}
        );
      } else {
        // Handle messages not part of existing threads
        this.logger.debug('Received message not associated with existing thread', {
          sender: message.sender,
          type: message.type,
        });

        this.emit('orphan_message', message);
      }
    } catch (error) {
      this.logger.error('Failed to handle incoming message', {
        message,
        error: error instanceof Error ? error.message : String(error),
      });
    }
  }

  public listThreads(participantId?: string): ConversationThread[] {
    if (participantId) {
      const participantThreadIds = this.participantThreads.get(participantId);
      if (!participantThreadIds) {
        return [];
      }

      return Array.from(participantThreadIds)
        .map(threadId => this.threads.get(threadId))
        .filter((thread): thread is ConversationThread => thread !== undefined);
    }

    return Array.from(this.threads.values());
  }

  public getThread(threadId: string): ConversationThread | undefined {
    return this.threads.get(threadId);
  }

  public async summarizeThread(threadId: string): Promise<string> {
    const thread = this.threads.get(threadId);
    if (!thread) {
      throw new Error(`Thread ${threadId} not found`);
    }

    if (thread.summary && thread.messages.length === 0) {
      return thread.summary;
    }

    // Generate summary based on messages
    const messageContent = thread.messages
      .slice(-50) // Last 50 messages to avoid token limits
      .map(msg => `${msg.sender}: ${msg.content}`)
      .join('\n');

    if (messageContent.length === 0) {
      return 'No messages in thread yet.';
    }

    // Create a simple extractive summary
    const summary = this.createExtractiveSummary(thread, messageContent);
    thread.summary = summary;

    this.logger.debug('Thread summary generated', {
      threadId,
      messageCount: thread.messages.length,
      summaryLength: summary.length,
    });

    return summary;
  }

  public async pauseThread(threadId: string): Promise<void> {
    const thread = this.threads.get(threadId);
    if (!thread) {
      throw new Error(`Thread ${threadId} not found`);
    }

    thread.status = 'paused';
    thread.lastActivity = new Date().toISOString();

    await this.addMessage(threadId, 'system', 'Thread paused', 'text');

    this.logger.info('Thread paused', { threadId });
    this.emit('thread_paused', thread);
  }

  public async resumeThread(threadId: string): Promise<void> {
    const thread = this.threads.get(threadId);
    if (!thread) {
      throw new Error(`Thread ${threadId} not found`);
    }

    thread.status = 'active';
    thread.lastActivity = new Date().toISOString();

    await this.addMessage(threadId, 'system', 'Thread resumed', 'text');

    this.logger.info('Thread resumed', { threadId });
    this.emit('thread_resumed', thread);
  }

  public async completeThread(threadId: string, summary?: string): Promise<void> {
    const thread = this.threads.get(threadId);
    if (!thread) {
      throw new Error(`Thread ${threadId} not found`);
    }

    thread.status = 'completed';
    thread.lastActivity = new Date().toISOString();
    
    if (summary) {
      thread.summary = summary;
    } else if (!thread.summary) {
      thread.summary = await this.summarizeThread(threadId);
    }

    await this.addMessage(threadId, 'system', 'Thread completed', 'text');

    // Remove from participant tracking (but keep the thread for history)
    for (const participantId of thread.participants) {
      const participantThreads = this.participantThreads.get(participantId);
      if (participantThreads) {
        participantThreads.delete(threadId);
        if (participantThreads.size === 0) {
          this.participantThreads.delete(participantId);
        }
      }
    }

    this.logger.info('Thread completed', { threadId });
    this.emit('thread_completed', thread);
  }

  private startEscalationMonitoring(): void {
    this.escalationTimer = setInterval(() => {
      this.checkEscalationRules();
    }, 30000); // Check every 30 seconds

    this.logger.debug('Escalation monitoring started');
  }

  private stopEscalationMonitoring(): void {
    if (this.escalationTimer) {
      clearInterval(this.escalationTimer);
      this.escalationTimer = undefined;
    }

    this.logger.debug('Escalation monitoring stopped');
  }

  private checkEscalationRules(): void {
    for (const thread of this.threads.values()) {
      if (thread.status !== 'active') continue;

      for (const rule of this.config.escalationRules) {
        if (this.shouldEscalate(thread, rule)) {
          this.triggerEscalation(thread, rule);
        }
      }
    }
  }

  private shouldEscalate(thread: ConversationThread, rule: EscalationRule): boolean {
    // Check if this rule has already been triggered recently
    const recentEscalation = thread.escalationHistory.find(
      e => e.rule === rule.name && !e.resolved &&
      (Date.now() - new Date(e.triggeredAt).getTime()) < 3600000 // 1 hour
    );

    if (recentEscalation) return false;

    // Check all conditions
    for (const condition of rule.conditions) {
      if (!this.evaluateCondition(thread, condition)) {
        return false;
      }
    }

    return true;
  }

  private evaluateCondition(thread: ConversationThread, condition: EscalationCondition): boolean {
    const now = Date.now();
    const threadStart = new Date(thread.createdAt).getTime();
    const lastActivity = new Date(thread.lastActivity).getTime();

    switch (condition.type) {
      case 'thread_duration':
        return (now - threadStart) > (condition.threshold as number * 1000);

      case 'no_progress':
        const timeframe = condition.timeframe || 1800; // 30 minutes default
        return (now - lastActivity) > (timeframe * 1000);

      case 'disagreement_count':
        // Count messages that indicate disagreement
        const disagreements = thread.messages.filter(msg =>
          msg.content.toLowerCase().includes('disagree') ||
          msg.content.toLowerCase().includes('no, ') ||
          msg.content.toLowerCase().includes('but ') ||
          msg.content.toLowerCase().includes('however')
        ).length;
        return disagreements >= (condition.threshold as number);

      case 'error_rate':
        // Count error messages
        const errorMessages = thread.messages.filter(msg =>
          msg.messageType === 'text' && (
            msg.content.toLowerCase().includes('error') ||
            msg.content.toLowerCase().includes('failed') ||
            msg.content.toLowerCase().includes('exception')
          )
        ).length;
        const totalMessages = thread.messages.length;
        const errorRate = totalMessages > 0 ? errorMessages / totalMessages : 0;
        return errorRate >= (condition.threshold as number);

      default:
        return false;
    }
  }

  private triggerEscalation(thread: ConversationThread, rule: EscalationRule): void {
    const escalation: EscalationEvent = {
      id: this.generateEscalationId(),
      threadId: thread.id,
      rule: rule.name,
      reason: `Escalation rule '${rule.name}' triggered`,
      triggeredAt: new Date().toISOString(),
      actions: rule.actions,
      resolved: false,
    };

    thread.escalationHistory.push(escalation);
    thread.status = 'escalated';

    this.logger.warn('Thread escalated', {
      threadId: thread.id,
      rule: rule.name,
      actions: rule.actions.length,
    });

    this.emit('escalation', thread, escalation.reason);

    // Execute escalation actions
    this.executeEscalationActions(thread, rule.actions);
  }

  private executeEscalationActions(thread: ConversationThread, actions: EscalationAction[]): void {
    for (const action of actions) {
      switch (action.type) {
        case 'notify_human':
          this.notifyHuman(thread, action);
          break;
        case 'request_expert':
          this.requestExpert(thread, action);
          break;
        case 'escalate_to_architect':
          this.escalateToArchitect(thread, action);
          break;
        case 'create_decision_thread':
          this.createDecisionThread(thread, action);
          break;
      }
    }
  }

  private notifyHuman(thread: ConversationThread, action: EscalationAction): void {
    this.logger.info('Human notification triggered', {
      threadId: thread.id,
      target: action.target,
      priority: action.priority,
    });
    // Implementation would integrate with notification systems
  }

  private requestExpert(thread: ConversationThread, action: EscalationAction): void {
    this.logger.info('Expert request triggered', {
      threadId: thread.id,
      priority: action.priority,
    });
    // Implementation would request expert agent participation
  }

  private escalateToArchitect(thread: ConversationThread, action: EscalationAction): void {
    this.logger.info('Architect escalation triggered', {
      threadId: thread.id,
      priority: action.priority,
    });
    // Implementation would involve architect agents
  }

  private createDecisionThread(thread: ConversationThread, action: EscalationAction): void {
    this.logger.info('Decision thread creation triggered', {
      threadId: thread.id,
      participants: action.participants,
    });
    // Implementation would create a new decision-focused thread
  }

  private createExtractiveSummary(thread: ConversationThread, messageContent: string): string {
    // Simple extractive summary - in a real implementation, this could use AI
    const sentences = messageContent.split(/[.!?]+/).filter(s => s.trim().length > 10);
    const summary = sentences.slice(0, 5).join('. ').trim();
    
    return summary || 'Discussion ongoing with no clear conclusions yet.';
  }

  private generateThreadId(): string {
    return `thread-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  private generateMessageId(): string {
    return `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  private generateEscalationId(): string {
    return `esc-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }
}