/**
 * VoiceSessionController
 *
 * HTTP endpoints for the long-lived voice agent session lifecycle:
 *
 *   POST   /api/v1/agent/sessions/voice/start         — called when voice call starts
 *   POST   /api/v1/agent/sessions/:sessionId/voice/inject — called per speech turn
 *   DELETE /api/v1/agent/sessions/:sessionId/voice    — called when user hangs up
 *
 * These endpoints replace the per-turn "POST /api/v1/agent/tasks" flow used
 * by text chat.  The voice-agent calls voice/start once, injects messages as
 * the user speaks, and calls voice/terminate on room disconnect.
 */
import {
  Controller, Post, Delete, Param, Body,
  NotFoundException, BadRequestException, Logger,
} from '@nestjs/common';
import { TenantId } from '@it0/common';
import { VoiceSessionManager } from '../../../domain/services/voice-session-manager.service';
import { SessionRepository } from '../../../infrastructure/repositories/session.repository';
import { AgentEngineType } from '../../../domain/value-objects/agent-engine-type.vo';
import { AgentSession } from '../../../domain/entities/agent-session.entity';
import * as crypto from 'crypto';

@Controller('api/v1/agent/sessions')
export class VoiceSessionController {
  private readonly logger = new Logger(VoiceSessionController.name);

  constructor(
    private readonly voiceSessionManager: VoiceSessionManager,
    private readonly sessionRepository: SessionRepository,
  ) {}

  /**
   * Start a long-lived voice agent session.
   *
   * Called ONCE when the user opens a voice call.
   * Creates (or reuses) an AgentSession and starts the background run loop.
   * Idempotent: safe to call multiple times for the same sessionId.
   */
  @Post('voice/start')
  async startVoiceSession(
    @TenantId() tenantId: string,
    @Body() body: { sessionId?: string; systemPrompt?: string },
  ) {
    let session: AgentSession | null = null;

    if (body.sessionId) {
      session = await this.sessionRepository.findById(body.sessionId);
      // Reject cross-tenant access
      if (session && session.tenantId !== tenantId) {
        session = null;
      }
    }

    if (!session) {
      // Create a fresh session pre-marked as voice mode
      session = new AgentSession();
      session.id = crypto.randomUUID();
      session.tenantId = tenantId;
      session.engineType = AgentEngineType.CLAUDE_AGENT_SDK;
      session.status = 'active';
      session.systemPrompt = body.systemPrompt;
      session.metadata = { voiceMode: true, title: '', titleSet: true };
      session.createdAt = new Date();
      session.updatedAt = new Date();
    } else {
      // Reuse existing session; mark as voice mode
      session.metadata = {
        ...session.metadata as Record<string, unknown>,
        voiceMode: true,
      };
      session.status = 'active';
      session.updatedAt = new Date();
    }

    await this.sessionRepository.save(session);

    // Start (or no-op if already running) the long-lived run loop
    await this.voiceSessionManager.startSession(session.id, tenantId);

    this.logger.log(`Voice session started: ${session.id} tenant=${tenantId}`);
    return { sessionId: session.id };
  }

  /**
   * Inject a new speech-turn message into the running voice session.
   *
   * Called each time STT produces a user utterance.
   * The background run loop picks up the message and executes one SDK turn,
   * streaming the response back via the WebSocket gateway.
   */
  @Post(':sessionId/voice/inject')
  async injectVoiceMessage(
    @TenantId() tenantId: string,
    @Param('sessionId') sessionId: string,
    @Body() body: { message: string },
  ) {
    if (!body.message?.trim()) {
      throw new BadRequestException('message is required');
    }

    // Verify session ownership
    const session = await this.sessionRepository.findById(sessionId);
    if (!session || session.tenantId !== tenantId) {
      throw new NotFoundException(`Voice session ${sessionId} not found`);
    }

    // Auto-start the run loop if the voice-agent reconnected (e.g. container restart)
    if (!this.voiceSessionManager.isActive(sessionId)) {
      this.logger.warn(`[VoiceSession ${sessionId}] Run loop not active — auto-starting`);
      await this.voiceSessionManager.startSession(sessionId, tenantId);
    }

    const injected = await this.voiceSessionManager.injectMessage(sessionId, body.message);
    if (!injected) {
      throw new NotFoundException(`Voice session ${sessionId} is not active`);
    }

    return { sessionId, injected: true };
  }

  /**
   * Terminate the voice session (user hung up).
   *
   * Sends a poison-pill to the run loop, aborts any in-flight SDK call,
   * and marks the AgentSession as completed.
   * Idempotent: safe to call even if the session is already gone.
   */
  @Delete(':sessionId/voice')
  async terminateVoiceSession(
    @TenantId() tenantId: string,
    @Param('sessionId') sessionId: string,
  ) {
    const session = await this.sessionRepository.findById(sessionId);

    if (session && session.tenantId === tenantId) {
      await this.voiceSessionManager.terminateSession(sessionId);

      session.status = 'completed';
      session.updatedAt = new Date();
      await this.sessionRepository.save(session);
    }

    this.logger.log(`Voice session terminated: ${sessionId}`);
    return { sessionId, terminated: true };
  }
}
