import 'dart:async';
import 'package:flutter/material.dart';
import '/data/models/message_model.dart';
import '/data/models/contact_model.dart';
import 'package:chepuhagram/presentation/widgets/message_bubble.dart';
import 'package:chepuhagram/data/repositories/contact_repository.dart';
import 'package:chepuhagram/domain/services/crypto_service.dart';
import 'package:chepuhagram/data/datasources/ws_client.dart';
import 'package:provider/provider.dart';
import '/logic/contact_provider.dart';
import '../../domain/services/api_service.dart';
import 'dart:math';
import 'package:chepuhagram/data/datasources/local_db_service.dart';
import 'package:chepuhagram/main.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'contacts_screen.dart';
import 'package:flutter/services.dart';
import 'user_profile_screen.dart';
import 'package:image_picker/image_picker.dart';
import '/core/theme_manager.dart';
import 'dart:io';

class ChatScreen extends StatefulWidget {
  final Contact contact;

  const ChatScreen({super.key, required this.contact});

  @override
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> with RouteAware {
  static const String _notificationLaunchKey = 'notification_launch_data';
  int myId = 0;
  late Contact _currentContact;
  bool _isKeyLoading = true;
  final TextEditingController _controller = TextEditingController();
  final FocusNode _inputFocusNode = FocusNode();
  final ContactRepository _contactRepository = ContactRepository();
  final apiService = ApiService();
  final CryptoService _cryptoService = CryptoService();
  List<MessageModel> messages = [];
  StreamSubscription<dynamic>? _socketSubscription;
  final Set<int> _sentReadReceipts = <int>{};
  final LocalDbService _localDbService = LocalDbService();
  Uint8List? _pendingImageBytes;
  MessageModel? _replyTo;
  bool _isOnline = false;
  DateTime? _lastOnline;
  Timer? _onlineTimer;
  DateTime? _lastTypingSent;
  bool _isTyping = false;
  Timer? _typingTimer;
  late SocketService _socketService;

  @override
  void initState() {
    super.initState();
    _currentContact = widget.contact;
    _socketService = Provider.of<SocketService>(context, listen: false);
    currentActiveChatContactId =
        _currentContact.id; // Устанавливаем активный чат
    flutterLocalNotificationsPlugin.cancel(currentActiveChatContactId!);
    final contactProvider = context.read<ContactProvider>();
    myId = contactProvider.getCurrentUserId() ?? 0;
    // Если ключа нет, загружаем его при входе
    _loadLocalName();
    if (_currentContact.publicKey == null) {
      _loadContactKey();
    }
    _loadHistory();
    _loadOnlineStatus();
    startOnlineUpdates();
    _controller.addListener(_sendTypingStatus);

    final socketService = Provider.of<SocketService>(context, listen: false);
    _socketSubscription = socketService.messages.listen(_handleIncomingMessage);
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute);
  }

  @override
  void didPopNext() {
    print("Пользователь вернулся на этот экран!");
    _loadLocalName();
    flutterLocalNotificationsPlugin.cancel(currentActiveChatContactId!);
  }

  Future<void> _loadLocalName() async {
    final prefs = await SharedPreferences.getInstance();

    final String? savedName = prefs.getString(
      'firstname_${_currentContact.id}',
    );
    final String? savedSurname = prefs.getString(
      'lastname_${_currentContact.id}',
    );
    print('Загружены имя $savedName, $savedSurname');
    if (mounted) {
      setState(() {
        if (savedName != null) {
          _currentContact.name = savedName;
        }
        if (savedSurname != null) {
          _currentContact.surname = savedSurname;
        }
      });
    }
  }

  void _sendTypingStatus() {
    final now = DateTime.now();
    if (_lastTypingSent == null ||
        now.difference(_lastTypingSent!) > const Duration(seconds: 3)) {
      _lastTypingSent = now;
      final socketService = Provider.of<SocketService>(context, listen: false);
      socketService.sendMessage({
        'type': 'typing',
        'receiver_id': _currentContact.id,
      });
    }
  }

  void _sendStopTypingStatus() {
    _socketService.sendMessage({
      'type': 'stop_typing',
      'receiver_id': _currentContact.id,
    });
  }

  Future<void> _loadOnlineStatus() async {
    flutterLocalNotificationsPlugin.cancel(currentActiveChatContactId!);
    try {
      print(
        "🔍 Загружаем онлайн статус для контакта ${_currentContact.name} (ID: ${_currentContact.id})",
      );
      final data = await apiService.getUserById(_currentContact.id);
      if (!mounted) return;
      DateTime now = DateTime.now();

      Duration offset = now.timeZoneOffset;
      print(
        "✅ Получен онлайн статус: ${data['online']}, last_online: ${data['last_online'] != null ? DateTime.tryParse(data['last_online']!)?.add(offset) : null}",
      );

      setState(() {
        _isOnline = data['online'] ?? false;
        if (data['last_online'] != null)
          _lastOnline = DateTime.parse(data['last_online']).add(offset);
        else
          _lastOnline = null;
      });
    } catch (e) {
      print("❌ ОШИБКА ПРИ ЗАГРУЗКЕ СТАТУСА ОНЛАЙН: $e");
      // Игнорируем ошибки при загрузке статуса
    }
  }

