import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:http_interceptor/http_interceptor.dart';
import 'types.dart';
import 'openapi_config.dart';
import '../utils/logging_interceptor.dart';
import '../utils/http_client_factory.dart';
import '../services/auth_service.dart';

class CqrsApiClient {
  final ApiClientConfig config;
  final AuthService? authService;
  late final http.Client _httpClient;

  CqrsApiClient({
    required this.config,
    this.authService,
    http.Client? httpClient,
  }) {
    _httpClient = httpClient ?? InterceptedClient.build(
      interceptors: [LoggingInterceptor()],
      client: HttpClientFactory.createClient(
        allowSelfSigned: config.allowSelfSignedCertificate,
      ),
    );
  }

  String get baseUrl => config.baseUrl;

  Future<Result<T>> executeQuery<T>({
    required String endpoint,
    required Serializable query,
    required T Function(Map<String, dynamic>) fromJson,
    bool isRetry = false,
  }) async {
    try {
      final url = Uri.parse('$baseUrl/api/query/$endpoint');
      final headers = await _buildHeaders();

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

      if (response.statusCode == 401 && !isRetry && authService != null) {
        final refreshResult = await authService!.refreshAccessToken();
        return refreshResult.when(
          success: (token) => executeQuery(
            endpoint: endpoint,
            query: query,
            fromJson: fromJson,
            isRetry: true,
          ),
          onError: (error) => _handleResponse<T>(response, fromJson),
          cancelled: () => _handleResponse<T>(response, fromJson),
        );
      }

      return _handleResponse<T>(response, fromJson);
    } on TimeoutException {
      return Result.error(ApiError.timeout());
    } catch (e, stackTrace) {
      return Result.error(
        ApiError.unknown(
          'Failed to execute query: ${e.toString()}',
          exception: Exception(stackTrace.toString()),
        ),
      );
    }
  }

  Future<Result<PaginatedResult<T>>> executePaginatedQuery<T>({
    required String endpoint,
    required Serializable query,
    required T Function(Map<String, dynamic>) itemFromJson,
    required int page,
    required int pageSize,
    List<FilterCriteria>? filters,
    bool isRetry = false,
  }) async {
    try {
      final url = Uri.parse(
        '$baseUrl/api/query/$endpoint?page=$page&pageSize=$pageSize',
      );
      final headers = await _buildHeaders();

      final queryData = {
        ...query.toJson(),
        if (filters != null && filters.isNotEmpty)
          'filters': filters.map((f) => f.toJson()).toList(),
      };

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

      if (response.statusCode == 401 && !isRetry && authService != null) {
        final refreshResult = await authService!.refreshAccessToken();
        return refreshResult.when(
          success: (token) => executePaginatedQuery(
            endpoint: endpoint,
            query: query,
            itemFromJson: itemFromJson,
            page: page,
            pageSize: pageSize,
            filters: filters,
            isRetry: true,
          ),
          onError: (error) => _handlePaginatedResponse<T>(response, itemFromJson, page, pageSize),
          cancelled: () => _handlePaginatedResponse<T>(response, itemFromJson, page, pageSize),
        );
      }

      return _handlePaginatedResponse<T>(response, itemFromJson, page, pageSize);
    } on TimeoutException {
      return Result.error(ApiError.timeout());
    } catch (e, stackTrace) {
      return Result.error(
        ApiError.unknown(
          'Failed to execute paginated query: ${e.toString()}',
          exception: Exception(stackTrace.toString()),
        ),
      );
    }
  }

  Future<Result<void>> executeCommand({
    required String endpoint,
    required Serializable command,
    bool isRetry = false,
  }) async {
    try {
      final url = Uri.parse('$baseUrl/api/command/$endpoint');
      final headers = await _buildHeaders();

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

      if (response.statusCode == 401 && !isRetry && authService != null) {
        final refreshResult = await authService!.refreshAccessToken();
        return refreshResult.when(
          success: (token) => executeCommand(
            endpoint: endpoint,
            command: command,
            isRetry: true,
          ),
          onError: (error) {
            if (response.statusCode >= 200 && response.statusCode < 300) {
              return Result.success(null);
            } else {
              return _handleErrorResponse(response);
            }
          },
          cancelled: () {
            if (response.statusCode >= 200 && response.statusCode < 300) {
              return Result.success(null);
            } else {
              return _handleErrorResponse(response);
            }
          },
        );
      }

      if (response.statusCode >= 200 && response.statusCode < 300) {
        return Result.success(null);
      } else {
        return _handleErrorResponse(response);
      }
    } on TimeoutException {
      return Result.error(ApiError.timeout());
    } catch (e, stackTrace) {
      return Result.error(
        ApiError.unknown(
          'Failed to execute command: ${e.toString()}',
          exception: Exception(stackTrace.toString()),
        ),
      );
    }
  }

  Future<Result<T>> executeCommandWithResult<T>({
    required String endpoint,
    required Serializable command,
    required T Function(Map<String, dynamic>) fromJson,
    bool isRetry = false,
  }) async {
    try {
      final url = Uri.parse('$baseUrl/api/command/$endpoint');
      final headers = await _buildHeaders();

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

      if (response.statusCode == 401 && !isRetry && authService != null) {
        final refreshResult = await authService!.refreshAccessToken();
        return refreshResult.when(
          success: (token) => executeCommandWithResult(
            endpoint: endpoint,
            command: command,
            fromJson: fromJson,
            isRetry: true,
          ),
          onError: (error) => _handleResponse<T>(response, fromJson),
          cancelled: () => _handleResponse<T>(response, fromJson),
        );
      }

      return _handleResponse<T>(response, fromJson);
    } on TimeoutException {
      return Result.error(ApiError.timeout());
    } catch (e, stackTrace) {
      return Result.error(
        ApiError.unknown(
          'Failed to execute command with result: ${e.toString()}',
          exception: Exception(stackTrace.toString()),
        ),
      );
    }
  }

