import 'dart:convert';
import 'dart:developer';

import 'package:dio/dio.dart';

abstract class TripRemoteDataSource {
  /// Fetch available landmarks for the provided preferences/start payload.
  Future<List<Map<String, dynamic>>> fetchLandmarks(Map<String, dynamic> body);

  /// Create a new trip using the optimizer payload (that includes landmarks).
  Future<Map<String, dynamic>> createTrip(Map<String, dynamic> body);

  /// Fetch an existing trip by UUID
  Future<Map<String, dynamic>> fetchTrip(String uuid);
}

class TripRemoteDataSourceImpl implements TripRemoteDataSource {
  final Dio dio;

  TripRemoteDataSourceImpl({required this.dio});

  @override
  Future<List<Map<String, dynamic>>> fetchLandmarks(Map<String, dynamic> body) async {
    final response = await dio.post('/get/landmarks', data: body);
    if (response.statusCode != 200) {
      throw Exception('Server error fetching landmarks: ${response.statusCode}');
    }

    if (response.data is! List) {
      throw Exception('Unexpected landmarks response format');
    }

    return _normalizeLandmarks(List<dynamic>.from(response.data as List));
  }

  @override
  Future<Map<String, dynamic>> createTrip(Map<String, dynamic> body) async {
    log('Creating trip with body: $body');
    // convert body to actual json
    String bodyJson = jsonEncode(body);
    log('Trip request JSON: $bodyJson');
    final response = await dio.post('/optimize/trip', data: body);
    if (response.statusCode != 200) {
      throw Exception('Server error: ${response.statusCode}');
    }

    if (response.data is! Map<String, dynamic>) {
      throw Exception('Unexpected response format');
    }

    final Map<String, dynamic> json = Map<String, dynamic>.from(response.data as Map);

    _ensureLandmarks(json);
    if (json.containsKey('landmarks') && json['landmarks'] is List) {
      return json;
    }

    final String? firstUuid = json['first_landmark_uuid'] as String?;
    if (firstUuid == null) {
      return json;
    }

    final List<Map<String, dynamic>> landmarks = [];
    String? next = firstUuid;
    while (next != null) {
      final lm = await _fetchLandmarkByUuid(next);
      landmarks.add(lm);
      final dynamic nxt = lm['next_uuid'];
      next = (nxt is String) ? nxt : null;
    }

    json['landmarks'] = landmarks;
    return json;
  }

  @override
  Future<Map<String, dynamic>> fetchTrip(String uuid) async {
    final response = await dio.get('/trip/$uuid');
    if (response.statusCode != 200) {
      throw Exception('Server error: ${response.statusCode}');
    }
    if (response.data is! Map<String, dynamic>) {
      throw Exception('Unexpected response format');
    }

    final Map<String, dynamic> json = Map<String, dynamic>.from(response.data as Map);
    // Normalize same as createTrip: if trip contains first_landmark_uuid, expand chain
    if (json.containsKey('landmarks') && json['landmarks'] is List) {
      _ensureLandmarks(json);
      return json;
    }

    final String? firstUuid = json['first_landmark_uuid'] as String?;
    if (firstUuid == null) return json;

    final List<Map<String, dynamic>> landmarks = [];
    String? next = firstUuid;
    while (next != null) {
      final lm = await _fetchLandmarkByUuid(next);
      landmarks.add(lm);
      final dynamic nxt = lm['next_uuid'];
      next = (nxt is String) ? nxt : null;
    }

    json['landmarks'] = landmarks;
    return json;
  }

  // Fetch a single landmark by uuid via the /landmark/{landmark_uuid} endpoint
  Future<Map<String, dynamic>> _fetchLandmarkByUuid(String uuid) async {
    final response = await dio.get('/landmark/$uuid');
    if (response.statusCode != 200) {
      throw Exception('Failed to fetch landmark $uuid: ${response.statusCode}');
    }
    if (response.data is! Map<String, dynamic>) {
      throw Exception('Unexpected landmark response format');
    }
    return _normalizeLandmark(Map<String, dynamic>.from(response.data as Map));
  }

  static void _ensureLandmarks(Map<String, dynamic> json) {
    final raw = json['landmarks'];
    if (raw is List) {
      json['landmarks'] = _normalizeLandmarks(raw);
    }
  }

  static List<Map<String, dynamic>> _normalizeLandmarks(List<dynamic> rawList) {
    return rawList
        .map((item) => _normalizeLandmark(Map<String, dynamic>.from(item as Map)))
        .toList();
  }

  static Map<String, dynamic> _normalizeLandmark(Map<String, dynamic> source) {
    final normalized = Map<String, dynamic>.from(source);
    final typeValue = normalized['type'];
    if (typeValue is String) {
      normalized['type'] = {'type': typeValue};
    }
    return normalized;
  }
}