  void startOnlineUpdates() {
    _onlineTimer = Timer.periodic(const Duration(minutes: 1), (_) {
      _loadOnlineStatus();
    });
  }

  Future<void> _loadContactKey() async {
    if (!mounted) return;
    setState(() => _isKeyLoading = true);
    try {
      final updatedContact = await _contactRepository.fetchContactById(
        _currentContact.id,
      );
      if (!mounted) return;
      setState(() {
        _currentContact = updatedContact;
        _isKeyLoading = false;
      });
      print(updatedContact.publicKey);
    } catch (e) {
      if (!mounted) return;
      setState(() => _isKeyLoading = false);
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text("Не удалось получить ключ шифрования собеседника"),
          behavior: SnackBarBehavior.floating, // Обязательно для margin
          margin: EdgeInsets.only(bottom: 80.0 + 10.0, left: 10.0, right: 10.0),
          duration: Duration(seconds: 3),
        ),
      );
    }
  }

  @override
  void dispose() {
    currentActiveChatContactId = null;
    _socketSubscription?.cancel();
    _controller.dispose();
    routeObserver.unsubscribe(this);
    _inputFocusNode.dispose();
    _onlineTimer?.cancel();
    _typingTimer?.cancel();
    _controller.removeListener(_sendTypingStatus);
    _sendStopTypingStatus();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final themeProv = context.watch<ThemeProvider>();
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          onPressed: () {
            if (Navigator.canPop(context)) {
              Navigator.pop(context);
            } else {
              Navigator.of(context).pushReplacement(
                MaterialPageRoute(builder: (_) => const ContactsScreen()),
              );
            }
          },
        ),
        title: GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => UserProfileScreen(
                  userId: _currentContact.id,
                  username: _currentContact.username,
                  name: _currentContact.name,
                ),
              ),
            );
          },
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                '${_currentContact.name} ${_currentContact.surname != 'Unknown' ? _currentContact.surname : ''}',
              ),
              if (_isKeyLoading == true)
                const Text(
                  'загрузка...',
                  style: const TextStyle(
                    fontSize: 12,
                    color: Color.fromARGB(255, 219, 219, 219),
                  ),
                )
              else if (_isTyping)
                Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    const Text(
                      'печатает',
                      style: TextStyle(fontSize: 12, color: Colors.greenAccent),
                    ),
                    const SizedBox(width: 4),
                    TypingIndicator(),
                  ],
                )
              else if (_isOnline)
                const Text(
                  'онлайн',
                  style: TextStyle(fontSize: 12, color: Colors.greenAccent),
                )
              else if (_lastOnline != null)
                Text(
                  'был(а) в сети ${_formatLastOnline(_lastOnline!)}',
                  style: const TextStyle(
                    fontSize: 12,
                    color: Color.fromARGB(255, 219, 219, 219),
                  ),
                )
              else
                const Text(
                  'был(а) недавно',
                  style: TextStyle(
                    fontSize: 12,
                    color: Color.fromARGB(255, 219, 219, 219),
                  ),
                ),
            ],
          ),
        ),
      ),
      body: Column(
        children: [
          Expanded(
            child: Container(
              decoration: themeProv.wallpaperPath != null
                  ? BoxDecoration(
                      image: DecorationImage(
                        image: FileImage(File(themeProv.wallpaperPath!)),
                        fit: BoxFit.cover,
                      ),
                    )
                  : null,
              child: ListView.builder(
                reverse: true, // Сообщения растут снизу вверх
                itemCount: messages.length,
                itemBuilder: (context, index) {
                  final msg = messages[messages.length - 1 - index];
                  return MessageBubble(
                    message: msg,
                    onTap: () => _showMessageActions(msg),
                  );
                },
              ),
            ),
          ),
          _buildMessageInput(),
        ],
      ),
    );
  }

  String _formatLastOnline(DateTime lastOnline) {
    final now = DateTime.now();
    final difference = now.difference(lastOnline);

    if (difference.inSeconds < 60) {
      return 'только что';
    } else if (difference.inMinutes < 60) {
      return '${difference.inMinutes} минут${_pluralize(difference.inMinutes, "у", "ы", "")} назад';
    } else if (difference.inHours < 24) {
      return '${difference.inHours} час${_pluralize(difference.inHours, "", "а", "ов")} назад';
    } else if (difference.inDays < 7) {
      return '${difference.inDays} ${_pluralize(difference.inDays, "день", "дня", "дней")} назад';
    } else {
      return 'давно';
    }
  }

  String _pluralize(int count, String form1, String form2, String form5) {
    final mod10 = count % 10;
    final mod100 = count % 100;
    if (mod10 == 1 && mod100 != 11) {
      return form1;
    } else if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) {
      return form2;
    } else {
      return form5;
    }
  }

  Future<void> _showMessageActions(MessageModel msg) async {
    if (!mounted) return;

    await showModalBottomSheet<void>(
      context: context,
      showDragHandle: true,
      builder: (ctx) {
        return SafeArea(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              ListTile(
                leading: const Icon(Icons.reply),
                title: const Text('Ответить'),
                onTap: () {
                  Navigator.of(ctx).pop();
                  setState(() => _replyTo = msg);
                  _inputFocusNode.requestFocus();
                },
              ),
              if (msg.isMe)
                ListTile(
                  leading: const Icon(Icons.edit),
                  title: const Text('Изменить'),
                  onTap: () {
                    Navigator.of(ctx).pop();
                    _editMessage(msg);
                  },
                ),
              ListTile(
                leading: const Icon(Icons.copy),
                title: const Text('Скопировать'),
                onTap: () async {
                  Navigator.of(ctx).pop();
                  await Clipboard.setData(ClipboardData(text: msg.text));
                  if (!mounted) return;
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text('Скопировано'),
                      behavior:
                          SnackBarBehavior.floating, // Обязательно для margin
                      margin: EdgeInsets.only(
                        bottom:
                            80.0 +
                            10.0, // 20px + стандартный отступ (по желанию)
                        left: 10.0,
                        right: 10.0,
                      ),
                      duration: Duration(seconds: 2),
                    ),
                  );
                },
              ),
              ListTile(
                leading: const Icon(Icons.forward),
                title: const Text('Переслать'),
                onTap: () {
                  Navigator.of(ctx).pop();
                  _showForwardContactPicker(msg);
                },
              ),
              if (msg.isMe)
                ListTile(
                  leading: const Icon(Icons.delete_outline),
                  title: const Text('Удалить'),
                  textColor: Colors.red,
                  iconColor: Colors.red,
                  onTap: () async {
                    Navigator.of(ctx).pop();
                    await _deleteMessage(msg);
                  },
                ),
              const SizedBox(height: 8),
            ],
          ),
        );
      },
    );
  }

  Future<void> _editMessage(MessageModel msg) async {
    final controller = TextEditingController(text: msg.text);
    final result = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Изменить сообщение'),
        content: TextField(
          controller: controller,
          minLines: 1,
          maxLines: 5,
          autofocus: true,
          decoration: const InputDecoration(hintText: 'Новый текст сообщения'),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(ctx).pop(false),
            child: const Text('Отмена'),
          ),
          ElevatedButton(
            onPressed: () => Navigator.of(ctx).pop(true),
            child: const Text('Сохранить'),
          ),
        ],
      ),
    );

    if (result != true || controller.text.trim().isEmpty) return;

    final newText = controller.text.trim();
    final myPrivKey = await _cryptoService.getPrivateKey();
    if (myPrivKey == null) return;
    final sharedSecret = await _cryptoService.deriveSharedSecret(
      myPrivKey,
      _currentContact.publicKey!,
    );
    final encryptedContent = await _cryptoService.encryptMessage(
      newText,
      sharedSecret,
    );

    final content50 = newText.length > 50 ? newText.substring(0, 50) : newText;
    final encryptedContent50 = await _cryptoService.encryptMessage(
      content50,
      sharedSecret,
    );

    setState(() {
      messages = messages.map((m) {
        if (m.id != null && m.id == msg.id) {
          return m.copyWith(text: newText, editedAt: DateTime.now());
        }
        return m;
      }).toList();
    });

    if (msg.id != null) {
      try {
        await _localDbService.updateMessageContent(
          msg.id!,
          encryptedContent,
          DateTime.now(),
        );
      } catch (_) {}
      Provider.of<SocketService>(context, listen: false).sendMessage({
        'type': 'edit_message',
        'message_id': msg.id,
        'content': encryptedContent,
        'content50': encryptedContent50,
      });
    }
  }

  Future<void> _deleteMessage(MessageModel msg) async {
    setState(() {
      messages.removeWhere(
        (m) =>
            (m.id != null && m.id == msg.id) ||
            (m.tempId != null && m.tempId == msg.tempId),
      );
    });

    final id = msg.id;
    if (id != null) {
      try {
        await _localDbService.deleteMessage(id);
      } catch (_) {}
      Provider.of<SocketService>(
        context,
        listen: false,
      ).sendMessage({'type': 'delete_message', 'message_id': id});
    }
  }

  Future<void> _showForwardContactPicker(MessageModel msg) async {
    final contactProvider = context.read<ContactProvider>();
    contactProvider.setCurrentUserId(myId);
    await contactProvider.loadAllContactsForNewChat();
    if (!mounted) return;

    final selectedContact = await showModalBottomSheet<Contact?>(
      context: context,
      isScrollControlled: true,
      builder: (ctx) {
        final provider = context.watch<ContactProvider>();
        if (provider.isLoading) {
          return const SizedBox(
            height: 150,
            child: Center(child: CircularProgressIndicator()),
          );
        }
        if (provider.error != null) {
          return Padding(
            padding: const EdgeInsets.all(16.0),
            child: Text('Ошибка загрузки контактов: ${provider.error}'),
          );
        }
        if (provider.allContacts.isEmpty) {
          return const Padding(
            padding: EdgeInsets.all(16.0),
            child: Text('Нет доступных контактов для пересылки.'),
          );
        }
        return SafeArea(
          child: ListView.builder(
            shrinkWrap: true,
            itemCount: provider.allContacts.length,
            itemBuilder: (ctx2, index) {
              final contact = provider.allContacts[index];
              return ListTile(
                leading: CircleAvatar(
                  child: Text(contact.name.isNotEmpty ? contact.name[0] : '?'),
                ),
                title: Text(contact.name),
                subtitle: Text(contact.username),
                onTap: () => Navigator.of(ctx).pop(contact),
              );
            },
          ),
        );
      },
    );

    if (selectedContact != null) {
      await _forwardMessage(msg, selectedContact);
    }
  }

  Future<void> _forwardMessage(MessageModel msg, Contact targetContact) async {
    final forwardText = msg.text.trim();
    if (forwardText.isEmpty) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Нечего пересылать.'),
          behavior: SnackBarBehavior.floating, // Обязательно для margin
          margin: EdgeInsets.only(
            bottom: 80.0 + 10.0, // 20px + стандартный отступ (по желанию)
            left: 10.0,
            right: 10.0,
          ),
          duration: Duration(seconds: 5),
        ),
      );
      return;
    }

    if (targetContact.publicKey == null) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            'Публичный ключ контакта ${targetContact.name} не найден.',
          ),
          behavior: SnackBarBehavior.floating,
          margin: EdgeInsets.only(bottom: 80.0 + 10.0, left: 10.0, right: 10.0),
          duration: Duration(seconds: 3),
        ),
      );
      return;
    }

    try {
      final myPrivKey = await _cryptoService.getPrivateKey();
      if (myPrivKey == null) {
        throw Exception('Не найден приватный ключ.');
      }
      final sharedSecret = await _cryptoService.deriveSharedSecret(
        myPrivKey,
        targetContact.publicKey!,
      );
      final encryptedContent = await _cryptoService.encryptMessage(
        forwardText,
        sharedSecret,
      );
      final previewText = forwardText.length > 50
          ? forwardText.substring(0, 50)
          : forwardText;
      final encryptedContent50 = await _cryptoService.encryptMessage(
        previewText,
        sharedSecret,
      );

      final tempId = DateTime.now().microsecondsSinceEpoch;
      final localMessage = MessageModel(
        tempId: tempId,
        text: forwardText.isNotEmpty ? forwardText : "[Фото]",
        isMe: true,
        senderId: myId,
        receiverId: targetContact.id,
        createdAt: DateTime.now(),
        status: MessageStatus.sending,
        localFileBytes: _pendingImageBytes,
      );
      if (_currentContact.id == targetContact.id) {
        setState(() {
          messages.add(localMessage);
          _pendingImageBytes = null;
        });
      }

      final ok = Provider.of<SocketService>(context, listen: false)
          .sendMessage({
            'type': 'private_message',
            'receiver_id': targetContact.id,
            'message_type': 'text',
            'content': encryptedContent,
            'content50': encryptedContent50,
            'temp_id': tempId,
          });

      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            ok
                ? 'Сообщение переслано контакту ${targetContact.name}.'
                : 'Не удалось переслать сообщение.',
          ),
          behavior: SnackBarBehavior.floating, // Обязательно для margin
          margin: EdgeInsets.only(
            bottom: 80.0 + 10.0, // 20px + стандартный отступ (по желанию)
            left: 10.0,
            right: 10.0,
          ),
          duration: Duration(seconds: 3),
        ),
      );

      setState(() {
        final idx = messages.indexWhere((m) => m.tempId == tempId);
        if (idx != -1) {
          messages[idx] = messages[idx].copyWith(
            status: ok ? MessageStatus.sent : MessageStatus.failed,
          );
        }
        _replyTo = null;
      });
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => ChatScreen(contact: targetContact),
        ),
      );
    } catch (e) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Ошибка пересылки: $e'),
          behavior: SnackBarBehavior.floating,
          margin: EdgeInsets.only(bottom: 80.0 + 10.0, left: 10.0, right: 10.0),
          duration: Duration(seconds: 5),
        ),
      );
    }
  }

  Widget _buildMessageInput() {
    return SafeArea(
      // Добавляем SafeArea здесь
      child: Container(
        color: Theme.of(context).colorScheme.surfaceVariant,
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            if (_replyTo != null)
              Container(
                padding: const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical: 8,
                ),
                margin: const EdgeInsets.only(bottom: 8),
                decoration: BoxDecoration(
                  color: Theme.of(context).colorScheme.surface,
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Row(
                  children: [
                    const Icon(Icons.reply, size: 18),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        _replyTo!.text,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ),
                    IconButton(
                      icon: const Icon(Icons.close, size: 18),
                      onPressed: () => setState(() => _replyTo = null),
                    ),
                  ],
                ),
              ),
            if (_pendingImageBytes != null)
              Container(
                margin: const EdgeInsets.only(bottom: 8),
                padding: const EdgeInsets.all(8),
                decoration: BoxDecoration(
                  color: Theme.of(context).colorScheme.surface,
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Row(
                  children: [
                    Expanded(
                      child: ClipRRect(
                        borderRadius: BorderRadius.circular(10),
                        child: Image.memory(
                          _pendingImageBytes!,
                          fit: BoxFit.cover,
                          height: 120,
                        ),
                      ),
                    ),
                    const SizedBox(width: 8),
                    IconButton(
                      icon: const Icon(Icons.close),
                      onPressed: () =>
                          setState(() => _pendingImageBytes = null),
                    ),
                  ],
                ),
              ),
            Row(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: [
                IconButton(
                  icon: const Icon(Icons.photo),
                  onPressed: _pickImage,
                ),
                Expanded(
                  child: TextField(
                    controller: _controller,
                    focusNode: _inputFocusNode,
                    minLines: 1,
                    maxLines: 5,
                    textInputAction: TextInputAction.newline,
                    textCapitalization: TextCapitalization.sentences,
                    decoration: const InputDecoration(
                      hintText: "Напиши сообщение...",
                    ),
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.send),
                  onPressed: () {
                    _sendMessage();
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _pickImage() async {
    final ImagePicker _picker = ImagePicker();
    final XFile? image = await _picker.pickImage(
      source: ImageSource.gallery,
      maxWidth: 1280,
      maxHeight: 1280,
      imageQuality: 80,
    );
    if (image != null) {
      final Uint8List fileBytes = await image.readAsBytes();
      if (!mounted) return;
      setState(() {
        _pendingImageBytes = fileBytes;
      });
    }
  }

  Future<void> _sendMessage() async {
    _sendStopTypingStatus();
    final rawText = _controller.text.trim();
    final hasImage = _pendingImageBytes != null;

    // Если и текст пустой, и картинки нет — выходим
    if (rawText.isEmpty && !hasImage) return;

    // Блокируем UI на время загрузки
    _controller.clear();

    try {
      // 1. Подготовка ключей
      final myPrivKey = await _cryptoService.getPrivateKey();
      final sharedSecret = await _cryptoService.deriveSharedSecret(
        myPrivKey!,
        _currentContact.publicKey!,
      );

      String? fileId;
      String? encryptedFileKey;
      String encryptedContent;
      String encryptedContent50;

      // 2. Если есть изображение — сначала загружаем его
      if (hasImage) {
        final encryptionResult = await _cryptoService.encryptImage(
          _pendingImageBytes!,
          sharedSecret,
        );
        if (encryptionResult == null) {
          throw Exception("Ошибка шифрования медиа");
        }
        final encryptedFileData = encryptionResult.$1;
        final fileKeyForServer = encryptionResult.$2;

        fileId = await apiService.uploadMedia(encryptedFileData);

        if (fileId == null) throw Exception("Ошибка загрузки файла на сервер");

        encryptedFileKey = fileKeyForServer;
      }

      // 3. Шифруем текст сообщения (даже если там пусто, или есть подпись к фото)
      // Если текста нет, но есть фото, отправим пустую строку или "[Фото]"
      final String textToEncrypt = rawText.isNotEmpty
          ? rawText
          : (hasImage ? "" : "");

      encryptedContent = await _cryptoService.encryptMessage(
        textToEncrypt,
        sharedSecret,
      );

      String previewText = rawText.isNotEmpty ? rawText : "[Фото]";
      if (previewText.length > 50) previewText = previewText.substring(0, 50);
      encryptedContent50 = await _cryptoService.encryptMessage(
        previewText,
        sharedSecret,
      );

      // 4. Создаем локальную модель для мгновенного отображения
      final tempId = DateTime.now().microsecondsSinceEpoch;
      final localMessage = MessageModel(
        tempId: tempId,
        text: rawText.isNotEmpty ? rawText : "[Фото]",
        isMe: true,
        senderId: myId,
        receiverId: _currentContact.id,
        createdAt: DateTime.now(),
        status: MessageStatus.sending,
        localFileBytes: _pendingImageBytes,
        replyToId: _replyTo?.id,
        replyToText: _replyTo?.text,
      );

      setState(() {
        messages.add(localMessage);
        _pendingImageBytes = null; // Очищаем черновик
      });

      // 5. Формируем финальный payload для сокета
      final payload = {
        "type": "private_message",
        "receiver_id": _currentContact.id,
        "message_type": hasImage ? "image" : "text",
        "content": encryptedContent, // Шифрованный текст (подпись)
        "content50": encryptedContent50, // Шифрованное превью
        "temp_id": tempId,
        if (hasImage) ...{
          "file_id": fileId,
          "encrypted_key": encryptedFileKey, // Зашифрованный AES-ключ файла
        },
        if (_replyTo?.id != null) ...{
          "reply_to_id": _replyTo!.id,
          "reply_to_text": _replyTo!.text,
        },
      };

      // 6. Отправка через сокет
      final ok = Provider.of<SocketService>(
        context,
        listen: false,
      ).sendMessage(payload);

      // Обновляем статус
      setState(() {
        final idx = messages.indexWhere((m) => m.tempId == tempId);
        if (idx != -1) {
          messages[idx] = messages[idx].copyWith(
            status: ok ? MessageStatus.sent : MessageStatus.failed,
          );
        }
        _replyTo = null;
      });
    } catch (e) {
      // В случае ошибки возвращаем текст в контроллер
      _controller.text = rawText;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text("Ошибка отправки: $e"),
          behavior: SnackBarBehavior.floating,
          margin: EdgeInsets.only(bottom: 80.0 + 10.0, left: 10.0, right: 10.0),
          duration: Duration(seconds: 5),
        ),
      );
    }
  }

  void _handleIncomingMessage(Map<String, dynamic> data) async {
    DateTime now = DateTime.now();

    Duration offset = now.timeZoneOffset;
    // ACK от сервера: сообщение сохранено и получило server_id
    if (data['type'] == 'message_sent') {
      final tempId = int.tryParse(data['temp_id']?.toString() ?? '');
      final serverId = int.tryParse(data['server_id']?.toString() ?? '');
      var ts = DateTime.tryParse(
        data['timestamp']?.toString() ?? '',
      )?.add(offset);

      if (tempId == null) return;

      if (!mounted) return;
      setState(() {
        final idx = messages.indexWhere((m) => m.tempId == tempId);
        if (idx == -1) return;
        messages[idx] = messages[idx].copyWith(
          id: serverId ?? messages[idx].id,
          createdAt: ts ?? messages[idx].createdAt,
          status: MessageStatus.sent,
        );
      });
      return;
    }

    // Backward compatibility: старый ack мог приходить как message_delivered с temp_id/server_id
    if (data['type'] == 'message_delivered' && data.containsKey('temp_id')) {
      final tempId = int.tryParse(data['temp_id']?.toString() ?? '');
      final serverId = int.tryParse(data['server_id']?.toString() ?? '');
      var ts = DateTime.tryParse(
        data['timestamp']?.toString() ?? '',
      )?.add(offset);

      if (tempId == null) return;

      if (!mounted) return;
      setState(() {
        final idx = messages.indexWhere((m) => m.tempId == tempId);
        if (idx == -1) return;
        messages[idx] = messages[idx].copyWith(
          id: serverId ?? messages[idx].id,
          createdAt: ts ?? messages[idx].createdAt,
          status: MessageStatus.sent,
        );
      });
      return;
    }

    // Доставка онлайн (получатель был в сети)
    if (data['type'] == 'message_delivered') {
      final messageId = int.tryParse(data['message_id']?.toString() ?? '');
      var ts = DateTime.tryParse(
        data['timestamp']?.toString() ?? '',
      )?.add(offset);
      if (messageId == null) return;

      if (!mounted) return;
      setState(() {
        for (int i = 0; i < messages.length; i++) {
          if (messages[i].id == messageId) {
            messages[i] = messages[i].copyWith(status: MessageStatus.delivered);
          }
        }
      });

      if (ts != null) {
        try {
          await _localDbService.updateDeliveredAt(messageId, ts);
        } catch (_) {}
      }
      return;
    }

    if (data['type'] == 'message_edited') {
      final messageId = int.tryParse(data['message_id']?.toString() ?? '');
      var ts = DateTime.tryParse(
        data['edited_at']?.toString() ?? '',
      )?.add(offset);
      if (messageId == null) return;

      final myPrivKey = await _cryptoService.getPrivateKey();
      if (myPrivKey == null) return;
      final sharedSecret = await _cryptoService.deriveSharedSecret(
        myPrivKey,
        _currentContact.publicKey!,
      );
      final decryptedText = await _cryptoService.decryptMessage(
        data['content'],
        sharedSecret,
      );

      if (!mounted) return;
      setState(() {
        messages = messages.map((m) {
          if (m.id != null && m.id == messageId) {
            return m.copyWith(text: decryptedText, editedAt: ts);
          }
          return m;
        }).toList();
      });

      try {
        await _localDbService.updateMessageContent(
          messageId,
          data['content'].toString(),
          ts,
        );
      } catch (_) {}
      return;
    }

    if (data['type'] == 'message_deleted') {
      final messageId = int.tryParse(data['message_id']?.toString() ?? '');
      if (messageId == null) return;
      if (!mounted) return;
      setState(() {
        messages.removeWhere((m) => m.id != null && m.id == messageId);
      });
      try {
        await _localDbService.deleteMessage(messageId);
      } catch (_) {}
      return;
    }

    if (data['type'] == 'message_read') {
      final messageId = int.tryParse(data['message_id'].toString());
      if (messageId == null) return;
      var ts = DateTime.tryParse(
        data['timestamp']?.toString() ?? '',
      )?.add(offset);

      if (!mounted) return;
      setState(() {
        for (int i = 0; i < messages.length; i++) {
          if (messages[i].id == messageId) {
            messages[i] = messages[i].copyWith(status: MessageStatus.read);
          }
        }
      });

      if (ts != null) {
        try {
          await _localDbService.updateReadAt(messageId, ts);
        } catch (_) {}
      }
      return;
    }

    if (data['type'] == 'private_message') {
      setState(() {
        _typingTimer?.cancel();
        _isTyping = false;
      });
      final senderId = int.tryParse(data['sender_id']?.toString() ?? '');
      final receiverId = int.tryParse(
        (data['receiver_id'] ?? data['recipient_id'])?.toString() ?? '',
      );
      if (senderId == null || receiverId == null) {
        print(
          'Invalid private_message ids: sender_id=${data['sender_id']} receiver_id=${data['receiver_id'] ?? data['recipient_id']}',
        );
        return;
      }

      // 1. Проверяем, что сообщение именно от того, с кем мы сейчас общаемся
      final isFromPartnerToMe =
          senderId == widget.contact.id && receiverId == myId;
      if (isFromPartnerToMe) {
        try {
          final myPrivKey = await _cryptoService.getPrivateKey();

          // 2. Вычисляем общий секрет для расшифровки
          final sharedSecret = await _cryptoService.deriveSharedSecret(
            myPrivKey!,
            widget.contact.publicKey!,
          );

          // 3. Расшифровываем контент
          final decryptedText = await _cryptoService.decryptMessage(
            data['content'],
            sharedSecret,
          );

          // 4. Добавляем в список и обновляем экран
          await LocalDbService().saveMessages([data]);
          if (!mounted) return;

          final serverMessageId = int.tryParse(data['id']?.toString() ?? '');
          if (serverMessageId != null &&
              !_sentReadReceipts.contains(serverMessageId)) {
            Provider.of<SocketService>(
              context,
              listen: false,
            ).sendReadReceipt(serverMessageId);
            _sentReadReceipts.add(serverMessageId);
          }

          setState(() {
            messages.add(
              MessageModel(
                id: int.tryParse(data['id']?.toString() ?? ''),
                text: decryptedText,
                isMe: false,
                senderId: senderId,
                receiverId: myId,
                createdAt: DateTime.parse(data['timestamp']).add(offset),
                status: MessageStatus.delivered,
                replyToId: data['reply_to_id'] == null
                    ? null
                    : int.tryParse(data['reply_to_id'].toString()),
                replyToText: data['reply_to_text'] != null
                    ? data['reply_to_text'].toString()
                    : null,
              ),
            );
          });
        } catch (e) {
          print("Ошибка расшифровки входящего сообщения: $e");
        }
      } else {
        print(
          "Сообщение от другого пользователя (ID: $senderId), игнорируем в этом чате",
        );
      }
    }
    if (data['type'] == 'user_online') {
      final userId = int.tryParse(data['user_id']?.toString() ?? '');
      if (userId == widget.contact.id) {
        setState(() => _isOnline = true);
      }
    }
    if (data['type'] == 'user_offline') {
      final userId = int.tryParse(data['user_id']?.toString() ?? '');
      if (userId == widget.contact.id) {
        setState(() {
          _isOnline = false;
          _lastOnline = DateTime.now();
        });

        _loadOnlineStatus();
      }
    }

    if (data['type'] == 'typing' && data['sender_id'] == _currentContact.id) {
      if (mounted) {
        setState(() => _isTyping = true);

        _typingTimer?.cancel();
        _typingTimer = Timer(const Duration(seconds: 4), () {
          if (mounted) setState(() => _isTyping = false);
        });
      }
    }
    if (data['type'] == 'stop_typing' &&
        data['sender_id'] == _currentContact.id) {
      if (mounted) {
        setState(() => _isTyping = false);

        _typingTimer?.cancel();
      }
    }
  }

  Future<void> _loadHistory() async {
    DateTime now = DateTime.now();

    Duration offset = now.timeZoneOffset;
    initialMessage = null; // Сбрасываем данные уведомления при загрузке ключа
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove(_notificationLaunchKey);
    try {
      final myPrivKey = await _cryptoService.getPrivateKey();
      final sharedSecret = await _cryptoService.deriveSharedSecret(
        myPrivKey!,
        widget.contact.publicKey!,
      );
      final cached = await _localDbService.getChatHistory(
        widget.contact.id,
        myId,
      );

      try {
        List<MessageModel> loadedLocalMessages = [];
        for (var msg in cached) {
          final decrypted = await _cryptoService.decryptMessage(
            msg['content'],
            sharedSecret,
          );

          final deliveredAt = msg['delivered_at'] == null
              ? null
              : DateTime.tryParse(msg['delivered_at'].toString())?.add(offset);
          final readAt = msg['read_at'] == null
              ? null
              : DateTime.tryParse(msg['read_at'].toString())?.add(offset);

          MessageStatus status = (msg['sender_id'] == myId)
              ? MessageStatus.sent
              : MessageStatus.delivered;
          if (msg['sender_id'] == myId) {
            if (readAt != null) {
              status = MessageStatus.read;
            } else if (deliveredAt != null) {
              status = MessageStatus.delivered;
            }
          }

          loadedLocalMessages.add(
            MessageModel(
              id: int.tryParse(msg['id']?.toString() ?? ''),
              text: decrypted,
              isMe: msg['sender_id'] == myId,
              senderId: msg['sender_id'],
              receiverId: msg['receiver_id'],
              createdAt: DateTime.parse(msg['timestamp']).add(offset),
              status: status,
              replyToId: msg['reply_to_id'] == null
                  ? null
                  : int.tryParse(msg['reply_to_id'].toString()),
              replyToText: msg['reply_to_text'] != null
                  ? msg['reply_to_text'].toString()
                  : null,
              editedAt: msg['edited_at'] != null
                  ? DateTime.tryParse(msg['edited_at'].toString())?.add(offset)
                  : null,
            ),
          );
        }
        if (cached.isNotEmpty) {
          if (!mounted) return;
          setState(() {
            messages = loadedLocalMessages;
            _isKeyLoading = false;
          });
        }
      } catch (e) {
        print(e);
      }

      final history = await apiService.getChatHistory(widget.contact.id);
      print(history);
      final alreadyReadIncomingMessageIds = <int>{};
      List<MessageModel> loadedMessages = [];
      for (var msg in history) {
        final msgId = int.tryParse(msg['id']?.toString() ?? '');
        if (msgId != null &&
            msg['sender_id'] != myId &&
            msg['read_at'] != null) {
          alreadyReadIncomingMessageIds.add(msgId);
        }

        final decrypted = await _cryptoService.decryptMessage(
          msg['content'],
          sharedSecret,
        );

        final deliveredAt = msg['delivered_at'] == null
            ? null
            : DateTime.tryParse(msg['delivered_at'].toString())?.add(offset);
        final readAt = msg['read_at'] == null
            ? null
            : DateTime.tryParse(msg['read_at'].toString())?.add(offset);

        MessageStatus status = (msg['sender_id'] == myId)
            ? MessageStatus.sent
            : MessageStatus.delivered;
        if (msg['sender_id'] == myId) {
          if (readAt != null) {
            status = MessageStatus.read;
          } else if (deliveredAt != null) {
            status = MessageStatus.delivered;
          }
        }

        loadedMessages.insert(
          0,
          MessageModel(
            id: int.tryParse(msg['id']?.toString() ?? ''),
            text: decrypted,
            isMe: msg['sender_id'] == myId,
            senderId: msg['sender_id'],
            receiverId: msg['receiver_id'],
            createdAt: DateTime.parse(msg['timestamp']).add(offset),
            status: status,
            replyToId: msg['reply_to_id'] == null
                ? null
                : int.tryParse(msg['reply_to_id'].toString()),
            replyToText: msg['reply_to_text'] != null
                ? msg['reply_to_text'].toString()
                : null,
            editedAt: msg['edited_at'] != null
                ? DateTime.tryParse(msg['edited_at'].toString())?.add(offset)
                : null,
          ),
        );
      }
      try {
        await _localDbService.deleteChatHistory(widget.contact.id, myId);
        await _localDbService.saveMessages(history);
      } catch (e) {
        print("Ошибка сохранения истории в локальную базу: $e");
      }

      if (!mounted) return;
      setState(() {
        messages = loadedMessages;
        _isKeyLoading = false;
      });

      // Отправляем read_receipt для сообщений собеседника, которые уже на экране.
      for (final m in loadedMessages) {
        if (m.isMe) continue;
        final id = m.id;
        if (id == null) continue;
        if (alreadyReadIncomingMessageIds.contains(id)) continue;
        if (_sentReadReceipts.contains(id)) continue;
        Provider.of<SocketService>(context, listen: false).sendReadReceipt(id);
        _sentReadReceipts.add(id);
      }
    } catch (e) {
      print("Ошибка загрузки истории: $e");
      if (!mounted) return;
      setState(() => _isKeyLoading = false);
    }
  }
}

class TypingIndicator extends StatefulWidget {
  const TypingIndicator({super.key});

  @override
  State<TypingIndicator> createState() => _TypingIndicatorState();
}

class _TypingIndicatorState extends State<TypingIndicator>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 600),
    )..repeat(reverse: true); // Анимация идет туда-сюда
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  Widget _buildDot(int index) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        // Рассчитываем смещение: только отрицательные значения (вверх)
        double delay = index * 0.5; // Увеличили задержку для плавности
        double shift = sin((_controller.value * 2 * pi) + delay);

        // Используем clamp или abs, чтобы точка не уходила ниже базовой линии
        double yOffset = (shift < 0 ? shift : 0) * 4;

        return SizedBox(
          width: 4, // Фиксированная зона для одной точки
          height: 5, // Фиксированная высота зоны анимации
          child: Align(
            alignment: Alignment.bottomCenter, // Точка всегда прижата к низу
            child: Container(
              width: 2,
              height: 2,
              decoration: const BoxDecoration(
                color: Colors.greenAccent,
                shape: BoxShape.circle,
              ),
              transform: Matrix4.translationValues(0, yOffset, 0),
            ),
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 12,
      child: Row(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: List.generate(3, (index) => _buildDot(index)),
      ),
    );
  }
}
