import 'dart:math';
import 'package:chepuhagram/data/models/contact_model.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'dart:convert';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:chepuhagram/core/constants.dart';
import 'package:flutter_http_cache/flutter_http_cache.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'dart:io';

class ApiService extends ChangeNotifier {
  final _client = http.Client();
  final _storage = const FlutterSecureStorage();
  bool _isRefreshing = false;
  bool _isCacheInitialized = false;

  final cache = HttpCache(
    config: const CacheConfig(
      maxMemorySize: 100 * 1024 * 1024, // 100MB
      maxDiskSize: 500 * 1024 * 1024, // 500MB
    ),
  );

  Future<void> _ensureCacheReady() async {
    if (!_isCacheInitialized) {
      await cache.initialize();
      _isCacheInitialized = true;
    }
  }

  /// Получает данные пользователя (включая его публичный ключ E2EE) по username
  Future<Contact?> getUserByUsername(String username) async {
    try {
      // Подставляй свой эндпоинт, например: /users/by-username/
      final response = await Dio().get('/users/by-username/$username'); 
      
      if (response.statusCode == 200 && response.data != null) {
        // Парсим полученные данные в модель контакта. 
        // Убедись, что метод Contact.fromJson или Contact.fromMap корректно обрабатывает поле public_key
        return Contact.fromJson(response.data);
      }
      return null;
    } catch (e) {
      print("[ApiService] Ошибка при получении пользователя по username: $e");
      return null;
    }
  }

  Future<String?> copyMediaOnServer(String fileId, int receiverId) async {
    try {
      final token = await getAccessToken();
      final response = await Dio().post(
        '${AppConstants.baseUrl}/media/copy',
        data: FormData.fromMap({'file_id': fileId, 'receiver_id': receiverId}),
        options: Options(headers: {'Authorization': 'Bearer $token'}),
      );
      if (response.statusCode == 200) {
        return response.data['new_file_id'];
      }
    } catch (e) {
      print("Ошибка копирования на сервере: $e");
    }
    return null;
  }

  Future<String?> uploadFile(
    List<int> bytes, {
    String purpose = 'media',
    Function(double)? onProgress,
  }) async {
    final token = await getAccessToken();
    final dio = Dio();
    final formData = FormData.fromMap({
      'file': MultipartFile.fromBytes(bytes, filename: 'media.enc'),
      'purpose': purpose,
    });

    final response = await dio.post(
      '${AppConstants.baseUrl}/media/v2/upload',
      data: formData,
      onSendProgress: (sent, total) {
        onProgress?.call(sent / total);
      },
      options: Options(headers: {'Authorization': 'Bearer $token'}),
    );

    return response.data['file_id'];
  }

  Future<(int?, String?)> getRemoteFileSizeAndName(String fileId) async {
    try {
      final token = await getAccessToken();
      final url =
          '${AppConstants.baseUrl}/media/size/$fileId'; // Скорректируй путь согласно роутеру

      final response = await Dio().get(
        url,
        options: Options(
          headers: {
            'Authorization': 'Bearer $token', // Твой токен, если требуется
          },
        ),
      );

      if (response.statusCode == 200 && response.data != null) {
        // Извлекаем размер из JSON: {"file_id": "...", "size": 123456}
        final intSize = response.data['size'] as int?;
        String? fileName = response.data['file_name'] as String?;

        if (fileName != null) {
          fileName = Uri.decodeComponent(fileName);
          print("Имя файла, полученное от сервера: $fileName");
        }
        debugPrint(
          'Успешно получен размер файла через API-size: $intSize байт',
        );
        return (intSize, fileName);
      }
    } catch (e) {
      debugPrint('Ошибка при получении размера файла через API-size: $e');
    }
    return (null, null);
  }

  Future<(http.ByteStream, String)> downloadFileAsStream(String fileId) async {
    final token = await getAccessToken(); // Получаем JWT токен авторизации

    final url = Uri.parse(
      '${AppConstants.baseUrl}/media/$fileId',
    ); // Замените на ваш эндпоинт скачивания

    final request = http.Request('GET', url);
    request.headers.addAll({'Authorization': 'Bearer $token'});

    // Отправляем запрос
    final http.StreamedResponse response = await _client.send(request);

    final contentDisposition = response.headers['content-disposition'];
    String serverFileName = 'media.enc';

    if (contentDisposition != null) {
      final match = RegExp(
        r"filename\*=UTF-8''(.+)",
      ).firstMatch(contentDisposition);
      if (match != null) {
        serverFileName = Uri.decodeComponent(match.group(1)!);
        print("Имя файла, полученное от сервера: $serverFileName");
      }
    }

    if (response.statusCode == 200) {
      return (response.stream, serverFileName);
    } else {
      throw Exception(
        'Ошибка скачивания файла: сервер вернул статус ${response.statusCode}',
      );
    }
  }

