/**
 * Unified TypeScript Coordination Wrapper
 *
 * Provides a type-safe interface for all Redis-based coordination operations
 * in the CFN Loop critical path. Wraps the existing redis-coordinator with
 * semantic coordination methods for:
 * - Agent lifecycle management
 * - Signal/wait primitives
 * - Consensus collection
 * - Task state management
 *
 * Key patterns:
 * - swarm:* namespace for unified coordination
 * - Backward compatible with cfn_loop:task:* legacy namespace
 * - Type-safe agent state tracking
 * - Automatic timeout handling
 */

import * as Redis from 'ioredis';
import type { Redis as RedisClient } from 'ioredis';
import { EventEmitter } from 'events';

/**
 * Configuration for coordination wrapper
 */
export interface CoordinationConfig {
  redisHost: string;
  redisPort: number;
  redisDb?: number;
  taskId: string;
  namespace?: 'swarm' | 'cfn_loop';
  defaultTimeout?: number;
}

/**
 * Agent state as tracked in Redis
 */
export interface AgentState {
  agentId: string;
  type: string;
  status: 'spawned' | 'running' | 'waiting' | 'completed' | 'failed';
  confidence?: number;
  testPassRate?: number;
  testsRun?: number;
  testsPassed?: number;
  result?: Record<string, unknown>;
  timestamp: string;
  iteration?: number;
}

/**
 * Consensus score from validator
 */
export interface ConsensusScore {
  agentId: string;
  score: number;
  feedback?: string;
  timestamp: string;
}

/**
 * Task state snapshot
 */
export interface TaskState {
  taskId: string;
  status: 'pending' | 'in_progress' | 'completed' | 'failed';
  context: Record<string, unknown>;
  agents: AgentState[];
  consensus?: number;
  iteration: number;
  updatedAt: string;
}

/**
 * Coordination signal/wait result
 */
export interface SignalResult {
  received: boolean;
  message?: string;
  timestamp?: string;
  timeout: boolean;
}

/**
 * Unified Coordination Wrapper
 *
 * Provides semantic coordination methods that abstract away Redis key patterns
 * and protocol details while maintaining type safety.
 */
export class CoordinationWrapper extends EventEmitter {
  private redis: RedisClient;
  private taskId: string;
  private namespace: 'swarm' | 'cfn_loop';
  private defaultTimeout: number;
  private isConnected: boolean = false;

  constructor(config: CoordinationConfig) {
    super();
    this.taskId = config.taskId;
    this.namespace = config.namespace || 'swarm';
    this.defaultTimeout = config.defaultTimeout || 120000; // 120 seconds

    // Create Redis client with provided configuration
    this.redis = new (Redis as any)({
      host: config.redisHost,
      port: config.redisPort,
      db: config.redisDb || 0,
      lazyConnect: true,
      maxRetriesPerRequest: null, // Needed for blocking operations
      enableReadyCheck: false
    });

    // Set up error handling
    this.redis.on('error', (error) => {
      this.emit('redis-error', error);
    });

    this.redis.on('connect', () => {
      this.isConnected = true;
      this.emit('connected');
    });

    this.redis.on('disconnect', () => {
      this.isConnected = false;
      this.emit('disconnected');
    });
  }

  /**
   * Connect to Redis
   */
  async connect(): Promise<void> {
    if (!this.isConnected) {
      await this.redis.connect();
    }
  }

  /**
   * Disconnect from Redis
   */
  async disconnect(): Promise<void> {
    if (this.isConnected) {
      await this.redis.disconnect();
    }
  }

  /**
   * Check if connected to Redis
   */
  isReady(): boolean {
    return this.isConnected;
  }

  /**
   * AGENT LIFECYCLE MANAGEMENT
   */

  /**
   * Register a spawned agent in the coordination system
   */
  async registerAgent(agentId: string, agentType: string): Promise<void> {
    const state: AgentState = {
      agentId,
      type: agentType,
      status: 'spawned',
      timestamp: new Date().toISOString()
    };

    const key = this.getAgentStateKey(agentId);
    await this.redis.set(key, JSON.stringify(state), 'EX', 86400); // 24h expiry
  }

  /**
   * Update agent status
   */
  async updateAgentStatus(
    agentId: string,
    status: AgentState['status']
  ): Promise<void> {
    const key = this.getAgentStateKey(agentId);
    const state = await this.getAgentState(agentId);

    if (!state) {
      throw new Error(`Agent ${agentId} not registered`);
    }

    state.status = status;
    state.timestamp = new Date().toISOString();

    await this.redis.set(key, JSON.stringify(state), 'EX', 86400);
  }

