import { Logger } from '../utils/logger.js';
import { AgentManager } from '../agents/agent-manager.js';
import { ConversationManager } from '../conversations/conversation-manager.js';
import { BzzzP2PConnector } from '../p2p/bzzz-connector.js';

export interface SemanticAddress {
  agent?: string;
  role?: string;
  project?: string;
  task?: string;
  path?: string;
  raw: string;
}

export interface ProtocolToolsConfig {
  agentManager: AgentManager;
  p2pConnector: BzzzP2PConnector;
  conversationManager: ConversationManager;
}

/**
 * BzzzProtocolTools implements the core BZZZ protocol operations as MCP tools
 */
export class BzzzProtocolTools {
  private logger: Logger;
  private agentManager: AgentManager;
  private p2pConnector: BzzzP2PConnector;
  private conversationManager: ConversationManager;

  constructor(config: ProtocolToolsConfig) {
    this.logger = new Logger('BzzzProtocolTools');
    this.agentManager = config.agentManager;
    this.p2pConnector = config.p2pConnector;
    this.conversationManager = config.conversationManager;
  }

  /**
   * Handle bzzz_announce - Agent presence announcement
   */
  async handleAnnounce(args: Record<string, any>): Promise<any> {
    const { agent_id, role, capabilities, specialization, max_tasks = 3 } = args;

    if (!agent_id || !role) {
      throw new Error('agent_id and role are required for announcement');
    }

    this.logger.info(`Announcing agent ${agent_id} with role ${role}`);

    try {
      // Create or update agent
      const agent = await this.agentManager.createAgent({
        id: agent_id,
        role,
        capabilities: capabilities || [],
        specialization: specialization || role,
        maxTasks: max_tasks,
      });

      // Announce to P2P network
      const announcement = {
        type: 'capability_broadcast',
        agent_id,
        role,
        capabilities: capabilities || [],
        specialization: specialization || role,
        max_tasks,
        timestamp: new Date().toISOString(),
        network_address: this.p2pConnector.getNodeId(),
      };

      await this.p2pConnector.publishMessage('bzzz/coordination/v1', announcement);

      return {
        success: true,
        message: `Agent ${agent_id} (${role}) announced to BZZZ network`,
        agent: {
          id: agent.id,
          role: agent.role,
          capabilities: agent.capabilities,
          specialization: agent.specialization,
          status: agent.status,
        },
      };
    } catch (error) {
      this.logger.error('Failed to announce agent:', error);
      throw new Error(`Announcement failed: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  /**
   * Handle bzzz_lookup - Semantic address discovery
   */
  async handleLookup(args: Record<string, any>): Promise<any> {
    const { semantic_address, filter_criteria = {} } = args;

    if (!semantic_address) {
      throw new Error('semantic_address is required for lookup');
    }

    this.logger.info(`Looking up semantic address: ${semantic_address}`);

    try {
      // Parse semantic address
      const address = this.parseSemanticAddress(semantic_address);
      
      // Discover matching agents
      const agents = await this.discoverAgents(address, filter_criteria);
      
      // Query P2P network for additional matches
      const networkResults = await this.queryP2PNetwork(address);
      
      // Combine and rank results
      const allMatches = [...agents, ...networkResults];
      const rankedMatches = this.rankMatches(allMatches, address, filter_criteria);

      return {
        success: true,
        address: semantic_address,
        parsed_address: address,
        matches: rankedMatches,
        count: rankedMatches.length,
        query_time: new Date().toISOString(),
      };
    } catch (error) {
      this.logger.error('Failed to lookup address:', error);
      throw new Error(`Lookup failed: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  /**
   * Handle bzzz_get - Content retrieval from addresses
   */
  async handleGet(args: Record<string, any>): Promise<any> {
    const { address, include_metadata = true, max_history = 10 } = args;

    if (!address) {
      throw new Error('address is required for get operation');
    }

    this.logger.info(`Getting content from address: ${address}`);

    try {
      const parsedAddress = this.parseSemanticAddress(address);
      
      // Retrieve content based on address type
      let content;
      let metadata = {};

      if (parsedAddress.agent) {
        // Get agent-specific content
        content = await this.getAgentContent(parsedAddress, max_history);
        if (include_metadata) {
          metadata = await this.getAgentMetadata(parsedAddress.agent);
        }
      } else if (parsedAddress.project) {
        // Get project-specific content
        content = await this.getProjectContent(parsedAddress, max_history);
        if (include_metadata) {
          metadata = await this.getProjectMetadata(parsedAddress.project);
        }
      } else {
        // General network query
        content = await this.getNetworkContent(parsedAddress, max_history);
      }

      return {
        success: true,
        address,
        content,
        metadata: include_metadata ? metadata : undefined,
        retrieved_at: new Date().toISOString(),
      };
    } catch (error) {
      this.logger.error('Failed to get content:', error);
      throw new Error(`Get operation failed: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  /**
   * Handle bzzz_post - Event/message posting
   */
  async handlePost(args: Record<string, any>): Promise<any> {
    const { target_address, message_type, content, priority = 'medium', thread_id } = args;

    if (!target_address || !message_type || !content) {
      throw new Error('target_address, message_type, and content are required for post operation');
    }

    this.logger.info(`Posting ${message_type} to address: ${target_address}`);

    try {
      const parsedAddress = this.parseSemanticAddress(target_address);
      
      // Create message payload
      const message = {
        type: message_type,
        content,
        priority,
        thread_id,
        target_address,
        sender_id: this.p2pConnector.getNodeId(),
        timestamp: new Date().toISOString(),
        parsed_address: parsedAddress,
      };

      // Determine routing strategy
      let deliveryResults;
      
      if (parsedAddress.agent) {
        // Direct agent messaging
        deliveryResults = await this.postToAgent(parsedAddress.agent, message);
      } else if (parsedAddress.role) {
        // Role-based broadcasting
        deliveryResults = await this.postToRole(parsedAddress.role, message);
      } else if (parsedAddress.project) {
        // Project-specific messaging
        deliveryResults = await this.postToProject(parsedAddress.project, message);
      } else {
        // General network broadcast
        deliveryResults = await this.postToNetwork(message);
      }

      return {
        success: true,
        message_id: this.generateMessageId(),
        target_address,
        message_type,
        delivery_results: deliveryResults,
        posted_at: new Date().toISOString(),
      };
    } catch (error) {
      this.logger.error('Failed to post message:', error);
      throw new Error(`Post operation failed: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  /**
   * Handle bzzz_thread - Conversation management
   */
  async handleThread(args: Record<string, any>): Promise<any> {
    const { action, thread_id, participants, topic } = args;

    if (!action) {
      throw new Error('action is required for thread operation');
    }

    this.logger.info(`Thread action: ${action}, thread_id: ${thread_id}`);

    try {
      let result;

      switch (action) {
        case 'create':
          if (!topic || !participants?.length) {
            throw new Error('topic and participants are required for creating threads');
          }
          result = await this.conversationManager.createThread({
            topic,
            participants,
            creator: this.p2pConnector.getNodeId(),
          });
          break;

        case 'join':
          if (!thread_id) {
            throw new Error('thread_id is required for joining threads');
          }
          result = await this.conversationManager.joinThread(
            thread_id,
            this.p2pConnector.getNodeId()
          );
          break;

        case 'leave':
          if (!thread_id) {
            throw new Error('thread_id is required for leaving threads');
          }
          result = await this.conversationManager.leaveThread(
            thread_id,
            this.p2pConnector.getNodeId()
          );
          break;

        case 'list':
          result = await this.conversationManager.listThreads(
            this.p2pConnector.getNodeId()
          );
          break;

        case 'summarize':
          if (!thread_id) {
            throw new Error('thread_id is required for summarizing threads');
          }
          result = await this.conversationManager.summarizeThread(thread_id);
          break;

        default:
          throw new Error(`Unknown thread action: ${action}`);
      }

      return {
        success: true,
        action,
        thread_id,
        result,
        timestamp: new Date().toISOString(),
      };
    } catch (error) {
      this.logger.error('Thread operation failed:', error);
      throw new Error(`Thread operation failed: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  /**
   * Handle bzzz_subscribe - Real-time event subscription
   */
  async handleSubscribe(args: Record<string, any>): Promise<any> {
    const { event_types, filter_address, callback_webhook } = args;

    if (!event_types?.length) {
      throw new Error('event_types is required for subscription');
    }

    this.logger.info(`Subscribing to events: ${event_types.join(', ')}`);

    try {
      const subscription = await this.p2pConnector.subscribe({
        eventTypes: event_types,
        filterAddress: filter_address,
        callbackWebhook: callback_webhook,
        subscriberId: this.p2pConnector.getNodeId(),
      });

      return {
        success: true,
        subscription_id: subscription.id,
        event_types,
        filter_address,
        callback_webhook,
        subscribed_at: new Date().toISOString(),
        status: 'active',
      };
    } catch (error) {
      this.logger.error('Failed to create subscription:', error);
      throw new Error(`Subscription failed: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  // Helper methods

  private parseSemanticAddress(address: string): SemanticAddress {
    // Parse bzzz://agent:role@project:task/path
    const bzzzMatch = address.match(/^bzzz:\/\/([^@\/]+)@([^\/]+)(?:\/(.+))?$/);
    
    if (bzzzMatch) {
      const [, agentRole, projectTask, path] = bzzzMatch;
      const [agent, role] = agentRole.split(':');
      const [project, task] = projectTask.split(':');
      
      return {
        agent: agent !== '*' ? agent : undefined,
        role: role !== '*' ? role : undefined,
        project: project !== '*' ? project : undefined,
        task: task !== '*' ? task : undefined,
        path: path || undefined,
        raw: address,
      };
    }

    // Simple address format
    return { raw: address };
  }

  private async discoverAgents(address: SemanticAddress, criteria: any): Promise<any[]> {
    const agents = await this.agentManager.getAgents();
    
    return agents.filter(agent => {
      if (address.agent && agent.id !== address.agent) return false;
      if (address.role && agent.role !== address.role) return false;
      if (criteria.availability && !agent.available) return false;
      if (criteria.performance_threshold && agent.performance < criteria.performance_threshold) return false;
      if (criteria.expertise?.length && !criteria.expertise.some((exp: string) => 
        agent.capabilities.includes(exp))) return false;
      
      return true;
    });
  }

  private async queryP2PNetwork(address: SemanticAddress): Promise<any[]> {
    // Query the P2P network for matching agents
    const query = {
      type: 'agent_discovery',
      criteria: address,
      timestamp: new Date().toISOString(),
    };

    const responses = await this.p2pConnector.queryNetwork(query, 5000); // 5 second timeout
    return responses;
  }

  private rankMatches(matches: any[], address: SemanticAddress, criteria: any): any[] {
    return matches
      .map(match => ({
        ...match,
        score: this.calculateMatchScore(match, address, criteria),
      }))
      .sort((a, b) => b.score - a.score)
      .slice(0, 20); // Limit to top 20 matches
  }

  private calculateMatchScore(match: any, address: SemanticAddress, criteria: any): number {
    let score = 0;

    // Exact matches get highest score
    if (address.agent && match.id === address.agent) score += 100;
    if (address.role && match.role === address.role) score += 50;
    
    // Capability matching
    if (criteria.expertise?.length) {
      const matchingExp = criteria.expertise.filter((exp: string) => 
        match.capabilities?.includes(exp)
      ).length;
      score += (matchingExp / criteria.expertise.length) * 30;
    }

    // Availability bonus
    if (match.available) score += 10;
    
    // Performance bonus
    if (match.performance) score += match.performance * 10;

    return score;
  }

  private async getAgentContent(address: SemanticAddress, maxHistory: number): Promise<any> {
    const agent = await this.agentManager.getAgent(address.agent!);
    if (!agent) {
      throw new Error(`Agent ${address.agent} not found`);
    }

    const content = {
      agent_info: agent,
      recent_activity: await this.agentManager.getRecentActivity(address.agent!, maxHistory),
      current_tasks: await this.agentManager.getCurrentTasks(address.agent!),
    };

    if (address.path) {
      (content as any)[address.path] = await this.agentManager.getAgentData(address.agent!, address.path);
    }

    return content;
  }

  private async getProjectContent(address: SemanticAddress, maxHistory: number): Promise<any> {
    // Get project-related content from P2P network
    return await this.p2pConnector.getProjectData(address.project!, maxHistory);
  }

  private async getNetworkContent(address: SemanticAddress, maxHistory: number): Promise<any> {
    // Get general network content
    return await this.p2pConnector.getNetworkData(address.raw, maxHistory);
  }

  private async getAgentMetadata(agentId: string): Promise<any> {
    return await this.agentManager.getAgentMetadata(agentId);
  }

  private async getProjectMetadata(projectId: string): Promise<any> {
    return await this.p2pConnector.getProjectMetadata(projectId);
  }

  private async postToAgent(agentId: string, message: any): Promise<any> {
    return await this.p2pConnector.sendDirectMessage(agentId, message);
  }

  private async postToRole(role: string, message: any): Promise<any> {
    const topic = `bzzz/roles/${role.toLowerCase().replace(/\s+/g, '_')}/v1`;
    return await this.p2pConnector.publishMessage(topic, message);
  }

  private async postToProject(projectId: string, message: any): Promise<any> {
    const topic = `bzzz/projects/${projectId}/coordination/v1`;
    return await this.p2pConnector.publishMessage(topic, message);
  }

  private async postToNetwork(message: any): Promise<any> {
    return await this.p2pConnector.publishMessage('bzzz/coordination/v1', message);
  }

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