import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:flutter/foundation.dart';

class ApiService {
  static const String baseUrl = 'https://nomazra.kelthos.com/api/nomazra';
  String? _token;
  String? _refreshToken;
  bool _isRefreshing = false;

  void setToken(String token) => _token = token;
  void setRefreshToken(String token) => _refreshToken = token;
  void clearToken() { _token = null; _refreshToken = null; }
  String? get currentToken => _token;
  String? get currentRefresh => _refreshToken;
  bool get hasValidToken => _token != null && _token!.isNotEmpty;

  Map<String, String> get _headers => {
        'Content-Type': 'application/json',
        if (_token != null) 'Authorization': 'Bearer $_token',
      };

  Map<String, String> get _authOnly => {
        if (_token != null) 'Authorization': 'Bearer $_token',
      };

  // Auto-refresh wrapper for authenticated requests
  Future<Map<String, dynamic>> _authRequest(
    Future<http.Response> Function() request,
  ) async {
    var res = await request();
    // If 401/403 and we have refresh token, try refreshing once
    if ((res.statusCode == 401 || res.statusCode == 403) && 
        _refreshToken != null && !_isRefreshing) {
      debugPrint('Auth failed (${res.statusCode}), attempting token refresh...');
      final refreshed = await _tryRefresh();
      if (refreshed) {
        debugPrint('Token refreshed, retrying request...');
        res = await request(); // Retry with new token
      }
    }
    return _handle(res);
  }

  Future<bool> _tryRefresh() async {
    if (_refreshToken == null || _isRefreshing) return false;
    _isRefreshing = true;
    try {
      final res = await http.post(
        Uri.parse('$baseUrl/auth/refresh/'),
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode({'refresh': _refreshToken}),
      );
      if (res.statusCode >= 200 && res.statusCode < 300) {
        final data = jsonDecode(res.body) as Map<String, dynamic>;
        if (data['access'] != null) {
          _token = data['access'] as String;
          debugPrint('Token refresh successful');
          return true;
        }
      }
      debugPrint('Token refresh failed: ${res.statusCode}');
    } catch (e) {
      debugPrint('Token refresh error: $e');
    } finally {
      _isRefreshing = false;
    }
    return false;
  }

  // ═══════════════ AUTH ═══════════════

