import Redis from 'ioredis';
import type { FactoryEvent, EventType } from './types';
import { STREAMS } from './types';

// ═══════════════════════════════════════════════════════════
// Redis Streams Event Bus
// ═══════════════════════════════════════════════════════════

const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';

let redis: Redis | null = null;
let subscriber: Redis | null = null;

// In-memory fallback for development without Redis
const inMemoryListeners: Map<string, Array<(event: FactoryEvent) => void>> = new Map();
let useInMemory = false;

function getRedis(): Redis {
  if (!redis) {
    redis = new Redis(REDIS_URL, {
      maxRetriesPerRequest: 3,
      lazyConnect: true,
      retryStrategy(times) {
        if (times > 3) {
          console.warn('[EventBus] Redis unavailable, falling back to in-memory');
          useInMemory = true;
          return undefined as unknown as number; // stop retrying
        }
        return Math.min(times * 200, 2000);
      },
    });
    redis.on('error', (err) => {
      if (!useInMemory) {
        console.warn('[EventBus] Redis error, switching to in-memory:', err.message);
        useInMemory = true;
      }
    });
    redis.on('connect', () => {
      console.log('[EventBus] Redis connected');
      useInMemory = false;
    });
  }
  return redis;
}

function getStreamForEvent(type: EventType): string {
  if (type.startsWith('pipeline.')) return STREAMS.PIPELINES;
  if (type.startsWith('task.')) return STREAMS.TASKS;
  if (type.startsWith('approval.')) return STREAMS.APPROVALS;
  if (type.startsWith('agent.')) return STREAMS.AGENTS;
  if (type.startsWith('deploy.')) return STREAMS.DEPLOYS;
  return STREAMS.NOTIFICATIONS;
}

// ═══════════════════════════════════════════════════════════
// Publish an event
// ═══════════════════════════════════════════════════════════

export async function emit(event: FactoryEvent): Promise<string> {
  const enrichedEvent: FactoryEvent = {
    ...event,
    timestamp: event.timestamp || new Date().toISOString(),
  };

  // In-memory fallback
  if (useInMemory) {
    const listeners = inMemoryListeners.get(event.type) || [];
    const allListeners = inMemoryListeners.get('*') || [];
    [...listeners, ...allListeners].forEach((fn) => {
      try {
        fn(enrichedEvent);
      } catch (err) {
        console.error('[EventBus] Listener error:', err);
      }
    });
    return `mem-${Date.now()}`;
  }

  // Redis Streams
  try {
    const stream = getStreamForEvent(event.type);
    const r = getRedis();
    const id = await r.xadd(
      stream,
      '*',
      'type', event.type,
      'data', JSON.stringify(enrichedEvent),
    );

    // Also publish to pub/sub for real-time WS broadcasting
    await r.publish(`factory:events:${event.type}`, JSON.stringify(enrichedEvent));

    return id || `redis-${Date.now()}`;
  } catch (err) {
    console.error('[EventBus] Failed to emit via Redis, using in-memory:', err);
    useInMemory = true;
    return emit(event); // retry in-memory
  }
}

// ═══════════════════════════════════════════════════════════
// Subscribe to events (in-memory for dev, Redis pub/sub for prod)
// ═══════════════════════════════════════════════════════════

export function on(eventType: EventType | '*', handler: (event: FactoryEvent) => void): () => void {
  if (useInMemory || !redis) {
    // In-memory listener
    const listeners = inMemoryListeners.get(eventType) || [];
    listeners.push(handler);
    inMemoryListeners.set(eventType, listeners);

    return () => {
      const current = inMemoryListeners.get(eventType) || [];
      inMemoryListeners.set(eventType, current.filter((fn) => fn !== handler));
    };
  }

  // Redis pub/sub
  if (!subscriber) {
    subscriber = getRedis().duplicate();
  }

  const channel = eventType === '*' ? 'factory:events:*' : `factory:events:${eventType}`;

  if (eventType === '*') {
    subscriber.psubscribe(channel);
    subscriber.on('pmessage', (_pattern, _ch, message) => {
      try {
        handler(JSON.parse(message));
      } catch (err) {
        console.error('[EventBus] Handler error:', err);
      }
    });
  } else {
    subscriber.subscribe(channel);
    subscriber.on('message', (_ch, message) => {
      try {
        handler(JSON.parse(message));
      } catch (err) {
        console.error('[EventBus] Handler error:', err);
      }
    });
  }

  return () => {
    if (eventType === '*') {
      subscriber?.punsubscribe(channel);
    } else {
      subscriber?.unsubscribe(channel);
    }
  };
}

// ═══════════════════════════════════════════════════════════
// Graceful shutdown
// ═══════════════════════════════════════════════════════════

export async function shutdown(): Promise<void> {
  if (subscriber) await subscriber.quit();
  if (redis) await redis.quit();
  subscriber = null;
  redis = null;
}

// Export the bus as a namespace
export const eventBus = { emit, on, shutdown };