  /**
   * Signal agent completion with confidence score
   */
  async signalCompletion(
    agentId: string,
    confidence: number,
    options?: {
      testPassRate?: number;
      testsRun?: number;
      testsPassed?: number;
      result?: Record<string, unknown>;
      iteration?: number;
    }
  ): Promise<void> {
    const key = this.getAgentStateKey(agentId);
    const state = await this.getAgentState(agentId);

    if (!state) {
      throw new Error(`Agent ${agentId} not registered`);
    }

    state.status = 'completed';
    state.confidence = confidence;
    state.testPassRate = options?.testPassRate;
    state.testsRun = options?.testsRun;
    state.testsPassed = options?.testsPassed;
    state.result = options?.result;
    state.iteration = options?.iteration;
    state.timestamp = new Date().toISOString();

    // Store in agent state with 24h expiry
    await this.redis.set(key, JSON.stringify(state), 'EX', 86400);

    // Also publish completion signal to waiting agents
    const channel = this.getCompletionChannel(agentId);
    await this.redis.publish(channel, JSON.stringify(state));

    // Add to completion leaderboard for consensus collection
    const leaderboardKey = this.getCompletionLeaderboardKey();
    await this.redis.zadd(
      leaderboardKey,
      confidence * 100, // Score for sorting
      JSON.stringify({
        agentId,
        confidence,
        timestamp: state.timestamp
      })
    );
  }

  /**
   * Get agent state
   */
  async getAgentState(agentId: string): Promise<AgentState | null> {
    const key = this.getAgentStateKey(agentId);
    const data = await this.redis.get(key);

    if (!data) {
      return null;
    }

    return JSON.parse(data) as AgentState;
  }

  /**
   * Get all agents in current task
   */
  async getAllAgents(): Promise<AgentState[]> {
    const pattern = this.getAgentStateKeyPattern();
    const keys = await this.redis.keys(pattern);

    const agents: AgentState[] = [];
    for (const key of keys) {
      const data = await this.redis.get(key);
      if (data) {
        agents.push(JSON.parse(data) as AgentState);
      }
    }

    return agents;
  }

  /**
   * COORDINATION PRIMITIVES
   */

  /**
   * Wait for a coordination signal with timeout
   */
  async waitForSignal(
    channel: string,
    timeoutMs?: number
  ): Promise<SignalResult> {
    const timeout = timeoutMs || this.defaultTimeout;
    const timeoutSeconds = Math.ceil(timeout / 1000);

    try {
      // Use BLPOP to block-wait on a list
      const listKey = this.getSignalKey(channel);
      const result = await this.redis.blpop(listKey, timeoutSeconds);

      if (result === null) {
        return {
          received: false,
          timeout: true
        };
      }

      return {
        received: true,
        message: result[1],
        timestamp: new Date().toISOString(),
        timeout: false
      };
    } catch (error) {
      return {
        received: false,
        timeout: false
      };
    }
  }

  /**
   * Broadcast a signal to all waiting agents
   */
  async broadcastSignal(channel: string, message: string): Promise<void> {
    // Use pub/sub for broadcast (fire-and-forget to subscribers)
    await this.redis.publish(channel, message);

    // Also store in a list for new subscribers (via BLPOP pattern)
    const listKey = this.getSignalKey(channel);
    await this.redis.lpush(listKey, message);

    // Keep only recent signals (last 100)
    await this.redis.ltrim(listKey, 0, 99);

    // Expire the list after 1 hour
    await this.redis.expire(listKey, 3600);
  }

  /**
   * Subscribe to signal channel (pub/sub pattern)
   */
  subscribeToSignal(
    channel: string,
    callback: (message: string) => void
  ): () => void {
    // Create a separate subscriber for pub/sub
    const subscriber = this.redis.duplicate();

    subscriber.on('message', (chan, message) => {
      if (chan === channel) {
        callback(message);
      }
    });

    subscriber.subscribe(channel);

    // Return unsubscribe function
    return () => {
      subscriber.unsubscribe(channel);
      subscriber.disconnect();
    };
  }

  /**
   * CONSENSUS & VALIDATION
   */

  /**
   * Collect consensus scores from multiple validators
   */
  async collectConsensus(
    agentIds: string[],
    timeoutMs?: number
  ): Promise<ConsensusScore[]> {
    const timeout = timeoutMs || this.defaultTimeout;
    const startTime = Date.now();
    const scores: ConsensusScore[] = [];

    for (const agentId of agentIds) {
      const elapsed = Date.now() - startTime;
      const remaining = Math.max(1, timeout - elapsed);
      const remainingSeconds = Math.ceil(remaining / 1000);

      // Wait for each validator to report consensus
      const scoreKey = this.getConsensusKey(agentId);
      const data = await this.redis.blpop(scoreKey, remainingSeconds);

      if (data) {
        const score = JSON.parse(data[1]) as ConsensusScore;
        scores.push(score);
      }
    }

    return scores;
  }

