/// CQRS API Client for communicating with the backend
library;

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

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

import 'types.dart';

// =============================================================================
// API Client Configuration
// =============================================================================

/// Configuration for the API client
class ApiClientConfig {
  final String baseUrl;
  final Duration timeout;
  final Map<String, String> defaultHeaders;

  const ApiClientConfig({
    required this.baseUrl,
    this.timeout = const Duration(seconds: 30),
    this.defaultHeaders = const {},
  });

  /// Default configuration for local development
  static const ApiClientConfig development = ApiClientConfig(
    baseUrl: 'http://localhost:5246',
    timeout: Duration(seconds: 30),
    defaultHeaders: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
  );
}

// =============================================================================
// CQRS API Client
// =============================================================================

/// HTTP client for CQRS-based API
///
/// Provides methods for executing:
/// - Queries (read operations)
/// - Commands (write operations)
/// - Paginated queries (list operations with filtering/sorting)
class CqrsApiClient {
  final ApiClientConfig config;
  final http.Client _httpClient;

  CqrsApiClient({
    required this.config,
    http.Client? httpClient,
  }) : _httpClient = httpClient ?? http.Client();

  /// Execute a query that returns a single value
  ///
  /// Example:
  /// ```dart
  /// final result = await client.executeQuery<bool>(
  ///   'health',
  ///   HealthQuery(),
  ///   (json) => json as bool,
  /// );
  /// ```
  Future<Result<TResult>> executeQuery<TResult>({
    required String endpoint,
    required Serializable query,
    required TResult Function(Object? json) fromJson,
  }) async {
    try {
      final url = Uri.parse('${config.baseUrl}/api/query/$endpoint');
      final body = _serializeQuery(query);

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

      return _handleResponse<TResult>(response, fromJson);
    } on TimeoutException catch (e) {
      return ApiError<TResult>(
        ApiErrorInfo(
          message: 'Request timeout: ${e.message ?? "Operation took too long"}',
          type: ApiErrorType.timeout,
        ),
      );
    } on SocketException catch (e) {
      return ApiError<TResult>(
        ApiErrorInfo(
          message: 'Network error: ${e.message}',
          type: ApiErrorType.network,
          details: e.osError?.message,
        ),
      );
    } on http.ClientException catch (e) {
      return ApiError<TResult>(
        ApiErrorInfo(
          message: 'HTTP client error: ${e.message}',
          type: ApiErrorType.http,
        ),
      );
    } on FormatException catch (e) {
      return ApiError<TResult>(
        ApiErrorInfo(
          message: 'JSON parsing error: ${e.message}',
          type: ApiErrorType.serialization,
          details: e.source.toString(),
        ),
      );
    } catch (e) {
      return ApiError<TResult>(
        ApiErrorInfo(
          message: 'Unexpected error: $e',
          type: ApiErrorType.unknown,
        ),
      );
    }
  }

  /// Execute a paginated query that returns a list of items
  ///
  /// Example:
  /// ```dart
  /// final result = await client.executePaginatedQuery<VehicleItem>(
  ///   'vehicles',
  ///   VehiclesQuery(),
  ///   (json) => VehicleItem.fromJson(json as Map<String, Object?>),
  ///   page: 1,
  ///   pageSize: 20,
  ///   filters: [FilterCriteria(field: 'status', operator: FilterOperator.equals, value: 'active')],
  ///   sorting: [SortCriteria(field: 'createdAt', direction: SortDirection.descending)],
  /// );
  /// ```
  Future<Result<PaginatedResponse<TItem>>> executePaginatedQuery<TItem>({
    required String endpoint,
    required Serializable query,
    required TItem Function(Map<String, Object?> json) itemFromJson,
    int page = 1,
    int pageSize = 20,
    List<FilterCriteria>? filters,
    List<SortCriteria>? sorting,
  }) async {
    try {
      final url = Uri.parse('${config.baseUrl}/api/query/$endpoint');

      // Build request body with query + pagination/filtering/sorting
      final queryMap = _queryToMap(query);
      final requestBody = <String, Object?>{
        ...queryMap,
        'page': page,
        'pageSize': pageSize,
        if (filters != null && filters.isNotEmpty)
          'filters': filters.map((f) => f.toJson()).toList(),
        if (sorting != null && sorting.isNotEmpty)
          'sorts': sorting.map((s) => s.toJson()).toList(),
      };

      final body = jsonEncode(requestBody);

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

      return _handlePaginatedResponse<TItem>(response, itemFromJson);
    } on TimeoutException catch (e) {
      return ApiError<PaginatedResponse<TItem>>(
        ApiErrorInfo(
          message: 'Request timeout: ${e.message ?? "Operation took too long"}',
          type: ApiErrorType.timeout,
        ),
      );
    } on SocketException catch (e) {
      return ApiError<PaginatedResponse<TItem>>(
        ApiErrorInfo(
          message: 'Network error: ${e.message}',
          type: ApiErrorType.network,
          details: e.osError?.message,
        ),
      );
    } on http.ClientException catch (e) {
      return ApiError<PaginatedResponse<TItem>>(
        ApiErrorInfo(
          message: 'HTTP client error: ${e.message}',
          type: ApiErrorType.http,
        ),
      );
    } on FormatException catch (e) {
      return ApiError<PaginatedResponse<TItem>>(
        ApiErrorInfo(
          message: 'JSON parsing error: ${e.message}',
          type: ApiErrorType.serialization,
          details: e.source.toString(),
        ),
      );
    } catch (e) {
      return ApiError<PaginatedResponse<TItem>>(
        ApiErrorInfo(
          message: 'Unexpected error: $e',
          type: ApiErrorType.unknown,
        ),
      );
    }
  }

