/// Agent execution endpoints for CQRS API
library;

import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:http/http.dart' as http;

import '../client.dart';
import '../types.dart';

// =============================================================================
// Enums
// =============================================================================

/// Represents the current status of an agent execution
enum ExecutionStatus {
  pending('Pending'),
  running('Running'),
  completed('Completed'),
  failed('Failed'),
  cancelled('Cancelled');

  const ExecutionStatus(this.value);
  final String value;

  static ExecutionStatus fromString(String value) {
    return ExecutionStatus.values.firstWhere(
      (status) => status.value == value,
      orElse: () => ExecutionStatus.pending,
    );
  }

  static ExecutionStatus fromInt(int value) {
    if (value >= 0 && value < ExecutionStatus.values.length) {
      return ExecutionStatus.values[value];
    }
    return ExecutionStatus.pending;
  }
}

// =============================================================================
// Commands
// =============================================================================

/// Command to start an agent execution
class StartAgentExecutionCommand implements Serializable {
  final String agentId;
  final String? conversationId;
  final String userPrompt;

  const StartAgentExecutionCommand({
    required this.agentId,
    this.conversationId,
    required this.userPrompt,
  });

  @override
  Map<String, Object?> toJson() => {
        'agentId': agentId,
        if (conversationId != null) 'conversationId': conversationId,
        'userPrompt': userPrompt,
      };
}

/// Command to complete an agent execution with results
class CompleteAgentExecutionCommand implements Serializable {
  final String executionId;
  final ExecutionStatus status;
  final String? response;
  final int? inputTokens;
  final int? outputTokens;
  final double? estimatedCost;
  final String? errorMessage;

  const CompleteAgentExecutionCommand({
    required this.executionId,
    required this.status,
    this.response,
    this.inputTokens,
    this.outputTokens,
    this.estimatedCost,
    this.errorMessage,
  });

  @override
  Map<String, Object?> toJson() => {
        'executionId': executionId,
        'status': ExecutionStatus.values.indexOf(status),
        if (response != null) 'response': response,
        if (inputTokens != null) 'inputTokens': inputTokens,
        if (outputTokens != null) 'outputTokens': outputTokens,
        if (estimatedCost != null) 'estimatedCost': estimatedCost,
        if (errorMessage != null) 'errorMessage': errorMessage,
      };
}

// =============================================================================
// Queries
// =============================================================================

/// Query to get a single execution by ID
class GetAgentExecutionQuery implements Serializable {
  final String id;

  const GetAgentExecutionQuery({required this.id});

  @override
  Map<String, Object?> toJson() => {'id': id};
}

// =============================================================================
// DTOs
// =============================================================================

/// Response when starting an execution (returns only ID)
class StartExecutionResult {
  final String id;

  const StartExecutionResult({required this.id});

  factory StartExecutionResult.fromJson(Map<String, Object?> json) {
    return StartExecutionResult(
      id: json['id'] as String,
    );
  }

  Map<String, Object?> toJson() => {'id': id};
}

/// Full agent execution details
class AgentExecutionDto {
  final String id;
  final String agentId;
  final String? conversationId;
  final String userPrompt;
  final String? response;
  final ExecutionStatus status;
  final DateTime startedAt;
  final DateTime? completedAt;
  final int? inputTokens;
  final int? outputTokens;
  final double? estimatedCost;
  final int messageCount;
  final String? errorMessage;

  const AgentExecutionDto({
    required this.id,
    required this.agentId,
    this.conversationId,
    required this.userPrompt,
    this.response,
    required this.status,
    required this.startedAt,
    this.completedAt,
    this.inputTokens,
    this.outputTokens,
    this.estimatedCost,
    required this.messageCount,
    this.errorMessage,
  });

