// lib/core/api/api_client.dart
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import '../storage/token_storage.dart';

class ApiClient {
  static const Duration _timeout = Duration(seconds: 10);
  // Set to true to enable mock/offline development mode (useful when backend is unavailable)
  static const bool _developmentMode = false;

  /// POST request
  ///
  /// - `url` should be a fully qualified endpoint (ApiEndpoints.*)
  /// - `body` is the JSON object to send (Map)
  /// - when `requiresAuth == true` token & username are added to the request body
  Future<Map<String, dynamic>> post(
    String url, {
    Map<String, dynamic>? body,
    bool requiresAuth = true,
  }) async {
    final headers = <String, String>{
      'Content-Type': 'application/json',
    };

    final Map<String, dynamic> finalBody = await _buildAuthBody(body, requiresAuth);

    late http.Response response;
    try {
      response = await http
          .post(
            Uri.parse(url),
            headers: headers,
            body: jsonEncode(finalBody),
          )
          .timeout(_timeout);
    } catch (e) {
      if (kDebugMode) debugPrint('ApiClient.post network error: $e');

      // Development mode: return mock responses for common endpoints on network errors
      if (_developmentMode) {
        if (url.contains('/user/login/')) {
          if (kDebugMode) debugPrint('Development mode: returning mock login response');
          final email = finalBody['username'] ?? 'test@example.com';
          return {
            'token': 'mock_token_${DateTime.now().millisecondsSinceEpoch}',
            'username': email,
            'email': email,
            'phone_number': '+1234567890',
          };
        } else if (url.contains('/user/register/')) {
          if (kDebugMode) debugPrint('Development mode: returning mock register response');
          final email = finalBody['email'] ?? 'test@example.com';
          return {
            'token': 'mock_token_${DateTime.now().millisecondsSinceEpoch}',
            'username': email,
            'email': email,
            'phone_number': finalBody['phone_number'] ?? '+1234567890',
          };
        } else if (url.contains('/events/type-list/')) {
          if (kDebugMode) debugPrint('Development mode: returning mock event types');
          return {
            'event_types': [
              {'id': 1, 'event_type': 'Concert', 'event_type_icon': null},
              {'id': 2, 'event_type': 'Workshop', 'event_type_icon': null},
              {'id': 3, 'event_type': 'Festival', 'event_type_icon': null},
              {'id': 4, 'event_type': 'Sports', 'event_type_icon': null},
              {'id': 5, 'event_type': 'Conference', 'event_type_icon': null},
              {'id': 6, 'event_type': 'Exhibition', 'event_type_icon': null},
            ],
          };
        } else if (url.contains('/events/pincode-events/')) {
          if (kDebugMode) debugPrint('Development mode: returning mock events');
          return {'events': _mockEvents};
        } else if (url.contains('/events/event-details/')) {
          if (kDebugMode) debugPrint('Development mode: returning mock event detail');
          final eventId = finalBody['event_id'] ?? 1;
          final match = _mockEvents.where((e) => e['id'] == eventId);
          return match.isNotEmpty
              ? Map<String, dynamic>.from(match.first)
              : Map<String, dynamic>.from(_mockEvents.first);
        } else if (url.contains('/events/events-by-month-year/')) {
          if (kDebugMode) debugPrint('Development mode: returning mock calendar');
          return {
            'total_number_of_events': 3,
            'dates': ['2026-04-05', '2026-04-12', '2026-04-20'],
            'date_events': [
              {'date': '2026-04-05', 'count': 1},
              {'date': '2026-04-12', 'count': 2},
              {'date': '2026-04-20', 'count': 1},
            ],
          };
        }
      }

      throw Exception('Network error: $e');
    }

    return _handleResponse(url, response, finalBody);
  }

  /// GET request
  ///
  /// - If requiresAuth==true, token & username will be attached as query parameters.
  /// - `params` will be appended as query parameters.
  Future<Map<String, dynamic>> get(
    String url, {
    Map<String, dynamic>? params,
    bool requiresAuth = true,
  }) async {
    // build final query params including auth if needed
    final Map<String, dynamic> finalParams = {};

    if (requiresAuth) {
      final token = await TokenStorage.getToken();
      final username = await TokenStorage.getUsername();
      if (token != null && username != null) {
        finalParams['token'] = token;
        finalParams['username'] = username;
      }
      // Guest mode: proceed without token — let backend decide
    }

    if (params != null) finalParams.addAll(params);

    final uri = Uri.parse(url).replace(queryParameters: finalParams.map((k, v) => MapEntry(k, v?.toString())));

    late http.Response response;
    try {
      response = await http.get(uri).timeout(_timeout);
    } catch (e) {
      if (kDebugMode) debugPrint('ApiClient.get network error: $e');
      throw Exception('Network error: $e');
    }

    return _handleResponse(url, response, finalParams);
  }

