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

import 'package:wz_location_plugin/models/wayz_common_models.dart';

import 'wz_http_exception.dart';

/// Minimal JSON HTTP helpers on [dart:io] [HttpClient].
///
/// When [client] is omitted, each request uses a short-lived client that is
/// closed afterward. When [client] is injected, it is never closed here.
class WzIoHttpClient {
  WzIoHttpClient({
    HttpClient? client,
    this.timeout = const Duration(seconds: 30),
  }) : _injected = client;

  final HttpClient? _injected;
  final Duration timeout;

  Future<Object?> getJson(Uri uri) {
    return _withClient((c) => _sendJson(c, uri, 'GET', null));
  }

  Future<Object?> postJson(Uri uri, Map<String, dynamic> body) {
    return _withClient((c) => _sendJson(c, uri, 'POST', body));
  }

  Future<T> _withClient<T>(Future<T> Function(HttpClient c) fn) async {
    final ownsClient = _injected == null;
    final client = _injected ?? HttpClient();
    try {
      return await fn(client).timeout(timeout);
    } finally {
      if (ownsClient) {
        client.close(force: true);
      }
    }
  }

  Future<Object?> _sendJson(
    HttpClient client,
    Uri uri,
    String method,
    Map<String, dynamic>? body,
  ) async {
    HttpClientRequest req;
    switch (method) {
      case 'GET':
        req = await client.getUrl(uri);
        break;
      case 'POST':
        req = await client.postUrl(uri);
        break;
      default:
        throw ArgumentError('Unsupported method: $method');
    }
    req.headers.set(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8');

    if (body != null && method == 'POST') {
      req.write(jsonEncode(body));
    }

    final res = await req.close();
    final text = await utf8.decoder.bind(res).join();
    final code = res.statusCode;

    Object? decoded;
    if (text.isNotEmpty) {
      try {
        decoded = jsonDecode(text);
      } catch (_) {
        throw WzHttpException(
          statusCode: code,
          message: 'Invalid JSON response',
          rawBody: text,
        );
      }
    }

    if (code < 200 || code >= 300) {
      WzApiError? apiErr;
      if (decoded is Map<String, dynamic>) {
        final msg = WzJson.toStringOrNull(decoded['message']);
        if (msg != null && msg.isNotEmpty) {
          apiErr = WzApiError.fromJson(decoded);
        }
      }
      throw WzHttpException(
        statusCode: code,
        message: apiErr?.message ?? 'HTTP $code',
        apiError: apiErr,
        rawBody: text.isEmpty ? null : text,
      );
    }

    return decoded;
  }
}