  /// Execute a command (write operation)
  ///
  /// Example:
  /// ```dart
  /// final result = await client.executeCommand(
  ///   'createVehicle',
  ///   CreateVehicleCommand(name: 'Tesla Model 3'),
  /// );
  /// ```
  Future<Result<void>> executeCommand({
    required String endpoint,
    required Serializable command,
  }) async {
    try {
      final url = Uri.parse('${config.baseUrl}/api/command/$endpoint');
      final body = _serializeQuery(command);

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

      return _handleCommandResponse(response);
    } on TimeoutException catch (e) {
      return ApiError<void>(
        ApiErrorInfo(
          message: 'Request timeout: ${e.message ?? "Operation took too long"}',
          type: ApiErrorType.timeout,
        ),
      );
    } on SocketException catch (e) {
      return ApiError<void>(
        ApiErrorInfo(
          message: 'Network error: ${e.message}',
          type: ApiErrorType.network,
          details: e.osError?.message,
        ),
      );
    } on http.ClientException catch (e) {
      return ApiError<void>(
        ApiErrorInfo(
          message: 'HTTP client error: ${e.message}',
          type: ApiErrorType.http,
        ),
      );
    } on FormatException catch (e) {
      return ApiError<void>(
        ApiErrorInfo(
          message: 'JSON parsing error: ${e.message}',
          type: ApiErrorType.serialization,
          details: e.source.toString(),
        ),
      );
    } catch (e) {
      return ApiError<void>(
        ApiErrorInfo(
          message: 'Unexpected error: $e',
          type: ApiErrorType.unknown,
        ),
      );
    }
  }

  // ---------------------------------------------------------------------------
  // Private Helper Methods
  // ---------------------------------------------------------------------------

  /// Serialize query/command object to JSON string
  String _serializeQuery(Serializable query) {
    final jsonMap = query.toJson();
    return jsonEncode(jsonMap);
  }

  /// Convert query object to Map for pagination requests
  Map<String, Object?> _queryToMap(Serializable query) {
    return query.toJson();
  }

  /// Handle HTTP response for single value queries
  Result<TResult> _handleResponse<TResult>(
    http.Response response,
    TResult Function(Object? json) fromJson,
  ) {
    if (response.statusCode >= 200 && response.statusCode < 300) {
      try {
        final json = jsonDecode(response.body);
        final result = fromJson(json);
        return ApiSuccess<TResult>(result);
      } catch (e) {
        return ApiError<TResult>(
          ApiErrorInfo(
            message: 'Failed to parse response',
            statusCode: response.statusCode,
            type: ApiErrorType.serialization,
            details: e.toString(),
          ),
        );
      }
    } else {
      return ApiError<TResult>(
        _parseErrorResponse(response),
      );
    }
  }

  /// Handle HTTP response for paginated queries
  Result<PaginatedResponse<TItem>> _handlePaginatedResponse<TItem>(
    http.Response response,
    TItem Function(Map<String, Object?>) itemFromJson,
  ) {
    if (response.statusCode >= 200 && response.statusCode < 300) {
      try {
        final json = jsonDecode(response.body) as Map<String, Object?>;
        final paginatedResponse = PaginatedResponse<TItem>.fromJson(
          json,
          itemFromJson,
        );
        return ApiSuccess<PaginatedResponse<TItem>>(paginatedResponse);
      } catch (e) {
        return ApiError<PaginatedResponse<TItem>>(
          ApiErrorInfo(
            message: 'Failed to parse paginated response',
            statusCode: response.statusCode,
            type: ApiErrorType.serialization,
            details: e.toString(),
          ),
        );
      }
    } else {
      return ApiError<PaginatedResponse<TItem>>(
        _parseErrorResponse(response),
      );
    }
  }

  /// Handle HTTP response for commands
  Result<void> _handleCommandResponse(http.Response response) {
    if (response.statusCode >= 200 && response.statusCode < 300) {
      return const ApiSuccess<void>(null);
    } else {
      return ApiError<void>(_parseErrorResponse(response));
    }
  }

  /// Parse error information from HTTP response
  ApiErrorInfo _parseErrorResponse(http.Response response) {
    String message = 'Request failed with status ${response.statusCode}';
    String? details;

    try {
      final json = jsonDecode(response.body) as Map<String, Object?>;
      message = json['message'] as String? ?? message;
      details = json['details'] as String? ?? json['error'] as String?;
    } catch (_) {
      // If JSON parsing fails, use response body as details
      details = response.body;
    }

    return ApiErrorInfo(
      message: message,
      statusCode: response.statusCode,
      type: _determineErrorType(response.statusCode),
      details: details,
    );
  }

  /// Determine error type based on HTTP status code
  ApiErrorType _determineErrorType(int statusCode) {
    if (statusCode >= 400 && statusCode < 500) {
      if (statusCode == 422) {
        return ApiErrorType.validation;
      }
      return ApiErrorType.http;
    } else if (statusCode >= 500) {
      return ApiErrorType.http;
    }
    return ApiErrorType.unknown;
  }

  /// Dispose of resources
  void dispose() {
    _httpClient.close();
  }
}