  Future<File?> downloadFile(String fileId, String filePath) async {
    final token = await getAccessToken();
    try {
      final response = await Dio().download(
        '${AppConstants.baseUrl}/media/$fileId',
        filePath,
        options: Options(
          headers: {'Authorization': 'Bearer $token'},
          validateStatus: (status) => status == 200,
        ),
      );

      if (response.statusCode == 200) {
        return File(filePath);
      }
    } catch (e) {
      debugPrint('Ошибка при скачивании: $e');
      final file = File(filePath);
      if (await file.exists()) await file.delete();
    }
    return null;
  }

  Future<String?> uploadFileStream(
    Stream<List<int>> stream,
    int sourceLength, {
    String purpose = 'media',
    void Function(int processed, int total)? onProgress,
    String? fileName,
  }) async {
    try {
      final token = await getAccessToken();
      final dio = Dio();

      print(
        '[DEBUG] uploadFileStream: работаем через поточное чтение. Размер=$sourceLength bytes, purpose=$purpose',
      );

      // БЕЗОПАСНО ДЛЯ RAM: Передаем стрим напрямую в Dio. Память не забивается!
      final formData = FormData.fromMap({
        'file': MultipartFile.fromStream(
          () => stream,
          sourceLength, // Передаем точную длину стрима, это важно для прогресса!
          filename: fileName,
        ),
        'purpose': purpose,
      });

      final response = await dio.post(
        '${AppConstants.baseUrl}/media/v2/upload',
        data: formData,
        onSendProgress: (sent, total) {
          // Твой print(sent) теперь будет вызываться динамически по мере ухода байт в сеть!
          if (total > 0) {
            onProgress?.call(sent, total);
          }
        },
        options: Options(
          headers: {'Authorization': 'Bearer $token'},
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      print('[DEBUG] uploadFileStream response status=${response.statusCode}');
      print('[DEBUG] uploadFileStream response data=${response.data}');

      if (response.statusCode == 200 || response.statusCode == 201) {
        final fileId = response.data['file_id']?.toString();
        print('[DEBUG] uploadFileStream: успешно загружен, file_id=$fileId');
        return fileId;
      } else {
        print(
          '[ERROR] uploadFileStream: ошибка ${response.statusCode} - ${response.data}',
        );
        return null;
      }
    } catch (e) {
      print('[ERROR] uploadFileStream exception: $e');
      return null;
    }
  }

  Future<bool> refreshToken() async {
    if (_isRefreshing) {
      // Already refreshing, wait for completion or return true assuming it will succeed
      return true;
    }
    _isRefreshing = true;
    notifyListeners();

    try {
      final refreshToken = await _storage.read(key: 'refresh_token');
      final response = await _client
          .post(
            Uri.parse('${AppConstants.baseUrl}/auth/refresh'),
            body: jsonEncode({'refresh_token': refreshToken}),
            headers: {'Content-Type': 'application/json'},
          )
          .timeout(Duration(seconds: 30));

      final decodedResponse =
          jsonDecode(utf8.decode(response.bodyBytes)) as Map;

      if (response.statusCode == 200) {
        await _storage.write(
          key: 'access_token',
          value: decodedResponse['access_token'],
        );
        await _storage.write(
          key: 'refresh_token',
          value: decodedResponse['refresh_token'],
        );
        notifyListeners();
        return true;
      } else {
        notifyListeners();
        return false;
      }
    } catch (e) {
      notifyListeners();
      return false;
    } finally {
      _isRefreshing = false;
    }
  }

  Future<String?> getAccessToken() async {
    String? token = await _storage.read(key: 'access_token');

    if (token != null) {
      bool isExpiredSoon =
          JwtDecoder.isExpired(token) ||
          JwtDecoder.getRemainingTime(token).inMinutes < 2;

      if (isExpiredSoon) {
        bool refreshed = await refreshToken();
        if (refreshed) {
          token = await _storage.read(key: 'access_token');
        } else {
          return null;
        }
      }
    }
    return token;
  }

  Future<bool> updateFcmToken(String fcmtoken) async {
    notifyListeners();
    try {
      final token = await getAccessToken();
      if (token == null) return false; // Нет токена — прерываем выполнение

      final response = await _client
          .post(
            Uri.parse(
              '${AppConstants.baseUrl}/auth/update-fcm?token=$fcmtoken',
            ),
            headers: {
              'Content-Type': 'application/json',
              'Authorization': 'Bearer $token',
            },
          )
          .timeout(const Duration(seconds: 10)); // Ограничиваем время ожидания

      if (response.statusCode == 200) {
        return true;
      } else {
        print("Ошибка установки FCM ключа: ${response.statusCode}");
        return false;
      }
    } catch (e) {
      print("ℹ️ Не удалось обновить FCM токен (нет сети): $e");
      return false; // Возвращаем false вместо падения приложения
    } finally {
      notifyListeners();
    }
  }

  Future<bool> setPublicKey(String publicKey) async {
    notifyListeners();

    try {
      final token = await getAccessToken();
      final response = await _client.post(
        Uri.parse('${AppConstants.baseUrl}/auth/set-public-key'),
        body: jsonEncode({'public_key': publicKey}),
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer $token',
        },
      );

      if (response.statusCode == 200) {
        return true;
      } else {
        print("Ошибка установки ключа: ${response.statusCode}");
        return false;
      }
    } catch (e) {
      rethrow;
    } finally {
      notifyListeners();
    }
  }

  Future<Map<String, dynamic>> getMe() async {
    final token = await getAccessToken();
    await cache.initialize();
    final client = CachedHttpClient(
      cache: cache,
      defaultCachePolicy: CachePolicy.networkFirst,
    );
    final response = await client.get(
      Uri.parse('${AppConstants.baseUrl}/users/me'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
    );

    if (response.statusCode == 200) {
      return jsonDecode(utf8.decode(response.bodyBytes))
          as Map<String, dynamic>;
    }
    throw Exception('Не удалось получить данные пользователя');
  }

  Future<bool> updateEncryptedPrivateKey(String encryptedPrivateKey) async {
    final token = await getAccessToken();
    final response = await _client.put(
      Uri.parse('${AppConstants.baseUrl}/users/me/encryption-key'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
      body: jsonEncode({'encrypted_private_key': encryptedPrivateKey}),
    );

    return response.statusCode == 200;
  }

  Future<bool> changePassword(
    String currentPassword,
    String newPassword,
  ) async {
    final token = await getAccessToken();
    final response = await _client.put(
      Uri.parse('${AppConstants.baseUrl}/users/me/password'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
      body: jsonEncode({
        'current_password': currentPassword,
        'new_password': newPassword,
      }),
    );

    return response.statusCode == 200;
  }

  Future<List<dynamic>> getChatHistory(
    int contactId, {
    bool forceRefresh = false,
  }) async {
    final token = await getAccessToken();
    await _ensureCacheReady();

    final client = CachedHttpClient(
      cache: cache,
      defaultCachePolicy: CachePolicy.networkFirst,
    );

    final Map<String, String> requestHeaders = {
      'Authorization': 'Bearer $token',
    };

    if (forceRefresh) {
      requestHeaders['Cache-Control'] = 'no-cache';
    }

    final response = await client.get(
      Uri.parse(
        '${AppConstants.baseUrl}/messages/history/${contactId.toString()}',
      ),
      headers: {
        'Content-Type': 'application/json',
        "Authorization": "Bearer $token",
      },
    );
    return jsonDecode(utf8.decode(response.bodyBytes)) as List<dynamic>;
  }

  Future<Uint8List?> downloadMedia(
    String fileId, {
    void Function(int received, int total)? onProgress,
  }) async {
    try {
      final token = await getAccessToken();
      await _ensureCacheReady();

      final client = CachedHttpClient(
        cache: cache,
        defaultCachePolicy: CachePolicy.networkFirst,
      );

      final uri = Uri.parse('${AppConstants.baseUrl}/media/$fileId');

      if (onProgress == null) {
        final response = await client.get(
          uri,
          headers: {'Authorization': 'Bearer $token'},
        );
        if (response.statusCode == 200) {
          return response.bodyBytes;
        }
        print('?????? ???????? ?????: ${response.statusCode}');
        return null;
      }

      final req = http.Request('GET', uri);
      req.headers['Authorization'] = 'Bearer $token';
      final streamed = await client.send(req);

      final total =
          int.tryParse(streamed.headers['content-length'] ?? '') ?? -1;
      int received = 0;
      final bytes = <int>[];
      await for (final chunk in streamed.stream) {
        bytes.addAll(chunk);
        received += chunk.length;
        onProgress(received, total);
      }

      if (streamed.statusCode == 200) {
        return Uint8List.fromList(bytes);
      }
      print('?????? ???????? ?????: ${streamed.statusCode}');
      return null;
    } catch (e) {
      print('?????? downloadMedia: $e');
      return null;
    }
  }

  Future<Map<String, dynamic>> updateMe({
    required String username,
    required String firstName,
    required String lastName,
    String? phone,
    String? email,
    String? about,
  }) async {
    final token = await getAccessToken();
    final response = await _client.put(
      Uri.parse('${AppConstants.baseUrl}/users/me'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
      body: jsonEncode({
        'username': username,
        'first_name': firstName,
        'last_name': lastName,
        'phone': (phone == null || phone.trim().isEmpty) ? null : phone.trim(),
        'email': (email == null || email.trim().isEmpty) ? null : email.trim(),
        'about': (about == null || about.trim().isEmpty) ? null : about.trim(),
      }),
    );

    final decoded = jsonDecode(utf8.decode(response.bodyBytes));
    if (response.statusCode == 200) {
      return decoded as Map<String, dynamic>;
    }
    throw Exception(
      (decoded is Map && decoded['detail'] != null)
          ? decoded['detail']
          : 'Failed to update profile',
    );
  }

  Future<Map<String, dynamic>> getUserById(int userId) async {
    final token = await getAccessToken();
    await _ensureCacheReady();
    final client = CachedHttpClient(
      cache: cache,
      defaultCachePolicy: CachePolicy.networkFirst,
    );
    final response = await client.get(
      Uri.parse('${AppConstants.baseUrl}/users/$userId'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
    );

    if (response.statusCode == 200) {
      return jsonDecode(utf8.decode(response.bodyBytes))
          as Map<String, dynamic>;
    }
    throw Exception('Не удалось получить информацию о пользователе');
  }

  Future<bool> updatePrivacySettings({
    bool? showEmail,
    bool? showPhone,
    bool? showAvatar,
    bool? showAbout,
    bool? showUsername,
    bool? showLastOnline,
  }) async {
    final token = await getAccessToken();
    final response = await _client.put(
      Uri.parse('${AppConstants.baseUrl}/users/me/privacy'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
      body: jsonEncode({
        if (showEmail != null) 'show_email': showEmail,
        if (showPhone != null) 'show_phone': showPhone,
        if (showAvatar != null) 'show_avatar': showAvatar,
        if (showAbout != null) 'show_about': showAbout,
        if (showUsername != null) 'show_username': showUsername,
        if (showLastOnline != null) 'show_last_online': showLastOnline,
      }),
    );

    return response.statusCode == 200;
  }

  Future<Map<String, dynamic>> getPrivacySettings() async {
    final token = await getAccessToken();

    await _ensureCacheReady();
    final client = CachedHttpClient(
      cache: cache,
      defaultCachePolicy: CachePolicy.networkFirst,
    );
    final response = await client.get(
      Uri.parse('${AppConstants.baseUrl}/users/me/privacy'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
    );

    if (response.statusCode == 200) {
      return jsonDecode(utf8.decode(response.bodyBytes))
          as Map<String, dynamic>;
    }
    throw Exception('Не удалось получить настройки конфиденциальности');
  }

  Future<bool> updateAvatar(String fileId) async {
    final token = await getAccessToken();
    final response = await _client.put(
      Uri.parse('${AppConstants.baseUrl}/users/me/avatar'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
      body: jsonEncode({'avatar_file_id': fileId}),
    );
    return response.statusCode == 200;
  }

  Future<Map<String, dynamic>> enableTotp() async {
    final token = await getAccessToken();
    final response = await _client.post(
      Uri.parse('${AppConstants.baseUrl}/auth/totp/enable'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
    );

    if (response.statusCode == 200) {
      return jsonDecode(utf8.decode(response.bodyBytes))
          as Map<String, dynamic>;
    }
    throw Exception(
      (jsonDecode(response.body) as Map<String, dynamic>)['detail'] ??
          'Failed to enable TOTP',
    );
  }

  Future<bool> verifyTotp(String code) async {
    final token = await getAccessToken();
    final response = await _client.post(
      Uri.parse('${AppConstants.baseUrl}/auth/totp/verify'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
      body: jsonEncode({'code': code}),
    );
    return response.statusCode == 200;
  }

  Future<bool> disableTotp() async {
    final token = await getAccessToken();
    final response = await _client.post(
      Uri.parse('${AppConstants.baseUrl}/auth/totp/disable'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
    );
    return response.statusCode == 200;
  }

  Future<bool> deleteAllMessages() async {
    final token = await getAccessToken();
    final response = await _client.delete(
      Uri.parse('${AppConstants.baseUrl}/messages/all'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $token',
      },
    );
    return response.statusCode == 200;
  }
}
