import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/client.dart';

class QdrantService {
  static const String _baseUrl = 'http://66.116.207.225:6333';
  static const String _apiKey = 'Package@321#';

  static const String collectionName = 'doormile_clients';

  // 16-dim feature vector (city-agnostic — city stored in payload only)
  //
  //  [0]     parcelVolume       (normalised 0–1 over max 1 200)
  //  [1]     activeContracts    (normalised 0–1 over max 40)
  //  [2–5]   businessType       one-hot (ecommerce, manufacturing, wholesale, retail)
  //  [6–9]   status             one-hot (newClient, pending, completed, updated)
  //  [10]    surveySubmitted    (0 / 1)
  //  [11]    dataConsent        basicOnly=0, full=1
  //  [12]    hasPhone           (0 / 1)
  //  [13]    hasTransitRoute    both from+to non-empty (0 / 1)
  //  [14]    frequencyScore     none=0, monthly=0.25, biweekly=0.5, weekly=0.75, daily=1
  //  [15]    hasNeighbourhood   (0 / 1)
  static const int _vectorSize = 16;

  final http.Client _http;
  QdrantService({http.Client? client}) : _http = client ?? http.Client();

  Map<String, String> get _headers => {
        'Content-Type': 'application/json',
        'api-key': _apiKey,
        'Cache-Control': 'no-cache, no-store, must-revalidate',
        'Pragma': 'no-cache',
        'Expires': '0',
      };

  /// Creates the collection if it does not already exist.
  Future<void> ensureCollection() async {
    final res = await _http.get(
      Uri.parse('$_baseUrl/collections/$collectionName'),
      headers: _headers,
    );
    if (res.statusCode == 200) return;

    await _http.put(
      Uri.parse('$_baseUrl/collections/$collectionName'),
      headers: _headers,
      body: jsonEncode({
        'vectors': {'size': _vectorSize, 'distance': 'Cosine'},
      }),
    );
  }

  /// Upsert (insert or update) a single client.
  Future<void> upsertClient(Client client) async {
    await _http.put(
      Uri.parse('$_baseUrl/collections/$collectionName/points'),
      headers: _headers,
      body: jsonEncode({
        'points': [_toPoint(client)],
      }),
    );
  }

  /// Batch upsert a list of clients.
  Future<void> upsertClients(List<Client> clients) async {
    if (clients.isEmpty) return;
    await _http.put(
      Uri.parse('$_baseUrl/collections/$collectionName/points'),
      headers: _headers,
      body: jsonEncode({
        'points': clients.map(_toPoint).toList(),
      }),
    );
  }

  /// Fetch all stored clients from Qdrant (up to 500).
  Future<List<Client>> loadClients() async {
    final res = await _http.post(
      Uri.parse('$_baseUrl/collections/$collectionName/points/scroll'),
      headers: _headers,
      body: jsonEncode({
        'limit': 500,
        'with_payload': true,
        'with_vector': false,
      }),
    );
    if (res.statusCode != 200) return [];
    final body = jsonDecode(res.body) as Map<String, dynamic>;
    final points = (body['result']?['points'] as List?) ?? [];
    return points.map((p) => _fromPoint(p as Map<String, dynamic>)).toList();
  }

  /// Drop and recreate the collection — used for staging resets.
  Future<void> deleteCollection() async {
    await _http.delete(
      Uri.parse('$_baseUrl/collections/$collectionName'),
      headers: _headers,
    );
  }

  /// Delete a client by its string ID.
  Future<void> deleteClient(String clientId) async {
    await _http.post(
      Uri.parse('$_baseUrl/collections/$collectionName/points/delete'),
      headers: _headers,
      body: jsonEncode({
        'points': [_stableId(clientId)],
      }),
    );
  }

  // ── Helpers ───────────────────────────────────────────────────────────────

  int _stableId(String id) {
    int h = 0;
    for (final c in id.codeUnits) {
      h = (h * 31 + c) & 0x7FFFFFFF;
    }
    return h == 0 ? 1 : h;
  }

