import { Logger } from 'pino';
import { spawn, ChildProcess } from 'child_process';
import { SessionManager, ExtensionConnection } from './session-manager.js';
import path from 'path';

export interface LiveKitAgentInstance {
  userId: string;
  sessionId: string;
  process: ChildProcess;
  roomName: string;
  startedAt: number;
  status: 'starting' | 'running' | 'stopping' | 'stopped' | 'error';
  pid?: number;
}

export class LiveKitAgentManager {
  private logger: Logger;
  private sessionManager: SessionManager;
  private agentInstances: Map<string, LiveKitAgentInstance> = new Map(); // sessionId -> agent
  private userToAgent: Map<string, string> = new Map(); // userId -> sessionId
  private agentPath: string;
  private liveKitConfig: any;

  constructor(logger: Logger, sessionManager: SessionManager, agentPath?: string) {
    this.logger = logger;
    this.sessionManager = sessionManager;
    this.agentPath = agentPath || path.join(process.cwd(), '../../agent-livekit');
    this.liveKitConfig = this.loadLiveKitConfig();
  }

  private loadLiveKitConfig(): any {
    // Default LiveKit configuration
    return {
      livekit_url: process.env.LIVEKIT_URL || 'ws://localhost:7880',
      api_key: process.env.LIVEKIT_API_KEY || 'devkey',
      api_secret: process.env.LIVEKIT_API_SECRET || 'secret',
      room_prefix: 'mcp-chrome-user-',
    };
  }

  /**
   * Start a LiveKit agent for a Chrome extension connection
   */
  async startAgentForConnection(connection: ExtensionConnection): Promise<LiveKitAgentInstance> {
    const { userId, sessionId } = connection;

    // Check if agent already exists for this user
    const existingSessionId = this.userToAgent.get(userId);
    if (existingSessionId && this.agentInstances.has(existingSessionId)) {
      const existingAgent = this.agentInstances.get(existingSessionId)!;
      if (existingAgent.status === 'running' || existingAgent.status === 'starting') {
        this.logger.info(`Agent already running for user ${userId}, reusing existing agent`);
        return existingAgent;
      }
    }

    // Create room name based on user ID
    const roomName = `${this.liveKitConfig.room_prefix}${userId}`;

    this.logger.info(
      `Starting LiveKit agent for user ${userId}, session ${sessionId}, room ${roomName}`,
    );

    // Create agent instance record
    const agentInstance: LiveKitAgentInstance = {
      userId,
      sessionId,
      process: null as any, // Will be set below
      roomName,
      startedAt: Date.now(),
      status: 'starting',
    };

    try {
      // Spawn the full LiveKit agent process directly
      const agentProcess = spawn(
        'python',
        [
          'livekit_agent.py',
          'start',
          '--url',
          this.liveKitConfig.livekit_url,
          '--api-key',
          this.liveKitConfig.api_key,
          '--api-secret',
          this.liveKitConfig.api_secret,
        ],
        {
          cwd: this.agentPath,
          env: {
            ...process.env,
            LIVEKIT_URL: this.liveKitConfig.livekit_url,
            LIVEKIT_API_KEY: this.liveKitConfig.api_key,
            LIVEKIT_API_SECRET: this.liveKitConfig.api_secret,
            MCP_SERVER_URL: 'http://localhost:3001/mcp',
            CHROME_USER_ID: userId, // Pass the user ID as environment variable
            // Voice processing optimization
            LIVEKIT_ROOM_NAME: roomName,
            OPENAI_API_KEY: process.env.OPENAI_API_KEY || '',
            DEEPGRAM_API_KEY: process.env.DEEPGRAM_API_KEY || '',
          },
          stdio: ['pipe', 'pipe', 'pipe'],
        },
      );

      agentInstance.process = agentProcess;
      agentInstance.pid = agentProcess.pid;

      // Set up process event handlers
      agentProcess.stdout?.on('data', (data) => {
        const output = data.toString();
        this.logger.info(`[Agent ${userId}] ${output.trim()}`);

        // Check for successful startup
        if (
          output.includes('Agent initialized successfully') ||
          output.includes('LiveKit agent started')
        ) {
          agentInstance.status = 'running';
          this.logger.info(`LiveKit agent for user ${userId} is now running`);
        }
      });

      agentProcess.stderr?.on('data', (data) => {
        const error = data.toString();
        this.logger.error(`[Agent ${userId}] ERROR: ${error.trim()}`);
      });

      agentProcess.on('close', (code) => {
        this.logger.info(`LiveKit agent for user ${userId} exited with code ${code}`);
        agentInstance.status = code === 0 ? 'stopped' : 'error';

        // Clean up mappings
        this.agentInstances.delete(sessionId);
        this.userToAgent.delete(userId);
      });

      agentProcess.on('error', (error) => {
        this.logger.error(`Failed to start LiveKit agent for user ${userId}:`, error);
        agentInstance.status = 'error';
      });

      // Store the agent instance
      this.agentInstances.set(sessionId, agentInstance);
      this.userToAgent.set(userId, sessionId);

      this.logger.info(
        `LiveKit agent process started for user ${userId} with PID ${agentProcess.pid}`,
      );

      return agentInstance;
    } catch (error) {
      this.logger.error(`Error starting LiveKit agent for user ${userId}:`, error);
      agentInstance.status = 'error';
      throw error;
    }
  }