  // ---------------------------------------------------------------------------
  // Mock event data for development / offline mode
  // ---------------------------------------------------------------------------
  static final List<Map<String, dynamic>> _mockEvents = [
    {
      'id': 1,
      'name': 'Tech Innovation Summit 2026',
      'title': 'Tech Innovation Summit',
      'description':
          'Join industry leaders for a two-day summit exploring the latest breakthroughs in AI, cloud computing, and sustainable technology. Featuring keynote speakers, hands-on workshops, and networking sessions.',
      'start_date': '2026-04-15',
      'end_date': '2026-04-16',
      'start_time': '09:00',
      'end_time': '18:00',
      'pincode': '560001',
      'place': 'Bengaluru International Exhibition Centre',
      'is_bookable': true,
      'event_type': 5,
      'thumb_img': 'https://picsum.photos/seed/event1/600/400',
      'images': [
        {'is_primary': true, 'image': 'https://picsum.photos/seed/event1a/800/500'},
        {'is_primary': false, 'image': 'https://picsum.photos/seed/event1b/800/500'},
      ],
      'important_information': 'Please carry a valid photo ID for entry.',
      'venue_name': 'BIEC Hall 2',
      'event_status': 'active',
      'latitude': 13.0147,
      'longitude': 77.5636,
      'location_name': 'Bengaluru',
      'important_info': [
        {'title': 'Entry', 'value': 'Free with registration'},
        {'title': 'Parking', 'value': 'Available on-site'},
      ],
    },
    {
      'id': 2,
      'name': 'Sunset Music Festival',
      'title': 'Sunset Music Festival',
      'description':
          'An open-air music festival featuring live performances from top artists across genres. Enjoy food stalls, art installations, and an unforgettable sunset experience.',
      'start_date': '2026-04-20',
      'end_date': '2026-04-20',
      'start_time': '16:00',
      'end_time': '23:00',
      'pincode': '400001',
      'place': 'Marine Drive Amphitheatre',
      'is_bookable': true,
      'event_type': 1,
      'thumb_img': 'https://picsum.photos/seed/event2/600/400',
      'images': [
        {'is_primary': true, 'image': 'https://picsum.photos/seed/event2a/800/500'},
      ],
      'venue_name': 'Marine Drive Amphitheatre',
      'event_status': 'active',
      'latitude': 18.9432,
      'longitude': 72.8235,
      'location_name': 'Mumbai',
      'important_info': [
        {'title': 'Age Limit', 'value': '16+'},
      ],
    },
    {
      'id': 3,
      'name': 'Creative Design Workshop',
      'title': 'Hands-on Design Workshop',
      'description':
          'A full-day workshop on UI/UX design principles, prototyping in Figma, and building design systems. Perfect for beginners and intermediate designers.',
      'start_date': '2026-05-03',
      'end_date': '2026-05-03',
      'start_time': '10:00',
      'end_time': '17:00',
      'pincode': '110001',
      'place': 'Design Hub Co-working',
      'is_bookable': true,
      'event_type': 2,
      'thumb_img': 'https://picsum.photos/seed/event3/600/400',
      'images': [
        {'is_primary': true, 'image': 'https://picsum.photos/seed/event3a/800/500'},
      ],
      'venue_name': 'Design Hub',
      'event_status': 'active',
      'latitude': 28.6139,
      'longitude': 77.2090,
      'location_name': 'New Delhi',
      'important_info': [
        {'title': 'Bring', 'value': 'Laptop with Figma installed'},
        {'title': 'Seats', 'value': '30 max'},
      ],
    },
    {
      'id': 4,
      'name': 'Marathon for a Cause',
      'title': 'City Marathon 2026',
      'description':
          'Run for fitness, run for charity! Choose from 5K, 10K, or full marathon routes through the city. All proceeds support local education initiatives.',
      'start_date': '2026-04-12',
      'end_date': '2026-04-12',
      'start_time': '05:30',
      'end_time': '12:00',
      'pincode': '600001',
      'place': 'Marina Beach Road',
      'is_bookable': true,
      'event_type': 4,
      'thumb_img': 'https://picsum.photos/seed/event4/600/400',
      'images': [
        {'is_primary': true, 'image': 'https://picsum.photos/seed/event4a/800/500'},
      ],
      'venue_name': 'Marina Beach',
      'event_status': 'active',
      'latitude': 13.0500,
      'longitude': 80.2824,
      'location_name': 'Chennai',
      'important_info': [
        {'title': 'Registration', 'value': 'Closes April 10'},
      ],
    },
    {
      'id': 5,
      'name': 'Art & Culture Exhibition',
      'title': 'Contemporary Art Exhibition',
      'description':
          'Explore contemporary artworks from emerging and established artists. The exhibition features paintings, sculptures, and digital art installations.',
      'start_date': '2026-05-10',
      'end_date': '2026-05-15',
      'start_time': '11:00',
      'end_time': '20:00',
      'pincode': '500001',
      'place': 'Salar Jung Museum Grounds',
      'is_bookable': true,
      'event_type': 6,
      'thumb_img': 'https://picsum.photos/seed/event5/600/400',
      'images': [
        {'is_primary': true, 'image': 'https://picsum.photos/seed/event5a/800/500'},
        {'is_primary': false, 'image': 'https://picsum.photos/seed/event5b/800/500'},
      ],
      'venue_name': 'Salar Jung Museum',
      'event_status': 'active',
      'latitude': 17.3713,
      'longitude': 78.4804,
      'location_name': 'Hyderabad',
      'important_info': [
        {'title': 'Entry Fee', 'value': '₹200'},
        {'title': 'Photography', 'value': 'Allowed without flash'},
      ],
    },
  ];

