// 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: 30);
  // Set to true to enable mock/offline development mode (useful when backend is unavailable)
  static const bool _developmentMode = true;

  /// 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',
          };
        }
      }

      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) {
        throw Exception('Authentication required');
      }
      finalParams['token'] = token;
      finalParams['username'] = username;
    }

    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);
  }

  /// Build request body and attach token + username if required
  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) {
        throw Exception('Authentication required');
      }

      finalBody['token'] = token;
      finalBody['username'] = username;
    }

    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);
  }
}