  /**
   * Stop a LiveKit agent for a user
   */
  async stopAgentForUser(userId: string): Promise<boolean> {
    const sessionId = this.userToAgent.get(userId);
    if (!sessionId) {
      this.logger.warn(`No agent found for user ${userId}`);
      return false;
    }

    return this.stopAgentForSession(sessionId);
  }

  /**
   * Stop a LiveKit agent for a session
   */
  async stopAgentForSession(sessionId: string): Promise<boolean> {
    const agentInstance = this.agentInstances.get(sessionId);
    if (!agentInstance) {
      this.logger.warn(`No agent found for session ${sessionId}`);
      return false;
    }

    this.logger.info(
      `Stopping LiveKit agent for user ${agentInstance.userId}, session ${sessionId}`,
    );

    agentInstance.status = 'stopping';

    try {
      if (agentInstance.process && !agentInstance.process.killed) {
        // Try graceful shutdown first
        agentInstance.process.kill('SIGTERM');

        // Force kill after 5 seconds if still running
        setTimeout(() => {
          if (agentInstance.process && !agentInstance.process.killed) {
            this.logger.warn(`Force killing LiveKit agent for user ${agentInstance.userId}`);
            agentInstance.process.kill('SIGKILL');
          }
        }, 5000);
      }

      return true;
    } catch (error) {
      this.logger.error(`Error stopping LiveKit agent for user ${agentInstance.userId}:`, error);
      return false;
    }
  }

  /**
   * Handle Chrome extension connection
   */
  async onChromeExtensionConnected(connection: ExtensionConnection): Promise<void> {
    this.logger.info(
      `Chrome extension connected, starting LiveKit agent for user ${connection.userId}`,
    );

    try {
      await this.startAgentForConnection(connection);
    } catch (error) {
      this.logger.error(`Failed to start LiveKit agent for Chrome connection:`, error);
    }
  }

  /**
   * Handle Chrome extension disconnection
   */
  async onChromeExtensionDisconnected(connection: ExtensionConnection): Promise<void> {
    this.logger.info(
      `Chrome extension disconnected, stopping LiveKit agent for user ${connection.userId}`,
    );

    try {
      await this.stopAgentForUser(connection.userId);
    } catch (error) {
      this.logger.error(`Failed to stop LiveKit agent for Chrome disconnection:`, error);
    }
  }

  /**
   * Get agent instance for a user
   */
  getAgentForUser(userId: string): LiveKitAgentInstance | null {
    const sessionId = this.userToAgent.get(userId);
    return sessionId ? this.agentInstances.get(sessionId) || null : null;
  }

  /**
   * Get agent instance for a session
   */
  getAgentForSession(sessionId: string): LiveKitAgentInstance | null {
    return this.agentInstances.get(sessionId) || null;
  }

  /**
   * Get all active agents
   */
  getAllActiveAgents(): LiveKitAgentInstance[] {
    return Array.from(this.agentInstances.values()).filter(
      (agent) => agent.status === 'running' || agent.status === 'starting',
    );
  }

  /**
   * Get agent statistics
   */
  getAgentStats(): any {
    const agents = Array.from(this.agentInstances.values());
    return {
      totalAgents: agents.length,
      runningAgents: agents.filter((a) => a.status === 'running').length,
      startingAgents: agents.filter((a) => a.status === 'starting').length,
      stoppedAgents: agents.filter((a) => a.status === 'stopped').length,
      errorAgents: agents.filter((a) => a.status === 'error').length,
      agentsByUser: Object.fromEntries(this.userToAgent.entries()),
    };
  }

  /**
   * Cleanup stopped agents
   */
  cleanupStoppedAgents(): void {
    const stoppedAgents: string[] = [];

    for (const [sessionId, agent] of this.agentInstances.entries()) {
      if (agent.status === 'stopped' || agent.status === 'error') {
        stoppedAgents.push(sessionId);
      }
    }

    for (const sessionId of stoppedAgents) {
      const agent = this.agentInstances.get(sessionId);
      if (agent) {
        this.agentInstances.delete(sessionId);
        this.userToAgent.delete(agent.userId);
        this.logger.info(`Cleaned up stopped agent for user ${agent.userId}`);
      }
    }
  }

  /**
   * Shutdown all agents
   */
  async shutdownAllAgents(): Promise<void> {
    this.logger.info('Shutting down all LiveKit agents...');

    const shutdownPromises = Array.from(this.agentInstances.keys()).map((sessionId) =>
      this.stopAgentForSession(sessionId),
    );

    await Promise.all(shutdownPromises);

    this.agentInstances.clear();
    this.userToAgent.clear();

    this.logger.info('All LiveKit agents shut down');
  }
}