  /// Build request body and attach token + username if available
  Future<Map<String, dynamic>> _buildAuthBody(Map<String, dynamic>? body, bool requiresAuth) async {
    final Map<String, dynamic> finalBody = {};

    if (requiresAuth) {
      final token = await TokenStorage.getToken();
      final username = await TokenStorage.getUsername();

      if (token != null && username != null) {
        finalBody['token'] = token;
        finalBody['username'] = username;
      }
      // Guest mode: proceed without token — let backend decide
    }

    if (body != null) finalBody.addAll(body);

    return finalBody;
  }

  /// Centralized response handling and error parsing
  Map<String, dynamic> _handleResponse(String url, http.Response response, Map<String, dynamic> requestBody) {
    dynamic decoded;
    try {
      decoded = jsonDecode(response.body);
    } catch (e) {
      decoded = response.body;
    }

    if (kDebugMode) {
      debugPrint('API -> $url');
      debugPrint('Status: ${response.statusCode}');
      debugPrint('Request body: ${jsonEncode(requestBody)}');
      debugPrint('Response body: ${response.body}');
    }

    if (response.statusCode >= 200 && response.statusCode < 300) {
      if (decoded is Map<String, dynamic>) return decoded;
      return {'data': decoded};
    }

    // Build human-friendly message from common server patterns
    String errorMessage = 'Request failed (status ${response.statusCode})';

    if (decoded is Map) {
      // 1) If there's an explicit top-level 'message' (string), prefer it
      if (decoded.containsKey('message') && decoded['message'] is String) {
        errorMessage = decoded['message'] as String;
      }
      // 2) If 'errors' exists and is a map, collect inner messages
      else if (decoded.containsKey('errors')) {
        final errs = decoded['errors'];
        final messages = <String>[];
        if (errs is String) {
          messages.add(errs);
        } else if (errs is List) {
          for (final e in errs) messages.add(e.toString());
        } else if (errs is Map) {
          // collect first-level messages (prefer the text, not the key)
          errs.forEach((k, v) {
            if (v is List && v.isNotEmpty) {
              messages.add(v.first.toString());
            } else if (v is String) {
              messages.add(v);
            } else {
              messages.add(v.toString());
            }
          });
        } else {
          messages.add(errs.toString());
        }
        if (messages.isNotEmpty) {
          errorMessage = messages.join(' | ');
        }
      }
      // 3) If '__all__' present (DRF default), show it
      else if (decoded.containsKey('__all__')) {
        final all = decoded['__all__'];
        if (all is List) {
          errorMessage = all.join(' | ');
        } else {
          errorMessage = all.toString();
        }
      }
      // 4) fallback - join map values' messages (prefer strings inside lists)
      else {
        final messages = <String>[];
        decoded.forEach((k, v) {
          if (v is List && v.isNotEmpty) {
            messages.add(v.first.toString());
          } else {
            messages.add(v.toString());
          }
        });
        errorMessage = messages.isNotEmpty ? messages.join(' | ') : decoded.toString();
      }
    } else if (decoded is String) {
      errorMessage = decoded;
    }

    throw Exception(errorMessage);
  }
}