  Map<String, dynamic> _toPoint(Client client) => {
        'id': _stableId(client.id),
        'vector': _toVector(client),
        'payload': {
          'clientId': client.id,
          'name': client.name,
          'city': client.city,
          'businessType': client.businessType.name,
          'parcelVolume': client.parcelVolume,
          'activeContracts': client.activeContracts,
          'provider': client.provider,
          'efficiency': client.efficiency,
          'status': client.status.name,
          'lastUpdated': client.lastUpdated,
          'surveySubmitted': client.surveySubmitted,
          'notes': client.notes,
          'dataConsent': client.dataConsent.name,
          'phone': client.phone,
          'frequency': client.frequency,
          'businessState': client.businessState,
          'transitType': client.transitType,
          'transitFrom': client.transitFrom,
          'transitTo': client.transitTo,
          'neighbourhood': client.neighbourhood,
          'logisticsSegment': client.logisticsSegment,
          'pincode': client.pincode,
          // GPS captured at submission — not shown in app, stored for field audit
          'surveyLat': client.surveyLat,
          'surveyLng': client.surveyLng,
          'surveyAddress': client.surveyAddress,
          'surveyZone': client.surveyZone,
          'surveyPincode': client.surveyPincode,
          // Field-agent attribution — who collected this survey (login session)
          'recordedByName': client.recordedByName,
          'recordedByEmail': client.recordedByEmail,
          'recordedByRole': client.recordedByRole,
          // Qdrant geo payload — enables geo-radius queries later
          if (client.surveyLat != 0.0 || client.surveyLng != 0.0)
            'surveyGeo': {'lat': client.surveyLat, 'lon': client.surveyLng},
        },
      };

  List<double> _toVector(Client client) {
    final v = List<double>.filled(_vectorSize, 0.0);
    v[0] = (client.parcelVolume / 1200.0).clamp(0.0, 1.0);
    v[1] = (client.activeContracts / 40.0).clamp(0.0, 1.0);
    v[2 + client.businessType.index] = 1.0;
    v[6 + client.status.index] = 1.0;
    v[10] = client.surveySubmitted ? 1.0 : 0.0;
    v[11] = client.dataConsent == DataConsent.full ? 1.0 : 0.0;
    v[12] = client.phone.isNotEmpty ? 1.0 : 0.0;
    v[13] = (client.transitFrom.isNotEmpty && client.transitTo.isNotEmpty)
        ? 1.0
        : 0.0;
    v[14] = _frequencyScore(client.frequency);
    v[15] = client.neighbourhood.isNotEmpty ? 1.0 : 0.0;
    return v;
  }

  double _frequencyScore(String freq) {
    switch (freq.toLowerCase()) {
      case 'daily':
        return 1.0;
      case 'weekly':
        return 0.75;
      case 'bi-weekly':
        return 0.5;
      case 'monthly':
        return 0.25;
      default:
        return 0.0;
    }
  }

  Client _fromPoint(Map<String, dynamic> point) {
    final p = point['payload'] as Map<String, dynamic>;
    return Client(
      id: p['clientId'] as String,
      name: p['name'] as String,
      city: p['city'] as String,
      businessType:
          BusinessType.values.firstWhere((b) => b.name == p['businessType']),
      parcelVolume: (p['parcelVolume'] as num?)?.toDouble() ?? 0.0,
      activeContracts: p['activeContracts'] as int,
      provider: p['provider'] as String,
      efficiency: p['efficiency'] as String,
      status: ClientStatus.values.firstWhere((s) => s.name == p['status']),
      lastUpdated: p['lastUpdated'] as String,
      surveySubmitted: p['surveySubmitted'] as bool,
      notes: (p['notes'] as String?) ?? '',
      dataConsent: DataConsent.values.firstWhere(
        (d) => d.name == (p['dataConsent'] as String? ?? 'full'),
        orElse: () => DataConsent.full,
      ),
      phone: (p['phone'] as String?) ?? '',
      frequency: (p['frequency'] as String?) ?? '',
      businessState: (p['businessState'] as String?) ?? '',
      transitType: (p['transitType'] as String?) ?? '',
      transitFrom: (p['transitFrom'] as String?) ?? '',
      transitTo: (p['transitTo'] as String?) ?? '',
      neighbourhood: (p['neighbourhood'] as String?) ?? '',
      logisticsSegment: (p['logisticsSegment'] as String?) ?? '',
      pincode: (p['pincode'] as String?) ?? '',
      surveyLat: (p['surveyLat'] as num?)?.toDouble() ?? 0.0,
      surveyLng: (p['surveyLng'] as num?)?.toDouble() ?? 0.0,
      surveyAddress: (p['surveyAddress'] as String?) ?? '',
      surveyZone: (p['surveyZone'] as String?) ?? '',
      surveyPincode: (p['surveyPincode'] as String?) ?? '',
      recordedByName: (p['recordedByName'] as String?) ?? '',
      recordedByEmail: (p['recordedByEmail'] as String?) ?? '',
      recordedByRole: (p['recordedByRole'] as String?) ?? '',
    );
  }
}