  /**
   * Report a consensus score
   */
  async reportConsensusScore(
    agentId: string,
    score: number,
    feedback?: string
  ): Promise<void> {
    const consensusScore: ConsensusScore = {
      agentId,
      score,
      feedback,
      timestamp: new Date().toISOString()
    };

    const key = this.getConsensusKey(agentId);
    await this.redis.lpush(key, JSON.stringify(consensusScore));
    await this.redis.expire(key, 86400); // 24h expiry
  }

  /**
   * Calculate average consensus from scores
   */
  calculateAverageConsensus(scores: ConsensusScore[]): number {
    if (scores.length === 0) {
      return 0;
    }

    const sum = scores.reduce((acc, score) => acc + score.score, 0);
    return sum / scores.length;
  }

  /**
   * TASK STATE MANAGEMENT
   */

  /**
   * Store task context (usually CFN Loop parameters)
   */
  async storeTaskContext(context: Record<string, unknown>): Promise<void> {
    const key = this.getTaskContextKey();
    await this.redis.set(key, JSON.stringify(context), 'EX', 86400);
  }

  /**
   * Load task context
   */
  async loadTaskContext(): Promise<Record<string, unknown> | null> {
    const key = this.getTaskContextKey();
    const data = await this.redis.get(key);

    if (!data) {
      return null;
    }

    return JSON.parse(data) as Record<string, unknown>;
  }

  /**
   * Update task status
   */
  async updateTaskStatus(
    status: TaskState['status'],
    iteration?: number
  ): Promise<void> {
    const key = this.getTaskStatusKey();

    const state: Partial<TaskState> = {
      taskId: this.taskId,
      status,
      updatedAt: new Date().toISOString()
    };

    if (iteration !== undefined) {
      state.iteration = iteration;
    }

    // Get existing context if available
    const contextData = await this.loadTaskContext();
    if (contextData) {
      state.context = contextData;
    }

    // Get all agents
    state.agents = await this.getAllAgents();

    await this.redis.set(key, JSON.stringify(state), 'EX', 86400);
  }

  /**
   * Get full task state snapshot
   */
  async getTaskState(): Promise<TaskState | null> {
    const key = this.getTaskStatusKey();
    const data = await this.redis.get(key);

    if (!data) {
      return null;
    }

    return JSON.parse(data) as TaskState;
  }

  /**
   * HELPER METHODS FOR KEY GENERATION
   */

  private getAgentStateKey(agentId: string): string {
    if (this.namespace === 'cfn_loop') {
      return `cfn_loop:task:${this.taskId}:agent:${agentId}`;
    }
    return `${this.namespace}:${this.taskId}:agent:${agentId}`;
  }

  private getAgentStateKeyPattern(): string {
    if (this.namespace === 'cfn_loop') {
      return `cfn_loop:task:${this.taskId}:agent:*`;
    }
    return `${this.namespace}:${this.taskId}:agent:*`;
  }

  private getCompletionChannel(agentId: string): string {
    if (this.namespace === 'cfn_loop') {
      return `cfn_loop:task:${this.taskId}:completion:${agentId}`;
    }
    return `${this.namespace}:${this.taskId}:completion`;
  }

  private getCompletionLeaderboardKey(): string {
    if (this.namespace === 'cfn_loop') {
      return `cfn_loop:task:${this.taskId}:completions`;
    }
    return `${this.namespace}:${this.taskId}:completions`;
  }

  private getSignalKey(channel: string): string {
    if (this.namespace === 'cfn_loop') {
      return `cfn_loop:task:${this.taskId}:signal:${channel}`;
    }
    return `${this.namespace}:${this.taskId}:signal:${channel}`;
  }

  private getConsensusKey(agentId: string): string {
    if (this.namespace === 'cfn_loop') {
      return `cfn_loop:task:${this.taskId}:consensus:${agentId}`;
    }
    return `${this.namespace}:${this.taskId}:consensus:${agentId}`;
  }

  private getTaskContextKey(): string {
    if (this.namespace === 'cfn_loop') {
      return `cfn_loop:task:${this.taskId}:context`;
    }
    return `${this.namespace}:${this.taskId}:context`;
  }

  private getTaskStatusKey(): string {
    if (this.namespace === 'cfn_loop') {
      return `cfn_loop:task:${this.taskId}:status`;
    }
    return `${this.namespace}:${this.taskId}:status`;
  }

  /**
   * CLEANUP & UTILITIES
   */

  /**
   * Clear all coordination state for this task
   */
  async clearTaskState(): Promise<void> {
    const pattern = this.getTaskKeysPattern();
    const keys = await this.redis.keys(pattern);

    if (keys.length > 0) {
      await this.redis.del(...keys);
    }
  }

  private getTaskKeysPattern(): string {
    if (this.namespace === 'cfn_loop') {
      return `cfn_loop:task:${this.taskId}:*`;
    }
    return `${this.namespace}:${this.taskId}:*`;
  }

  /**
   * Get Redis client for advanced operations
   */
  getRedisClient(): RedisClient {
    return this.redis;
  }
}

export default CoordinationWrapper;