  Future<Result<String>> uploadFile({
    required String endpoint,
    required String filePath,
    required String fieldName,
    Map<String, String>? additionalFields,
    bool isRetry = false,
  }) async {
    try {
      final url = Uri.parse('$baseUrl/api/command/$endpoint');
      final headers = await _buildHeaders();
      final request = http.MultipartRequest('POST', url)
        ..headers.addAll(headers)
        ..files.add(await http.MultipartFile.fromPath(fieldName, filePath));

      if (additionalFields != null) {
        request.fields.addAll(additionalFields);
      }

      final response = await request.send().timeout(config.timeout);
      final responseBody = await response.stream.bytesToString();

      if (response.statusCode == 401 && !isRetry && authService != null) {
        final refreshResult = await authService!.refreshAccessToken();
        return refreshResult.when(
          success: (token) => uploadFile(
            endpoint: endpoint,
            filePath: filePath,
            fieldName: fieldName,
            additionalFields: additionalFields,
            isRetry: true,
          ),
          onError: (error) {
            if (response.statusCode >= 200 && response.statusCode < 300) {
              return Result.success(responseBody);
            } else {
              return _parseErrorFromString(responseBody, response.statusCode);
            }
          },
          cancelled: () {
            if (response.statusCode >= 200 && response.statusCode < 300) {
              return Result.success(responseBody);
            } else {
              return _parseErrorFromString(responseBody, response.statusCode);
            }
          },
        );
      }

      if (response.statusCode >= 200 && response.statusCode < 300) {
        return Result.success(responseBody);
      } else {
        return _parseErrorFromString(responseBody, response.statusCode);
      }
    } on TimeoutException {
      return Result.error(ApiError.timeout());
    } catch (e, stackTrace) {
      return Result.error(
        ApiError.unknown(
          'Failed to upload file: ${e.toString()}',
          exception: Exception(stackTrace.toString()),
        ),
      );
    }
  }

  Future<Map<String, String>> _buildHeaders() async {
    final headers = <String, String>{
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      ...config.defaultHeaders,
    };

    if (authService != null) {
      // Proactively ensure token is valid and refresh if needed
      final token = await authService!.ensureValidToken();
      if (token != null) {
        headers['Authorization'] = 'Bearer $token';
      }
    }

    return headers;
  }

  Result<T> _handleResponse<T>(
    http.Response response,
    T Function(Map<String, dynamic>) fromJson,
  ) {
    try {
      if (response.statusCode >= 200 && response.statusCode < 300) {
        if (response.body.isEmpty) {
          return Result.success(null as T);
        }
        final data = jsonDecode(response.body) as Map<String, dynamic>;
        return Result.success(fromJson(data));
      } else {
        return _handleErrorResponse(response);
      }
    } catch (e) {
      return Result.error(
        ApiError.unknown('Failed to parse response: ${e.toString()}'),
      );
    }
  }

  Result<PaginatedResult<T>> _handlePaginatedResponse<T>(
    http.Response response,
    T Function(Map<String, dynamic>) itemFromJson,
    int page,
    int pageSize,
  ) {
    try {
      if (response.statusCode >= 200 && response.statusCode < 300) {
        final data = jsonDecode(response.body) as Map<String, dynamic>;
        final items = (data['items'] as List?)
            ?.map((item) => itemFromJson(item as Map<String, dynamic>))
            .toList() ?? [];
        final totalCount = data['totalCount'] as int? ?? items.length;

        return Result.success(
          PaginatedResult(
            items: items,
            page: page,
            pageSize: pageSize,
            totalCount: totalCount,
          ),
        );
      } else {
        return _handleErrorResponse(response);
      }
    } catch (e) {
      return Result.error(
        ApiError.unknown('Failed to parse paginated response: ${e.toString()}'),
      );
    }
  }

  Result<Never> _handleErrorResponse(http.Response response) {
    try {
      final data = jsonDecode(response.body) as Map<String, dynamic>;
      final message = data['message'] as String? ?? 'An error occurred';

      if (response.statusCode == 422) {
        final errors = data['errors'] as Map<String, dynamic>?;
        final details = errors?.map(
          (key, value) => MapEntry(
            key,
            (value as List?)?.map((e) => e.toString()).toList() ?? [],
          ),
        );
        return Result.error(
          ApiError.validation(message, details),
        );
      }

      return Result.error(
        ApiError.http(statusCode: response.statusCode, message: message),
      );
    } catch (e) {
      return Result.error(
        ApiError.http(
          statusCode: response.statusCode,
          message: response.reasonPhrase ?? 'Unknown error',
        ),
      );
    }
  }

  Result<Never> _parseErrorFromString(String body, int statusCode) {
    try {
      final data = jsonDecode(body) as Map<String, dynamic>;
      final message = data['message'] as String? ?? 'An error occurred';
      return Result.error(
        ApiError.http(statusCode: statusCode, message: message),
      );
    } catch (e) {
      return Result.error(
        ApiError.http(statusCode: statusCode, message: 'Unknown error'),
      );
    }
  }

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