import 'dart:async';
import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:uuid/uuid.dart';

import 'models/websocket_event.dart';
import 'models/progress_event.dart';
import 'models/execution_event.dart';
import 'exceptions/comfyui_api_exception.dart';
import 'types/callback_types.dart';
import 'models/history_response.dart';
import 'models/checkpoint.dart';
import 'models/vae.dart';
import 'models/lora.dart';
import 'websocket_manager.dart';

/// A Dart SDK for interacting with the ComfyUI API
class ComfyUiApi {
  final String host;
  final String clientId;
  final http.Client _httpClient;
  final WebSocketManager _webSocketManager;

  /// Creates a new ComfyUI API client
  ComfyUiApi({
    required this.host,
    required this.clientId,
    http.Client? httpClient,
  })  : _httpClient = httpClient ?? http.Client(),
        _webSocketManager = WebSocketManager(host: host, clientId: clientId);

  /// Expose WebSocketManager streams and methods
  Stream<WebSocketEvent> get events => _webSocketManager.events;
  Stream<Map<String, dynamic>> get progressUpdates =>
      _webSocketManager.progressUpdates;
  Stream<ProgressEvent> get progressEvents => _webSocketManager.progressEvents;
  Stream<ExecutionEvent> get executionEvents =>
      _webSocketManager.executionEvents;

  void onEventType(WebSocketEventType type, WebSocketEventCallback callback) {
    _webSocketManager.onEventType(type, callback);
  }

  void onProgressChanged(ProgressEventCallback callback) {
    _webSocketManager.onProgressChanged(callback);
  }

  void onPromptStart(PromptEventCallback callback) {
    _webSocketManager.onPromptStart(callback);
  }

  void onPromptFinished(PromptEventCallback callback) {
    _webSocketManager.onPromptFinished(callback);
  }

  Future<void> connectWebSocket() => _webSocketManager.connect();

  void dispose() {
    _webSocketManager.dispose();
    _httpClient.close();
  }

  /// Gets the current queue status
  Future<Map<String, dynamic>> getQueue() async {
    final response = await _httpClient.get(Uri.parse('$host/queue'));
    _validateResponse(response);
    return jsonDecode(response.body);
  }

  /// Gets the history of the queue
  Future<HistoryResponse> getHistory({int maxItems = 64}) async {
    final response = await _httpClient
        .get(Uri.parse('$host/api/history?max_items=$maxItems'));
    _validateResponse(response);
    return HistoryResponse.fromJson(jsonDecode(response.body));
  }

  /// Gets image data by filename
  Future<List<int>> getImage(String filename) async {
    final response =
        await _httpClient.get(Uri.parse('$host/api/view?filename=$filename'));
    _validateResponse(response);
    return response.bodyBytes;
  }

  /// Gets a list of all available models
  Future<Map<String, dynamic>> getModels() async {
    final response =
        await _httpClient.get(Uri.parse('$host/api/experiment/models'));
    _validateResponse(response);
    return jsonDecode(response.body);
  }

  /// Gets a list of checkpoints
  Future<List<Checkpoint>> getCheckpoints() async {
    final response = await _httpClient
        .get(Uri.parse('$host/api/experiment/models/checkpoints'));
    _validateResponse(response);
    final List<dynamic> jsonData = jsonDecode(response.body);
    return jsonData.map((item) => Checkpoint.fromJson(item)).toList();
  }

  /// Gets details for a specific checkpoint
  Future<Map<String, dynamic>> getCheckpointDetails(
      String pathAndFileName) async {
    final response = await _httpClient.get(Uri.parse(
        '$host/api/view_metadata/checkpoints?filename=$pathAndFileName'));
    _validateResponse(response);
    return jsonDecode(response.body);
  }

  /// Gets a list of LoRAs
  Future<List<Lora>> getLoras() async {
    final response =
        await _httpClient.get(Uri.parse('$host/api/experiment/models/loras'));
    _validateResponse(response);
    final List<dynamic> jsonData = jsonDecode(response.body);
    return jsonData.map((item) => Lora.fromJson(item)).toList();
  }

  /// Gets details for a specific LoRA
  Future<Map<String, dynamic>> getLoraDetails(String pathAndFileName) async {
    final response = await _httpClient.get(
        Uri.parse('$host/api/view_metadata/loras?filename=$pathAndFileName'));
    _validateResponse(response);
    return jsonDecode(response.body);
  }

  /// Gets a list of VAEs
  Future<List<Vae>> getVaes() async {
    final response =
        await _httpClient.get(Uri.parse('$host/api/experiment/models/vae'));
    _validateResponse(response);
    final List<dynamic> jsonData = jsonDecode(response.body);
    return jsonData.map((item) => Vae.fromJson(item)).toList();
  }

  /// Gets details for a specific VAE
  Future<Map<String, dynamic>> getVaeDetails(String pathAndFileName) async {
    final response = await _httpClient.get(
        Uri.parse('$host/api/view_metadata/vae?filename=$pathAndFileName'));
    _validateResponse(response);
    return jsonDecode(response.body);
  }

  /// Gets a list of upscale models
  Future<List<dynamic>> getUpscaleModels() async {
    final response = await _httpClient
        .get(Uri.parse('$host/api/experiment/models/upscale_models'));
    _validateResponse(response);
    return jsonDecode(response.body);
  }

  /// Gets details for a specific upscale model
  Future<Map<String, dynamic>> getUpscaleModelDetails(
      String pathAndFileName) async {
    final response = await _httpClient.get(Uri.parse(
        '$host/api/view_metadata/upscale_models?filename=$pathAndFileName'));
    _validateResponse(response);
    return jsonDecode(response.body);
  }

  /// Gets a list of embeddings
  Future<List<dynamic>> getEmbeddings() async {
    final response = await _httpClient
        .get(Uri.parse('$host/api/experiment/models/embeddings'));
    _validateResponse(response);
    return jsonDecode(response.body);
  }

  /// Gets details for a specific embedding
  Future<Map<String, dynamic>> getEmbeddingDetails(
      String pathAndFileName) async {
    final response = await _httpClient.get(Uri.parse(
        '$host/api/view_metadata/embeddings?filename=$pathAndFileName'));
    _validateResponse(response);
    return jsonDecode(response.body);
  }

  /// Gets information about all available objects (nodes)
  Future<Map<String, dynamic>> getObjectInfo() async {
    final response = await _httpClient.get(Uri.parse('$host/api/object_info'));
    _validateResponse(response);
    return jsonDecode(response.body);
  }

  /// Submits a prompt (workflow) to generate an image
  Future<Map<String, dynamic>> submitPrompt(Map<String, dynamic> prompt) async {
    final response = await _httpClient.post(
      Uri.parse('$host/api/prompt'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(prompt),
    );
    _validateResponse(response);
    final responseData = jsonDecode(response.body);

    // Trigger onPromptStart event if prompt_id exists
    if (responseData.containsKey('prompt_id')) {
      final promptId = responseData['prompt_id'];
      _webSocketManager.triggerOnPromptStart(promptId);
    }

    return responseData;
  }

  /// Validates HTTP response and throws an exception if needed
  void _validateResponse(http.Response response) {
    if (response.statusCode < 200 || response.statusCode >= 300) {
      throw ComfyUiApiException(
          statusCode: response.statusCode,
          message: 'API request failed: ${response.body}');
    }
  }
}