  factory AgentExecutionDto.fromJson(Map<String, Object?> json) {
    // Handle status as either int or string
    ExecutionStatus status;
    final Object? statusValue = json['status'];
    if (statusValue is int) {
      status = ExecutionStatus.fromInt(statusValue);
    } else if (statusValue is String) {
      status = ExecutionStatus.fromString(statusValue);
    } else {
      status = ExecutionStatus.pending;
    }

    return AgentExecutionDto(
      id: json['id'] as String,
      agentId: json['agentId'] as String,
      conversationId: json['conversationId'] as String?,
      userPrompt: json['userPrompt'] as String,
      response: json['response'] as String?,
      status: status,
      startedAt: DateTime.parse(json['startedAt'] as String),
      completedAt: json['completedAt'] != null
          ? DateTime.parse(json['completedAt'] as String)
          : null,
      inputTokens: json['inputTokens'] as int?,
      outputTokens: json['outputTokens'] as int?,
      estimatedCost: json['estimatedCost'] != null
          ? (json['estimatedCost'] as num).toDouble()
          : null,
      messageCount: json['messageCount'] as int,
      errorMessage: json['errorMessage'] as String?,
    );
  }

  Map<String, Object?> toJson() => {
        'id': id,
        'agentId': agentId,
        'conversationId': conversationId,
        'userPrompt': userPrompt,
        'response': response,
        'status': status.value,
        'startedAt': startedAt.toIso8601String(),
        'completedAt': completedAt?.toIso8601String(),
        'inputTokens': inputTokens,
        'outputTokens': outputTokens,
        'estimatedCost': estimatedCost,
        'messageCount': messageCount,
        'errorMessage': errorMessage,
      };
}

/// Execution list item (lightweight version for lists)
class ExecutionListItemDto {
  final String id;
  final String agentId;
  final String agentName;
  final String? conversationId;
  final String userPrompt;
  final ExecutionStatus status;
  final DateTime startedAt;
  final DateTime? completedAt;
  final int? inputTokens;
  final int? outputTokens;
  final double? estimatedCost;
  final int messageCount;
  final String? errorMessage;

  const ExecutionListItemDto({
    required this.id,
    required this.agentId,
    required this.agentName,
    this.conversationId,
    required this.userPrompt,
    required this.status,
    required this.startedAt,
    this.completedAt,
    this.inputTokens,
    this.outputTokens,
    this.estimatedCost,
    required this.messageCount,
    this.errorMessage,
  });

  factory ExecutionListItemDto.fromJson(Map<String, Object?> json) {
    // Handle status as either int or string
    ExecutionStatus status;
    final Object? statusValue = json['status'];
    if (statusValue is int) {
      status = ExecutionStatus.fromInt(statusValue);
    } else if (statusValue is String) {
      status = ExecutionStatus.fromString(statusValue);
    } else {
      status = ExecutionStatus.pending;
    }

    return ExecutionListItemDto(
      id: json['id'] as String,
      agentId: json['agentId'] as String,
      agentName: json['agentName'] as String,
      conversationId: json['conversationId'] as String?,
      userPrompt: json['userPrompt'] as String,
      status: status,
      startedAt: DateTime.parse(json['startedAt'] as String),
      completedAt: json['completedAt'] != null
          ? DateTime.parse(json['completedAt'] as String)
          : null,
      inputTokens: json['inputTokens'] as int?,
      outputTokens: json['outputTokens'] as int?,
      estimatedCost: json['estimatedCost'] != null
          ? (json['estimatedCost'] as num).toDouble()
          : null,
      messageCount: json['messageCount'] as int,
      errorMessage: json['errorMessage'] as String?,
    );
  }

  Map<String, Object?> toJson() => {
        'id': id,
        'agentId': agentId,
        'agentName': agentName,
        'conversationId': conversationId,
        'userPrompt': userPrompt,
        'status': status.value,
        'startedAt': startedAt.toIso8601String(),
        'completedAt': completedAt?.toIso8601String(),
        'inputTokens': inputTokens,
        'outputTokens': outputTokens,
        'estimatedCost': estimatedCost,
        'messageCount': messageCount,
        'errorMessage': errorMessage,
      };
}

// =============================================================================
// Extension Methods
// =============================================================================

