import 'dart:io';

import 'package:wz_location_plugin/models/wayz_geocode_response.dart';
import 'package:wz_location_plugin/models/wayz_reverse_geocode_response.dart';

import 'src/io/wz_http_exception.dart';
import 'src/io/wz_io_http.dart';

/// Pure-Dart WAYZ scenario / hub HTTP APIs (`dart:io` only; not for Flutter Web).
///
/// Import via `package:wz_location_plugin/wz_location_plugin_scenario.dart` so apps
/// that target Web can avoid pulling `dart:io`.

class WzScenarioHttpApi {
  WzScenarioHttpApi({
    required this.accessKey,
    this.host = 'api.newayz.com',
    HttpClient? httpClient,
    Duration timeout = const Duration(seconds: 30),
  }) : _http = WzIoHttpClient(client: httpClient, timeout: timeout);

  final String accessKey;
  final String host;
  final WzIoHttpClient _http;

  /// 正地理编码
  Future<WzGeocodeResponse> geocode({
    required String address,
    String? city,
  }) async {
    final q = <String, String>{
      'access_key': accessKey,
      'address': address,
      if (city != null && city.isNotEmpty) 'city': city,
    };
    final uri = Uri.https(host, '/location/scenario/v1/geocode', q);
    final decoded = await _http.getJson(uri);
    final map = _asSingleResultMap(decoded);
    return WzGeocodeResponse.fromJson(map);
  }

  /// 逆地理编码 — POST `/location/hub/v1/track_points`
  Future<WzReverseGeocodeResponse> reverseGeocode({
    required String asset,
    required double longitude,
    required double latitude,
    String? responseSprf,
    int? positionTimestampMs,
    String? spatialReference,
    String? source,
    double? accuracy,
    int? bodyTimestampMs,
    String? requestId,
  }) async {
    final query = <String, String>{
      'access_key': accessKey,
      if (responseSprf != null && responseSprf.isNotEmpty) 'response_sprf': responseSprf,
    };
    final uri = Uri.https(host, '/location/hub/v1/track_points', query);

    final position = <String, dynamic>{
      'point': <String, dynamic>{
        'longitude': longitude,
        'latitude': latitude,
      },
    };
    if (positionTimestampMs != null) {
      position['timestamp'] = positionTimestampMs;
    }

    final location = <String, dynamic>{
      'position': position,
    };
    if (spatialReference != null && spatialReference.isNotEmpty) {
      location['spatialReference'] = spatialReference;
    }
    if (source != null && source.isNotEmpty) {
      location['source'] = source;
    }
    if (accuracy != null) {
      location['accuracy'] = accuracy;
    }

    final body = <String, dynamic>{
      'access_key': accessKey,
      'asset': asset,
      'location': location,
    };
    if (bodyTimestampMs != null) {
      body['timestamp'] = bodyTimestampMs;
    }
    if (requestId != null && requestId.isNotEmpty) {
      body['id'] = requestId;
    }

    final decoded = await _http.postJson(uri, body);
    if (decoded is! Map) {
      throw WzHttpException(message: 'reverseGeocode: expected JSON object');
    }
    return WzReverseGeocodeResponse.fromJson(decoded.cast<String, dynamic>());
  }

  /// POI 关键字搜索 — GET `/location/scenario/v1/placesearch`
  ///
  /// [city] 与 [cityCode] 需至少填写一项（与官方文档一致）。
  Future<List<WzGeocodeResponse>> searchPoiByKeyword({
    required String name,
    String? city,
    String? cityCode,
    int pageIndex = 1,
    int pageSize = 20,
  }) async {
    final c = city;
    final cc = cityCode;
    if ((c == null || c.isEmpty) && (cc == null || cc.isEmpty)) {
      throw ArgumentError('searchPoiByKeyword: city 与 cityCode 需至少填写一个');
    }
    final q = <String, String>{
      'access_key': accessKey,
      'name': name,
      'page_index': '$pageIndex',
      'page_size': '$pageSize',
    };
    if (c != null && c.isNotEmpty) {
      q['city'] = c;
    }
    if (cc != null && cc.isNotEmpty) {
      q['city_code'] = cc;
    }
    final uri = Uri.https(host, '/location/scenario/v1/placesearch', q);
    final decoded = await _http.getJson(uri);
    return parseGeocodeList(decoded);
  }

  /// POI 周边搜索 — GET `/location/scenario/v1/nearbysearch`
  ///
  /// [location]：`经度,纬度`，默认 gcj02。[keywords]、[category]、[orderby] 可选；按距离排序时可传 [orderby] 为 `distance_score`。
  Future<List<WzGeocodeResponse>> searchPoiNearby({
    required String location,
    required int radiusMeters,
    String? keywords,
    String? category,
    String? orderby,
    int pageIndex = 1,
    int pageSize = 20,
  }) async {
    final q = <String, String>{
      'access_key': accessKey,
      'location': location,
      'radius': '$radiusMeters',
      'page_index': '$pageIndex',
      'page_size': '$pageSize',
    };
    if (keywords != null && keywords.isNotEmpty) {
      q['keywords'] = keywords;
    }
    if (category != null && category.isNotEmpty) {
      q['category'] = category;
    }
    if (orderby != null && orderby.isNotEmpty) {
      q['orderby'] = orderby;
    }
    final uri = Uri.https(host, '/location/scenario/v1/nearbysearch', q);
    final decoded = await _http.getJson(uri);
    return parseGeocodeList(decoded);
  }

  /// Parses POI / geocode list payloads into [WzGeocodeResponse].
  static List<WzGeocodeResponse> parseGeocodeList(Object? decoded) {
    final raw = _extractList(decoded);
    return raw
        .map((e) => WzGeocodeResponse.fromJson((e as Map).cast<String, dynamic>()))
        .toList();
  }

  static Map<String, dynamic> _asSingleResultMap(Object? decoded) {
    if (decoded is Map) {
      return decoded.cast<String, dynamic>();
    }
    if (decoded is List && decoded.isNotEmpty && decoded.first is Map) {
      return (decoded.first as Map).cast<String, dynamic>();
    }
    throw WzHttpException(message: 'geocode: expected JSON object or non-empty array');
  }

  static List<dynamic> _extractList(Object? decoded) {
    if (decoded is List) {
      return decoded;
    }
    if (decoded is Map<String, dynamic>) {
      for (final key in const ['data', 'result', 'results', 'list', 'items']) {
        final v = decoded[key];
        if (v is List) {
          return v;
        }
      }
    }
    if (decoded is Map) {
      final m = decoded.cast<String, dynamic>();
      for (final key in const ['data', 'result', 'results', 'list', 'items']) {
        final v = m[key];
        if (v is List) {
          return v;
        }
      }
    }
    throw WzHttpException(message: 'POI search: expected JSON array or wrapped list');
  }
}
