import { Injectable, Logger } 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 { UsageRecordRepository } from '../../infrastructure/repositories/usage-record.repository';
import { AgentStreamGateway } from '../../interfaces/ws/agent-stream.gateway';
import { AgentSession } from '../../domain/entities/agent-session.entity';
import { AgentTask } from '../../domain/entities/agent-task.entity';
import { UsageRecord } from '../../domain/entities/usage-record.entity';
import { TaskStatus } from '../../domain/value-objects/task-status.vo';
import { EngineStreamEvent } from '../../domain/ports/outbound/agent-engine.port';
import { EventPublisherPort } from '../../domain/ports/outbound/event-publisher.port';
import { ExecuteTaskDto } from '../dto/execute-task.dto';
import { EventPatterns } from '@it0/common';
import * as crypto from 'crypto';

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

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

  async execute(
    tenantId: string,
    dto: ExecuteTaskDto,
  ): Promise<{ sessionId: string; taskId: string }> {
    const engine = this.engineRegistry.getActiveEngine();

    // Create or reuse session
    let session: AgentSession;
    if (dto.sessionId) {
      const existing = await this.sessionRepository.findById(dto.sessionId);
      if (existing) {
        session = existing;
      } else {
        session = this.createNewSession(tenantId, engine.engineType);
      }
    } else {
      session = this.createNewSession(tenantId, engine.engineType);
    }
    await this.sessionRepository.save(session);

    // Create task record
    const task = new AgentTask();
    task.id = crypto.randomUUID();
    task.tenantId = tenantId;
    task.sessionId = session.id;
    task.prompt = dto.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
    this.runEngineStream(engine, session, task, dto, tenantId);

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

  private createNewSession(tenantId: string, engineType: string): AgentSession {
    const session = new AgentSession();
    session.id = crypto.randomUUID();
    session.tenantId = tenantId;
    session.engineType = engineType;
    session.status = 'active';
    session.metadata = {};
    session.createdAt = new Date();
    session.updatedAt = new Date();
    return session;
  }

  private async runEngineStream(
    engine: any,
    session: AgentSession,
    task: AgentTask,
    dto: ExecuteTaskDto,
    tenantId: string,
  ): Promise<void> {
    try {
      const stream = engine.executeTask({
        sessionId: session.id,
        prompt: dto.prompt,
        systemPrompt: '',
        allowedTools: dto.allowedTools || [],
        maxTurns: dto.maxTurns || 10,
        maxBudgetUsd: dto.maxBudgetUsd,
        skill: dto.skill,
      });

      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);

          // Record usage for billing
          await this.recordUsage(tenantId, task, session, event, engine.engineType);
        }

        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(
        `Engine stream error for session ${session.id}: ${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);

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

  private async recordUsage(
    tenantId: string,
    task: AgentTask,
    session: AgentSession,
    event: Extract<EngineStreamEvent, { type: 'completed' }>,
    engineType: string,
  ): Promise<void> {
    const inputTokens = event.inputTokens ?? 0;
    const outputTokens = event.outputTokens ?? 0;
    const totalTokens = event.tokensUsed ?? (inputTokens + outputTokens);

    if (totalTokens === 0) return;

    try {
      const record = new UsageRecord();
      record.id = crypto.randomUUID();
      record.tenantId = tenantId;
      record.taskId = task.id;
      record.sessionId = session.id;
      record.engineType = engineType;
      record.inputTokens = inputTokens;
      record.outputTokens = outputTokens;
      record.totalTokens = totalTokens;
      record.costUsd = event.costUsd ?? 0;
      record.model = event.model ?? '';

      await this.usageRecordRepository.save(record);

      // Publish billing event for billing-service to aggregate
      await this.eventPublisher.publish(EventPatterns.USAGE_RECORDED, {
        tenantId,
        taskId: task.id,
        sessionId: session.id,
        engineType,
        inputTokens,
        outputTokens,
        totalTokens,
        costUsd: event.costUsd ?? 0,
        model: event.model ?? '',
      });
    } catch (err: any) {
      this.logger.error(`Failed to record usage for task ${task.id}: ${err.message}`);
    }
  }
}
