import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:wisata_app/config/api_config.dart';
import 'package:wisata_app/models/destination.dart';

class DestinationService {
  const DestinationService({http.Client? client}) : _client = client;

  final http.Client? _client;

  static List<Destination> localDestinations = [];

  static List<Destination> get destinations => localDestinations;

  Future<List<Destination>> getAllDestinations() async {
    final client = _client ?? http.Client();
    try {
      final items = <dynamic>[];
      var currentPage = 1;
      var lastPage = 1;

      do {
        final uri = ApiConfig.apiUri('/destinations').replace(
          queryParameters: {
            'per_page': '100',
            'page': currentPage.toString(),
          },
        );
        final response = await client.get(uri).timeout(
              const Duration(seconds: 15),
            );

        if (response.statusCode < 200 || response.statusCode >= 300) {
          throw DestinationServiceException(
            'API destinasi gagal dimuat. HTTP ${response.statusCode}.',
          );
        }

        final decoded = jsonDecode(response.body);
        items.addAll(_extractItems(decoded));
        lastPage = _lastPageFrom(decoded);
        currentPage++;
      } while (currentPage <= lastPage);

      final destinations = items
          .whereType<Map<String, dynamic>>()
          .map(Destination.fromJson)
          .where((destination) => destination.title.isNotEmpty)
          .toList();

      localDestinations = destinations;
      return destinations;
    } finally {
      if (_client == null) client.close();
    }
  }

  Future<List<Destination>> getDestinations() => getAllDestinations();

  Future<Destination?> getDestinationDetail(String id) async {
    try {
      return await getDestinationById(id);
    } on DestinationServiceException {
      return _findCachedById(id);
    }
  }

  Future<Destination> getDestinationById(String id) async {
    final client = _client ?? http.Client();
    try {
      final response = await client
          .get(ApiConfig.apiUri('/destinations/$id'))
          .timeout(const Duration(seconds: 15));

      if (response.statusCode < 200 || response.statusCode >= 300) {
        throw DestinationServiceException(
          'Detail destinasi gagal dimuat. HTTP ${response.statusCode}.',
        );
      }

      final decoded = jsonDecode(response.body);
      final data = decoded is Map<String, dynamic> ? decoded['data'] : decoded;
      if (data is! Map<String, dynamic>) {
        throw const DestinationServiceException(
          'Format detail destinasi dari API tidak valid.',
        );
      }

      final destination = Destination.fromJson(data);
      final nextDestinations = [...localDestinations];
      final index = nextDestinations.indexWhere((item) => item.id == id);
      if (index >= 0) {
        nextDestinations[index] = destination;
      } else {
        nextDestinations.add(destination);
      }
      localDestinations = nextDestinations;
      return destination;
    } finally {
      if (_client == null) client.close();
    }
  }

  List<Destination> get localDestinationsSnapshot => localDestinations;

  Destination findById(String id) {
    final destination = _findCachedById(id);
    if (destination != null) return destination;

    throw DestinationServiceException(
      'Destinasi dengan ID $id tidak ditemukan.',
    );
  }

  static Destination? _findCachedById(String id) {
    for (final item in localDestinations) {
      if (item.id == id) return item;
    }
    return null;
  }

  List<dynamic> _extractItems(Object? decoded) {
    if (decoded is List) return decoded;
    if (decoded is Map<String, dynamic>) {
      final data = decoded['data'];
      if (data is List) return data;
      if (data is Map<String, dynamic>) {
        final nestedData = data['data'];
        if (nestedData is List) return nestedData;
      }
    }
    throw const DestinationServiceException(
      'Format daftar destinasi dari API tidak valid.',
    );
  }

  int _lastPageFrom(Object? decoded) {
    if (decoded is Map<String, dynamic>) {
      final meta = decoded['meta'];
      if (meta is Map<String, dynamic>) {
        final lastPage = meta['last_page'];
        if (lastPage is num) return lastPage.toInt();
        if (lastPage is String) return int.tryParse(lastPage) ?? 1;
      }
    }
    return 1;
  }
}

class DestinationServiceException implements Exception {
  const DestinationServiceException(this.message);

  final String message;

  @override
  String toString() => message;
}
