import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
import { EngineRegistry } from '../../infrastructure/engines/engine-registry';
import { SessionRepository } from '../../infrastructure/repositories/session.repository';
import { TaskRepository } from '../../infrastructure/repositories/task.repository';
import { AgentStreamGateway } from '../../interfaces/ws/agent-stream.gateway';
import { TaskStatus } from '../../domain/value-objects/task-status.vo';
import { ApproveCommandPort } from '../../domain/ports/inbound/approve-command.port';

@Injectable()
export class ApproveCommandUseCase implements ApproveCommandPort {
  private readonly logger = new Logger(ApproveCommandUseCase.name);

  constructor(
    private readonly engineRegistry: EngineRegistry,
    private readonly sessionRepository: SessionRepository,
    private readonly taskRepository: TaskRepository,
    private readonly gateway: AgentStreamGateway,
  ) {}

  async approve(taskId: string, approvedBy: string): Promise<void> {
    const task = await this.taskRepository.findById(taskId);
    if (!task) {
      throw new NotFoundException(`Task ${taskId} not found`);
    }

    if (task.status !== TaskStatus.AWAITING_APPROVAL) {
      throw new BadRequestException(
        `Task ${taskId} is not awaiting approval (current status: ${task.status})`,
      );
    }

    const session = await this.sessionRepository.findById(task.sessionId);
    if (!session) {
      throw new NotFoundException(`Session ${task.sessionId} not found`);
    }

    const engine = this.engineRegistry.getActiveEngine();

    task.status = TaskStatus.RUNNING;
    await this.taskRepository.save(task);

    // Fire-and-forget: continue the session and stream events
    (async () => {
      try {
        const stream = engine.continueSession(session.id, 'approved');
        for await (const event of stream) {
          this.gateway.emitStreamEvent(session.id, event);

          if (event.type === 'completed') {
            task.status = TaskStatus.COMPLETED;
            task.result = event.summary;
            task.tokensUsed = event.tokensUsed;
            task.completedAt = new Date();
            await this.taskRepository.save(task);

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

          if (event.type === 'error') {
            task.status = TaskStatus.FAILED;
            task.result = event.message;
            task.completedAt = new Date();
            await this.taskRepository.save(task);

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

          if (event.type === 'approval_required') {
            task.status = TaskStatus.AWAITING_APPROVAL;
            await this.taskRepository.save(task);
          }
        }
      } catch (error) {
        this.logger.error(
          `Approval continuation error for task ${taskId}: ${error instanceof Error ? error.message : 'Unknown error'}`,
        );

        task.status = TaskStatus.FAILED;
        task.result = error instanceof Error ? error.message : 'Unknown error';
        task.completedAt = new Date();
        await this.taskRepository.save(task);

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

  async reject(taskId: string, rejectedBy: string, reason?: string): Promise<void> {
    const task = await this.taskRepository.findById(taskId);
    if (!task) {
      throw new NotFoundException(`Task ${taskId} not found`);
    }

    if (task.status !== TaskStatus.AWAITING_APPROVAL) {
      throw new BadRequestException(
        `Task ${taskId} is not awaiting approval (current status: ${task.status})`,
      );
    }

    const session = await this.sessionRepository.findById(task.sessionId);
    if (!session) {
      throw new NotFoundException(`Session ${task.sessionId} not found`);
    }

    const engine = this.engineRegistry.getActiveEngine();

    task.status = TaskStatus.CANCELLED;
    task.result = reason || 'Command rejected';
    task.completedAt = new Date();
    await this.taskRepository.save(task);

    await engine.cancelTask(session.id);

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