/// Agent execution management endpoints
extension ExecutionEndpoint on CqrsApiClient {
  /// Start an agent execution
  ///
  /// Returns the ID of the newly created execution.
  ///
  /// Example:
  /// ```dart
  /// final result = await client.startAgentExecution(
  ///   StartAgentExecutionCommand(
  ///     agentId: 'agent-uuid',
  ///     conversationId: 'conversation-uuid', // Optional
  ///     userPrompt: 'Generate a function to calculate factorial',
  ///   ),
  /// );
  ///
  /// result.when(
  ///   success: (started) => print('Execution ID: ${started.id}'),
  ///   error: (error) => print('Error: ${error.message}'),
  /// );
  /// ```
  Future<Result<StartExecutionResult>> startAgentExecution(
    StartAgentExecutionCommand command,
  ) async {
    try {
      final Uri url =
          Uri.parse('${config.baseUrl}/api/command/startAgentExecution');
      final String body = jsonEncode(command.toJson());

      final http.Response response = await http
          .post(
            url,
            headers: config.defaultHeaders,
            body: body,
          )
          .timeout(config.timeout);

      if (response.statusCode >= 200 && response.statusCode < 300) {
        try {
          final Object? json = jsonDecode(response.body);
          final StartExecutionResult result =
              StartExecutionResult.fromJson(json as Map<String, Object?>);
          return ApiSuccess<StartExecutionResult>(result);
        } catch (e) {
          return ApiError<StartExecutionResult>(
            ApiErrorInfo(
              message: 'Failed to parse start execution response',
              statusCode: response.statusCode,
              type: ApiErrorType.serialization,
              details: e.toString(),
            ),
          );
        }
      } else {
        return ApiError<StartExecutionResult>(
          ApiErrorInfo(
            message: 'Start execution failed',
            statusCode: response.statusCode,
            type: ApiErrorType.http,
          ),
        );
      }
    } on TimeoutException catch (e) {
      return ApiError<StartExecutionResult>(
        ApiErrorInfo(
          message: 'Request timeout: ${e.message ?? "Operation took too long"}',
          type: ApiErrorType.timeout,
        ),
      );
    } on SocketException catch (e) {
      return ApiError<StartExecutionResult>(
        ApiErrorInfo(
          message: 'Network error: ${e.message}',
          type: ApiErrorType.network,
          details: e.osError?.message,
        ),
      );
    } catch (e) {
      return ApiError<StartExecutionResult>(
        ApiErrorInfo(
          message: 'Unexpected error: $e',
          type: ApiErrorType.unknown,
        ),
      );
    }
  }

  /// Complete an agent execution with results
  ///
  /// Example:
  /// ```dart
  /// final result = await client.completeAgentExecution(
  ///   CompleteAgentExecutionCommand(
  ///     executionId: 'execution-uuid',
  ///     status: ExecutionStatus.completed,
  ///     response: 'Here is the factorial function...',
  ///     inputTokens: 100,
  ///     outputTokens: 200,
  ///     estimatedCost: 0.003,
  ///   ),
  /// );
  /// ```
  Future<Result<void>> completeAgentExecution(
    CompleteAgentExecutionCommand command,
  ) async {
    return executeCommand(
      endpoint: 'completeAgentExecution',
      command: command,
    );
  }

  /// Get a single execution by ID with full details
  ///
  /// Example:
  /// ```dart
  /// final result = await client.getAgentExecution('execution-uuid');
  ///
  /// result.when(
  ///   success: (execution) {
  ///     print('Status: ${execution.status.value}');
  ///     print('Prompt: ${execution.userPrompt}');
  ///     print('Response: ${execution.response}');
  ///     print('Tokens: ${execution.inputTokens} in, ${execution.outputTokens} out');
  ///   },
  ///   error: (error) => print('Error: ${error.message}'),
  /// );
  /// ```
  Future<Result<AgentExecutionDto>> getAgentExecution(String id) async {
    return executeQuery<AgentExecutionDto>(
      endpoint: 'getAgentExecution',
      query: GetAgentExecutionQuery(id: id),
      fromJson: (Object? json) =>
          AgentExecutionDto.fromJson(json as Map<String, Object?>),
    );
  }
}