  Future<Map<String, dynamic>> register({
    required String email,
    required String password,
    required String name,
    String? country,
    String language = 'en',
  }) async {
    final res = await http.post(
      Uri.parse('$baseUrl/auth/register/'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'email': email,
        'password': password,
        'display_name': name,
        if (country != null) 'country': country,
        'language': language,
      }),
    );
    return _handle(res);
  }

  Future<Map<String, dynamic>> login({
    required String email,
    required String password,
  }) async {
    final res = await http.post(
      Uri.parse('$baseUrl/auth/login/'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'email': email, 'password': password}),
    );
    return _handle(res);
  }

  Future<Map<String, dynamic>> refreshAccessToken() async {
    if (_refreshToken == null) throw ApiException('No refresh token', 401);
    final res = await http.post(
      Uri.parse('$baseUrl/auth/refresh/'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'refresh': _refreshToken}),
    );
    final data = _handle(res);
    if (data['access'] != null) _token = data['access'] as String;
    return data;
  }

  Future<Map<String, dynamic>> getProfile() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/auth/me/'), headers: _headers));
  }

  Future<Map<String, dynamic>> updateProfile(Map<String, dynamic> data) async {
    return _authRequest(() => http.patch(
      Uri.parse('$baseUrl/auth/me/update/'),
      headers: _headers,
      body: jsonEncode(data),
    ));
  }

  Future<Map<String, dynamic>> changePassword({
    required String currentPassword,
    required String newPassword,
  }) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/auth/me/password/'),
      headers: _headers,
      body: jsonEncode({
        'current_password': currentPassword,
        'new_password': newPassword,
      }),
    ));
  }

  Future<Map<String, dynamic>> updateFcmToken(String fcmToken) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/auth/me/fcm-token/'),
      headers: _headers,
      body: jsonEncode({'fcm_token': fcmToken}),
    ));
  }

  Future<Map<String, dynamic>> deleteAccount() async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/auth/delete-account/'),
      headers: _headers,
    ));
  }

  Future<Map<String, dynamic>> passwordResetRequest(String email) async {
    final res = await http.post(
      Uri.parse('$baseUrl/auth/reset-password/'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'email': email}),
    );
    return _handle(res);
  }

  Future<Map<String, dynamic>> passwordResetConfirm({
    required String token,
    required String newPassword,
  }) async {
    final res = await http.post(
      Uri.parse('$baseUrl/auth/reset-password/confirm/'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'token': token, 'new_password': newPassword}),
    );
    return _handle(res);
  }

  // ═══════════════ CONVERSATIONS ═══════════════

  Future<Map<String, dynamic>> getConversations() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/conversations/'), headers: _headers));
  }

  Future<Map<String, dynamic>> createConversation({
    String? title,
    String? country,
    String? legalArea,
  }) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/conversations/'),
      headers: _headers,
      body: jsonEncode({
        'title': title ?? 'New Conversation',
        if (country != null) 'country': country,
        if (legalArea != null) 'legal_area': legalArea,
      }),
    ));
  }

  Future<Map<String, dynamic>> getConversationDetail(String id) async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/conversations/$id/'), headers: _headers));
  }

  Future<void> deleteConversation(String id) async {
    await _authRequest(() => http.delete(Uri.parse('$baseUrl/conversations/$id/'), headers: _headers));
    // Success if we got here without exception
  }

  Future<Map<String, dynamic>> getMessages(String conversationId) async {
    return _authRequest(() => http.get(
      Uri.parse('$baseUrl/conversations/$conversationId/messages/'),
      headers: _headers,
    ));
  }

  Future<Map<String, dynamic>> sendMessage({
    required String conversationId,
    required String message,
    String inputType = 'text',
  }) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/conversations/$conversationId/messages/'),
      headers: _headers,
      body: jsonEncode({'content': message, 'input_type': inputType}),
    ));
  }

  // ═══════════════ DOCUMENTS ═══════════════

  Future<Map<String, dynamic>> uploadFile(String filePath, String fileName) async {
    // For multipart, manually handle auth retry
    Future<http.Response> doUpload() async {
      final request = http.MultipartRequest('POST', Uri.parse('$baseUrl/documents/'));
      request.headers.addAll(_authOnly);
      request.fields['original_name'] = fileName;
      final ext = fileName.split('.').last.toLowerCase();
      final mimeType = _mimeForExt(ext);
      request.files.add(await http.MultipartFile.fromPath(
        'file', filePath, filename: fileName,
        contentType: MediaType.parse(mimeType),
      ));
      final streamRes = await request.send();
      return http.Response.fromStream(streamRes);
    }
    return _authRequest(doUpload);
  }

  static String _mimeForExt(String ext) {
    const map = {
      'pdf': 'application/pdf',
      'doc': 'application/msword',
      'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
      'txt': 'text/plain',
      'csv': 'text/csv',
      'jpg': 'image/jpeg',
      'jpeg': 'image/jpeg',
      'png': 'image/png',
      'webp': 'image/webp',
      'mp3': 'audio/mpeg',
      'wav': 'audio/wav',
      'ogg': 'audio/ogg',
    };
    return map[ext] ?? 'application/octet-stream';
  }

  Future<Map<String, dynamic>> getDocuments() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/documents/'), headers: _headers));
  }

  Future<Map<String, dynamic>> getDocumentDetail(String id) async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/documents/$id/'), headers: _headers));
  }

  Future<void> deleteDocument(String id) async {
    await _authRequest(() => http.delete(Uri.parse('$baseUrl/documents/$id/'), headers: _headers));
  }

  Future<Map<String, dynamic>> linkDocumentToConversation(String docId, String conversationId) async {
    return _authRequest(() => http.patch(
      Uri.parse('$baseUrl/documents/$docId/'),
      headers: _headers,
      body: jsonEncode({'conversation_id': conversationId}),
    ));
  }

  // ═══════════════ VOICE ═══════════════

  Future<Map<String, dynamic>> startVoiceSession({String? country, String? legalArea}) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/voice/start-session/'),
      headers: _headers,
      body: jsonEncode({
        if (country != null) 'country': country,
        if (legalArea != null) 'legal_area': legalArea,
      }),
    ));
  }

  Future<Map<String, dynamic>> endVoiceSession(String sessionId) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/voice/end-session/'),
      headers: _headers,
      body: jsonEncode({'session_id': sessionId}),
    ));
  }

  Future<Map<String, dynamic>> getVoiceSessions() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/voice/sessions/'), headers: _headers));
  }

  Future<Map<String, dynamic>> getTtsVoices() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/voice/tts/voices/'), headers: _headers));
  }

  Future<Map<String, dynamic>> voiceTranscribe(String filePath, String fileName) async {
    Future<http.Response> doTranscribe() async {
      final request = http.MultipartRequest('POST', Uri.parse('$baseUrl/voice/transcribe/'));
      request.headers.addAll(_authOnly);
      request.files.add(await http.MultipartFile.fromPath('audio', filePath, filename: fileName, contentType: MediaType('audio', 'wav')));
      final streamRes = await request.send();
      return http.Response.fromStream(streamRes);
    }
    return _authRequest(doTranscribe);
  }

  Future<Map<String, dynamic>> voiceTts({
    required String text,
    String? voice,
  }) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/voice/tts/'),
      headers: _headers,
      body: jsonEncode({
        'text': text,
        if (voice != null) 'voice': voice,
      }),
    ));
  }

  /// Returns raw audio bytes from OpenAI TTS endpoint.
  Future<List<int>> voiceTtsBytes({
    required String text,
    String voice = 'nova',
    double speed = 1.0,
  }) async {
    Future<http.Response> doTts() async {
      return http.post(
        Uri.parse('$baseUrl/voice/tts/'),
        headers: _headers,
        body: jsonEncode({
          'text': text,
          'voice': voice,
          'speed': speed,
          'response_format': 'mp3',
        }),
      );
    }
    
    var res = await doTts();
    // Handle auth retry manually for bytes response
    if ((res.statusCode == 401 || res.statusCode == 403) && _refreshToken != null && !_isRefreshing) {
      final refreshed = await _tryRefresh();
      if (refreshed) res = await doTts();
    }
    
    if (res.statusCode >= 200 && res.statusCode < 300) {
      return res.bodyBytes;
    }
    String msg = 'TTS failed';
    try {
      final body = jsonDecode(res.body);
      if (body is Map && body.containsKey('error')) msg = body['error'].toString();
    } catch (_) {}
    throw ApiException(msg, res.statusCode);
  }

  /// Combined voice turn: send audio, get MP3 response back (1 round-trip).
  /// Returns {audioBytes, userText, aiText}.
  Future<Map<String, dynamic>> voiceTurn({
    required String filePath,
    required String conversationId,
    String voice = 'nova',
  }) async {
    Future<http.Response> doTurn() async {
      final request = http.MultipartRequest('POST', Uri.parse('$baseUrl/voice/turn/'));
      request.headers.addAll(_authOnly);
      request.fields['conversation_id'] = conversationId;
      request.fields['voice'] = voice;
      request.files.add(await http.MultipartFile.fromPath(
        'audio', filePath, filename: 'recording.wav',
        contentType: MediaType('audio', 'wav'),
      ));
      final streamRes = await request.send();
      return http.Response.fromStream(streamRes);
    }

    var res = await doTurn();
    if ((res.statusCode == 401 || res.statusCode == 403) && _refreshToken != null && !_isRefreshing) {
      final refreshed = await _tryRefresh();
      if (refreshed) res = await doTurn();
    }

    if (res.statusCode >= 200 && res.statusCode < 300) {
      final contentType = res.headers['content-type'] ?? '';
      if (contentType.contains('audio/')) {
        return {
          'audioBytes': res.bodyBytes,
          'userText': res.headers['x-user-text'] ?? '',
          'aiText': res.headers['x-ai-text'] ?? '',
        };
      }
      // JSON response (empty transcription)
      try {
        final body = jsonDecode(res.body);
        return {
          'audioBytes': <int>[],
          'userText': body['text']?.toString() ?? '',
          'aiText': body['ai_text']?.toString() ?? '',
        };
      } catch (_) {
        return {'audioBytes': <int>[], 'userText': '', 'aiText': ''};
      }
    }
    String msg = 'Voice processing failed';
    try {
      final body = jsonDecode(res.body);
      if (body is Map && body.containsKey('error')) msg = body['error'].toString();
    } catch (_) {}
    throw ApiException(msg, res.statusCode);
  }

  // ═══════════════ PDF EXPORT ═══════════════

  Future<List<int>> exportPdfBytes(String messageId) async {
    Future<http.Response> doExport() => http.post(
      Uri.parse('$baseUrl/export/pdf/$messageId/'),
      headers: _headers,
    );
    var res = await doExport();
    if ((res.statusCode == 401 || res.statusCode == 403) && _refreshToken != null) {
      if (await _tryRefresh()) res = await doExport();
    }
    if (res.statusCode >= 200 && res.statusCode < 300) {
      return res.bodyBytes;
    }
    String msg = 'PDF export failed';
    try {
      final body = jsonDecode(res.body);
      if (body is Map && body.containsKey('error')) msg = body['error'].toString();
    } catch (_) {}
    throw ApiException(msg, res.statusCode);
  }

  // ═══════════════ SUBSCRIPTION ═══════════════

  Future<Map<String, dynamic>> verifyPurchase({
    required String purchaseToken,
    required String productId,
  }) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/subscription/verify/'),
      headers: _headers,
      body: jsonEncode({
        'purchase_token': purchaseToken,
        'product_id': productId,
      }),
    ));
  }

  // ═══════════════ OFFLINE ═══════════════

  Future<Map<String, dynamic>> getOfflineContent() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/offline/content/'), headers: _headers));
  }

  Future<Map<String, dynamic>> getOfflineBundle() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/offline/bundle/'), headers: _headers));
  }

  // ═══════════════ RIGHTS CARDS ═══════════════

  Future<Map<String, dynamic>> getRightsCards() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/rights-cards/'), headers: _headers));
  }

  Future<Map<String, dynamic>> getRightsCardDetail(String id) async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/rights-cards/$id/'), headers: _headers));
  }

  Future<Map<String, dynamic>> getRightsCardsByCategory(String category) async {
    return _authRequest(() => http.get(
      Uri.parse('$baseUrl/rights-cards/category/$category/'),
      headers: _headers,
    ));
  }

  // ═══════════════ GLOSSARY ═══════════════

  Future<Map<String, dynamic>> getGlossary({String? query}) async {
    final q = query != null && query.isNotEmpty ? '?search=$query' : '';
    return _authRequest(() => http.get(Uri.parse('$baseUrl/glossary/$q'), headers: _headers));
  }

  Future<Map<String, dynamic>> getGlossaryTerm(String term) async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/glossary/$term/'), headers: _headers));
  }

  Future<Map<String, dynamic>> searchGlossary(String query) async {
    return _authRequest(() => http.get(
      Uri.parse('$baseUrl/glossary/search/?q=$query'),
      headers: _headers,
    ));
  }

  // ═══════════════ BOOKMARKS ═══════════════

  Future<Map<String, dynamic>> getBookmarks() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/bookmarks/'), headers: _headers));
  }

  Future<Map<String, dynamic>> createBookmark(Map<String, dynamic> data) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/bookmarks/'),
      headers: _headers,
      body: jsonEncode(data),
    ));
  }

  Future<void> deleteBookmark(String id) async {
    await _authRequest(() => http.delete(Uri.parse('$baseUrl/bookmarks/$id/'), headers: _headers));
  }

  // ═══════════════ REMINDERS ═══════════════

  Future<Map<String, dynamic>> getReminders() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/reminders/'), headers: _headers));
  }

  Future<Map<String, dynamic>> createReminder(Map<String, dynamic> data) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/reminders/'),
      headers: _headers,
      body: jsonEncode(data),
    ));
  }

  Future<Map<String, dynamic>> updateReminder(String id, Map<String, dynamic> data) async {
    return _authRequest(() => http.patch(
      Uri.parse('$baseUrl/reminders/$id/'),
      headers: _headers,
      body: jsonEncode(data),
    ));
  }

  Future<void> deleteReminder(String id) async {
    await _authRequest(() => http.delete(Uri.parse('$baseUrl/reminders/$id/'), headers: _headers));
  }

  // ═══════════════ AI TOOLS ═══════════════

  Future<Map<String, dynamic>> statuteCheck({
    required String caseType,
    required String incidentDate,
    String? country,
  }) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/statute-check/'),
      headers: _headers,
      body: jsonEncode({
        'case_type': caseType,
        'incident_date': incidentDate,
        if (country != null) 'country': country,
      }),
    ));
  }

  Future<Map<String, dynamic>> estimateCost(Map<String, dynamic> data) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/cost-estimate/'),
      headers: _headers,
      body: jsonEncode(data),
    ));
  }

  Future<Map<String, dynamic>> contractDraft({
    required String contractType,
    required String details,
    String? country,
  }) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/contract-draft/'),
      headers: _headers,
      body: jsonEncode({
        'contract_type': contractType,
        'details': details,
        if (country != null) 'country': country,
      }),
    ));
  }

  Future<Map<String, dynamic>> lawSearch({
    required String description,
    String? country,
  }) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/law-search/'),
      headers: _headers,
      body: jsonEncode({
        'description': description,
        if (country != null) 'country': country,
      }),
    ));
  }

  // ═══════════════ COUNTRIES & EMERGENCY ═══════════════

  Future<Map<String, dynamic>> getCountries() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/countries/'), headers: _headers));
  }

  Future<Map<String, dynamic>> getCountryDetail(String code) async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/countries/$code/'), headers: _headers));
  }

  Future<Map<String, dynamic>> getCountryTopics(String code) async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/countries/$code/topics/'), headers: _headers));
  }

  Future<Map<String, dynamic>> getEmergencyInfo({String? country}) async {
    final q = country != null ? '?country=$country' : '';
    return _authRequest(() => http.get(Uri.parse('$baseUrl/emergency-info/$q'), headers: _headers));
  }

  // ═══════════════ INTAKE ═══════════════

  Future<Map<String, dynamic>> getIntakeQuestions() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/intake/'), headers: _headers));
  }

  Future<Map<String, dynamic>> submitIntake(Map<String, dynamic> answers) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/intake/'),
      headers: _headers,
      body: jsonEncode({'answers': answers}),
    ));
  }

  // ═══════════════ STATS & SUBSCRIPTION ═══════════════

  Future<Map<String, dynamic>> getStats() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/stats/'), headers: _headers));
  }

  Future<Map<String, dynamic>> getSubscriptionStatus() async {
    return _authRequest(() => http.get(Uri.parse('$baseUrl/subscription/status/'), headers: _headers));
  }

  // ═══════════════ FEEDBACK ═══════════════

  Future<Map<String, dynamic>> submitFeedback(String messageId, {
    required String rating,
    String? comment,
  }) async {
    return _authRequest(() => http.post(
      Uri.parse('$baseUrl/feedback/$messageId/'),
      headers: _headers,
      body: jsonEncode({
        'rating': rating,
        if (comment != null) 'comment': comment,
      }),
    ));
  }

  // ═══════════════ HANDLER ═══════════════

  Map<String, dynamic> _handle(http.Response res) {
    if (res.statusCode >= 200 && res.statusCode < 300) {
      if (res.body.isEmpty) return {'success': true};
      final decoded = jsonDecode(res.body);
      if (decoded is List) return {'results': decoded};
      return decoded as Map<String, dynamic>;
    }
    if (res.statusCode == 401 && _refreshToken != null) {
      // Token might be expired — caller should try refresh
    }
    String msg = 'Request failed';
    try {
      final body = jsonDecode(res.body);
      if (body is Map && body.containsKey('detail')) {
        msg = body['detail'].toString();
      } else if (body is Map && body.containsKey('error')) {
        msg = body['error'].toString();
      }
    } catch (_) {
      debugPrint('API error ${res.statusCode}: ${res.body}');
    }
    throw ApiException(msg, res.statusCode);
  }
}

class ApiException implements Exception {
  final String message;
  final int statusCode;
  ApiException(this.message, this.statusCode);

  bool get isLimitError =>
      statusCode == 429 ||
      statusCode == 402 ||
      (statusCode == 403 && _looksLikeLimit) ||
      _looksLikeLimit;

  bool get _looksLikeLimit {
    final m = message.toLowerCase();
    return m.contains('limit') ||
        m.contains('quota') ||
        m.contains('exceeded') ||
        m.contains('upgrade') ||
        m.contains('plan') ||
        m.contains('allowance') ||
        m.contains('rate limit');
  }

  @override
  String toString() => 'ApiException($statusCode): $message';
}
