import { Controller, Post, Body, Param, Delete, Get, NotFoundException, BadRequestException, Logger } from '@nestjs/common';
import { TenantId } from '@it0/common';
import { EngineRegistry } from '../../../infrastructure/engines/engine-registry';
import { AgentStreamGateway } from '../../ws/agent-stream.gateway';
import { SessionRepository } from '../../../infrastructure/repositories/session.repository';
import { TaskRepository } from '../../../infrastructure/repositories/task.repository';
import { AgentSession } from '../../../domain/entities/agent-session.entity';
import { AgentTask } from '../../../domain/entities/agent-task.entity';
import { TaskStatus } from '../../../domain/value-objects/task-status.vo';
import { AgentEngineType } from '../../../domain/value-objects/agent-engine-type.vo';
import * as crypto from 'crypto';

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

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

  // TODO 10: Execute task
  @Post('tasks')
  async executeTask(
    @TenantId() tenantId: string,
    @Body() body: { prompt: string; systemPrompt?: string; maxTurns?: number; allowedTools?: string[] },
  ) {
    const engine = this.engineRegistry.getActiveEngine();

    const session = new AgentSession();
    session.id = crypto.randomUUID();
    session.tenantId = tenantId;
    session.engineType = engine.engineType;
    session.status = 'active';
    session.systemPrompt = body.systemPrompt;
    session.metadata = {};
    session.createdAt = new Date();
    session.updatedAt = new Date();
    await this.sessionRepository.save(session);

    const task = new AgentTask();
    task.id = crypto.randomUUID();
    task.tenantId = tenantId;
    task.sessionId = session.id;
    task.prompt = body.prompt;
    task.status = TaskStatus.RUNNING;
    task.startedAt = new Date();
    task.createdAt = new Date();
    await this.taskRepository.save(task);

    // Fire-and-forget: iterate engine stream and emit events via gateway
    (async () => {
      try {
        this.logger.log(`[Task ${task.id}] Starting engine stream for session=${session.id}, prompt="${body.prompt.slice(0, 80)}"`);
        const stream = engine.executeTask({
          sessionId: session.id,
          prompt: body.prompt,
          systemPrompt: body.systemPrompt || '',
          allowedTools: body.allowedTools || [],
          maxTurns: body.maxTurns || 10,
        });

        let eventCount = 0;
        let finished = false;
        for await (const event of stream) {
          eventCount++;
          this.logger.log(`[Task ${task.id}] Event #${eventCount}: type=${event.type}${event.type === 'text' ? ` len=${(event as any).content?.length}` : ''}${event.type === 'error' ? ` msg=${(event as any).message}` : ''}`);
          this.gateway.emitStreamEvent(session.id, event);

          if (event.type === 'completed' && !finished) {
            finished = true;
            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' && !finished) {
            finished = true;
            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);
          }
        }
        this.logger.log(`[Task ${task.id}] Stream ended after ${eventCount} events`);
      } catch (error) {
        this.logger.error(`[Task ${task.id}] Stream error: ${error instanceof Error ? error.message : 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);

        this.gateway.emitStreamEvent(session.id, {
          type: 'error',
          message: task.result,
          code: 'EXECUTION_ERROR',
        });
      }
    })();

    return { sessionId: session.id, taskId: task.id };
  }

  // TODO 11: Cancel task
  @Delete('tasks/:taskId')
  async cancelTask(@Param('taskId') taskId: string) {
    const task = await this.taskRepository.findById(taskId);
    if (!task) {
      throw new NotFoundException(`Task ${taskId} not found`);
    }

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

    const engine = this.engineRegistry.getActiveEngine();
    await engine.cancelTask(session.id);

    task.status = TaskStatus.CANCELLED;
    task.completedAt = new Date();
    await this.taskRepository.save(task);

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

    return { message: 'Task cancelled', taskId };
  }

  // TODO 12: Approve command
  @Post('tasks/:taskId/approve')
  async approveCommand(@Param('taskId') taskId: string, @Body() body: { approved: boolean }) {
    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();

    if (body.approved) {
      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) {
          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);
        }
      })();

      return { message: 'Command approved', taskId };
    } else {
      task.status = TaskStatus.CANCELLED;
      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);

      return { message: 'Command rejected', taskId };
    }
  }

  // TODO 13: List engines
  @Get('engines')
  async listEngines() {
    const engines = this.engineRegistry.listAvailable();
    return { engines };
  }

  // TODO 14: Switch engine
  @Post('engines/switch')
  async switchEngine(@Body() body: { engineType: string }) {
    const engine = this.engineRegistry.switchEngine(body.engineType as AgentEngineType);
    return { message: 'Engine switched', engineType: engine.engineType };
  }
}
