import 'dart:async';

import 'package:comunic/helpers/conversations_helper.dart';
import 'package:comunic/helpers/events_helper.dart';
import 'package:comunic/helpers/server_config_helper.dart';
import 'package:comunic/helpers/users_helper.dart';
import 'package:comunic/lists/conversation_messages_list.dart';
import 'package:comunic/lists/users_list.dart';
import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/config.dart';
import 'package:comunic/models/conversation.dart';
import 'package:comunic/models/conversation_message.dart';
import 'package:comunic/models/new_conversation_message.dart';
import 'package:comunic/ui/dialogs/pick_file_dialog.dart';
import 'package:comunic/ui/routes/main_route/main_route.dart';
import 'package:comunic/ui/tiles/conversation_message_tile.dart';
import 'package:comunic/ui/tiles/server_conversation_message_tile.dart';
import 'package:comunic/ui/widgets/account_image_widget.dart';
import 'package:comunic/ui/widgets/safe_state.dart';
import 'package:comunic/ui/widgets/scroll_watcher.dart';
import 'package:comunic/ui/widgets/user_writing_in_conv_notifier.dart';
import 'package:comunic/utils/account_utils.dart';
import 'package:comunic/utils/date_utils.dart';
import 'package:comunic/utils/files_utils.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/list_utils.dart';
import 'package:comunic/utils/log_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:comunic/utils/video_utils.dart';
import 'package:dio/dio.dart';
import 'package:emoji_picker/emoji_picker.dart';
import 'package:flutter/material.dart';
import 'package:mime/mime.dart';

/// Conversation screen
///
/// @author Pierre HUBERT

enum ErrorLevel { NONE, MINOR, MAJOR }
enum _OlderMessagesLevel { NONE, LOADING, NO_MORE_AVAILABLE }

class ConversationScreen extends StatefulWidget {
  final int conversationID;

  const ConversationScreen({Key key, this.conversationID})
      : assert(conversationID != null),
        super(key: key);

  @override
  State<StatefulWidget> createState() => _ConversationScreenState();
}

class _ConversationScreenState extends SafeState<ConversationScreen> {
  //Helpers
  final ConversationsHelper _conversationsHelper = ConversationsHelper();
  final UsersHelper _usersHelper = UsersHelper();

  // Class members
  Conversation _conversation;
  ConversationMessagesList _messages;
  UsersList _usersInfo = UsersList();
  ErrorLevel _error = ErrorLevel.NONE;
  final _textFieldFocus = FocusNode();

  bool _showEmojiPicker = false;

  bool _isSendingMessage = false;
  TextEditingController _textController = TextEditingController();
  ScrollWatcher _scrollController;
  _OlderMessagesLevel _loadingOlderMessages = _OlderMessagesLevel.NONE;

  int _lastWritingEventSent = 0;

  CancelToken _sendCancel;
  double _sendProgress;

  String get textMessage => _textController.text;

  bool get _isMessageValid =>
      textMessage.length >=
          ServerConfigurationHelper.config.conversationsPolicy.minMessageLen &&
      textMessage.length <
          ServerConfigurationHelper.config.conversationsPolicy.maxMessageLen;

  showKeyboard() => _textFieldFocus.requestFocus();

  hideKeyboard() => _textFieldFocus.unfocus();

  hideEmojiContainer() => setState(() => _showEmojiPicker = false);

  showEmojiContainer() => setState(() => _showEmojiPicker = true);

  // Colors definition
  Color get _senderColor =>
      _conversation.color ??
      config().defaultConversationColor ??
      /*(darkTheme() ? Color(0xff2b343b) :*/ Colors.blue.shade700; //);

  Color get _receiverColor =>
      darkTheme() ? Color(0xff3a3d40) : Colors.grey.shade600;

  Color get _greyColor => Color(0xff8f8f8f);

  Color get _gradientColorStart =>
      (_conversation.color ?? config().defaultConversationColor)
          ?.withOpacity(0.7) ??
      (darkTheme() ? Color(0xff00b6f3) : Colors.blue.shade300);

  Color get _gradientColorEnd =>
      _conversation.color ??
      config().defaultConversationColor ??
      (darkTheme() ? Color(0xff0184dc) : Colors.blueAccent.shade700);

  Color get _separatorColor =>
      darkTheme() ? Color(0xff272c35) : Color(0xffBEBEBE);

  LinearGradient get _fabGradient => LinearGradient(
        colors: [_gradientColorStart, _gradientColorEnd],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      );

  LinearGradient get _disabledGradient => LinearGradient(
        colors: [_greyColor, _receiverColor],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      );

  @override
  void initState() {
    super.initState();
    _init();
  }

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

  void _setError(ErrorLevel err) => setState(() => _error = err);

  void _setSending(bool sending) => setState(() => _isSendingMessage = sending);

  void _setLoadingOlderMessagesState(_OlderMessagesLevel state) => setState(() {
        _loadingOlderMessages = state;
      });

  /// Method called when an error occurred while loading messages
  void _errorLoading() =>
      _setError(_messages == null ? ErrorLevel.MAJOR : ErrorLevel.MINOR);

  /// Load the first conversations
  Future<void> _init() async {
    _scrollController = ScrollWatcher(onReachBottom: _loadOlderMessages);

    _conversation =
        await ConversationsHelper().getSingle(widget.conversationID);

    // Fetch latest messages
    await _loadMessages(false);
    await _loadMessages(true);

    await _conversationsHelper
        .registerConversationEvents(widget.conversationID);

    this.listen<NewConversationMessageEvent>((ev) async {
      if (ev.msg.convID == widget.conversationID) {
        try {
          await _conversationsHelper.saveMessage(ev.msg);
          await _applyNewMessages(ConversationMessagesList()..add(ev.msg));
        } catch (e, s) {
          print("Failed to show new message! $e => $s");
          _errorLoading();
        }
      }
    });

    this.listen<UpdatedConversationMessageEvent>((ev) async {
      if (ev.msg.convID == widget.conversationID) {
        await _conversationsHelper.saveMessage(ev.msg);
        setState(() => _messages.replace(ev.msg));
      }
    });

    this.listen<DeletedConversationMessageEvent>((ev) async {
      if (ev.msg.convID == widget.conversationID) {
        await _conversationsHelper.removeMessage(ev.msg);
        setState(() => _messages.removeMsg(ev.msg.id));
      }
    });

    this.listen<RemovedUserFromConversationEvent>((ev) {
      if (ev.userID == userID() && ev.convID == widget.conversationID) {
        setState(() => _error = ErrorLevel.MAJOR);
      }
    });

    this.listen<DeletedConversationEvent>((ev) {
      if (ev.convID == widget.conversationID) {
        setState(() => _error = ErrorLevel.MAJOR);
      }
    });
  }

  /// Free resources when this conversation widget is no longer required
  void _deallocate() {
    _conversationsHelper.unregisterConversationEvents(widget.conversationID);
  }

  /// Load a list of messages
  Future<void> _loadMessages(bool online) async {
    if (!mounted) return;

    try {
      //First, get the messages
      final messages = await _conversationsHelper.getNewMessages(
        conversationID: widget.conversationID,
        lastMessageID: _messages == null ? 0 : _messages.lastMessageID,
        online: online,
      );

      // In case we are offline and we did not get any message we do not do
      // anything (we wait for the online request)
      if (messages.length == 0 && !online) return;

      await _applyNewMessages(messages);
    } catch (e, s) {
      debugPrint("Failed to load messages! $e => $s", wrapWidth: 4096);
      _errorLoading();
    }
  }

  /// Get older messages
  Future<void> _loadOlderMessages() async {
    if (_loadingOlderMessages != _OlderMessagesLevel.NONE ||
        _messages == null ||
        _messages.length == 0) return;
    try {
      // Let's start to load older messages
      _setLoadingOlderMessagesState(_OlderMessagesLevel.LOADING);

      final messages = await _conversationsHelper.getOlderMessages(
          conversationID: widget.conversationID,
          oldestMessagesID: _messages.firstMessageID);

      // Mark as not loading anymore
      _setLoadingOlderMessagesState(_OlderMessagesLevel.NONE);

      // Check if there is no more unread messages
      if (messages.length == 0) {
        _setLoadingOlderMessagesState(_OlderMessagesLevel.NO_MORE_AVAILABLE);
        return;
      }

      // Apply the messages
      _applyNewMessages(messages);
    } catch (e, s) {
      print("Failed to load older messages! $e => $s");
      _errorLoading();
    }
  }

  /// Apply new messages [messages] must not be null
  ///
  /// Throws in case of failure
  Future<void> _applyNewMessages(ConversationMessagesList messages) async {
    // We ignore new messages once the area is no longer visible
    if (!this.mounted) return;

    //Then get information about users
    final usersToGet =
        findMissingFromSet(_usersInfo.usersID.toSet(), messages.getUsersID());

    final users = await _usersHelper.getList(usersToGet);

    // Save the new list of messages
    setState(() {
      _usersInfo.addAll(users);
      if (_messages == null)
        _messages = messages;
      else
        _messages.addAll(messages);

      //Reverse the order of the messages (if required)
      if (messages.length > 0) {
        _messages.sort();
        final reverse = _messages.reversed;
        _messages = ConversationMessagesList();
        _messages.addAll(reverse);
      }
    });

    // Remove previous errors
    _setError(ErrorLevel.NONE);
  }

  /// Send a file message
  Future<void> _sendFileMessage() async {
    try {
      final file = await showPickFileDialog(
        context: context,
        maxFileSize: srvConfig.conversationsPolicy.filesMaxSize,
        allowedMimeTypes: srvConfig.conversationsPolicy.allowedFilesType,
        imageMaxWidth: srvConfig.conversationsPolicy.maxMessageImageWidth,
        imageMaxHeight: srvConfig.conversationsPolicy.maxMessageImageHeight,
      );

      if (file == null) return;

      BytesFile thumbnail;

      if (isVideo(lookupMimeType(file.filename)))
        thumbnail = await generateVideoThumbnail(
          videoFile: file,
          maxWidth: srvConfig.conversationsPolicy.maxThumbnailWidth,
        );

      _sendCancel = CancelToken();
      final progressCb =
          (count, total) => setState(() => _sendProgress = count / total);
      final res = await ConversationsHelper().sendMessage(
        NewConversationMessage(
            conversationID: widget.conversationID,
            message: null,
            file: file,
            thumbnail: thumbnail),
        sendProgress: progressCb,
        cancelToken: _sendCancel,
      );
      assert(res == SendMessageResult.SUCCESS);
    } catch (e, s) {
      logError(e, s);
      showSimpleSnack(context, tr("Failed to send a file!"));
    }

    setState(() {
      _sendCancel = null;
      _sendProgress = null;
    });
  }

  /// Send a new text message
  Future<void> _submitTextMessage() async {
    if (await _submitMessage(NewConversationMessage(
          conversationID: widget.conversationID,
          message: textMessage,
        )) ==
        SendMessageResult.SUCCESS) _clearSendMessageForm();
  }

  /// Submit a new message
  Future<SendMessageResult> _submitMessage(
      NewConversationMessage message) async {
    //Send the message
    _setSending(true);
    final result = await _conversationsHelper.sendMessage(message);
    _setSending(false);

    //Check the result of the operation
    if (result != SendMessageResult.SUCCESS)
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            result == SendMessageResult.MESSAGE_REJECTED
                ? tr("Message rejected by the server!")
                : tr("Could not send message!"),
          ),
          duration: Duration(milliseconds: 500),
        ),
      );

    return result;
  }

  /// Clear send message form
  void _clearSendMessageForm() {
    setState(() => _textController = TextEditingController());
  }

  /// Error handling
  Widget _buildError() {
    return buildErrorCard(tr("Could not load the list of messages!"));
  }

  /// Widget shown when loading older messages
  Widget _buildLoadingOlderMessage() {
    return Container(
      padding: EdgeInsets.all(8.0),
      child: CircularProgressIndicator(),
    );
  }

  /// Notice shown when there is no messages to show
  Widget _buildNoMessagesNotice() {
    return Expanded(
      child: Center(
        child: Text(tr("There is no message yet in this conversation.")),
      ),
    );
  }

  /// Messages list
  Widget _buildMessagesList() {
    return Expanded(
        child: ListView.builder(
      controller: _scrollController,
      reverse: true,
      itemCount: _messages.length,
      itemBuilder: (c, i) => _buildMessageItem(i),
    ));
  }

  Widget _buildMessageItem(int msgIndex) {
    final msg = _messages[msgIndex];
    final nextMessage =
        msgIndex + 1 < _messages.length ? _messages[msgIndex + 1] : null;

    return Column(
      children: <Widget>[
        Container(
          child: !isSameDate(msg.date, nextMessage?.date)
              ? _buildDateWidget(msg.date)
              : null,
        ),
        msg.isServerMessage
            ? Container(
                alignment: Alignment.center,
                child: ServerConversationMessageTile(
                    message: msg.serverMessage, users: _usersInfo),
              )
            : Container(
                margin: EdgeInsets.symmetric(vertical: 5),
                alignment:
                    msg.isOwner ? Alignment.centerRight : Alignment.centerLeft,
                child: msg.isOwner
                    ? _buildSenderLayout(msg, nextMessage)
                    : _buildReceiverLayout(msg, nextMessage),
              ),
      ],
    );
  }

  Widget _buildSenderLayout(
      ConversationMessage message, ConversationMessage previousMessage) {
    final messageRadius = Radius.circular(10);

    return Container(
      margin: EdgeInsets.only(
          top: previousMessage?.isOwner == true ? 0 : 12, right: 5),
      constraints:
          BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.65),
      decoration: BoxDecoration(
        color: _senderColor,
        borderRadius: BorderRadius.only(
          topLeft: messageRadius,
          topRight: messageRadius,
          bottomLeft: messageRadius,
        ),
      ),
      child: Padding(
        padding: EdgeInsets.all(10),
        child: _buildMessage(message),
      ),
    );
  }

  Widget _buildReceiverLayout(
      ConversationMessage message, ConversationMessage previousMessage) {
    final messageRadius = Radius.circular(10);

    return Row(children: [
      SizedBox(width: 5),
      AccountImageWidget(
        user: _usersInfo.getUser(message.userID),
      ),
      SizedBox(width: 5),
      Container(
        margin: EdgeInsets.only(
            top: previousMessage == null ||
                    message.userID != previousMessage.userID
                ? 12
                : 0),
        constraints:
            BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.65),
        decoration: BoxDecoration(
          color: _receiverColor,
          borderRadius: BorderRadius.only(
            bottomRight: messageRadius,
            topRight: messageRadius,
            bottomLeft: messageRadius,
          ),
        ),
        child: Padding(
          padding: EdgeInsets.all(10),
          child: _buildMessage(message),
        ),
      ),
    ]);
  }

  Widget _buildMessage(ConversationMessage msg) => ConversationMessageTile(
        message: msg,
        user: _usersInfo.getUser(msg.userID),
        onRequestMessageStats: _requestMessageStats,
        onRequestMessageUpdate: _updateMessage,
        onRequestMessageDelete: _deleteMessage,
      );

  Widget _buildDateWidget(DateTime dt) => Center(
          child: Container(
        child: Text(
          formatDisplayDate(dt, time: false),
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        padding: EdgeInsets.only(top: 50, bottom: 5),
      ));

  /// Send new message form
  Widget _buildSendMessageForm() => Container(
        padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
        child: Row(
          children: <Widget>[
            GestureDetector(
              onTap: !_isSendingMessage ? _sendFileMessage : null,
              child: Container(
                padding: EdgeInsets.all(6),
                decoration: BoxDecoration(
                  gradient:
                      _isSendingMessage ? _disabledGradient : _fabGradient,
                  shape: BoxShape.circle,
                ),
                child: Icon(
                  Icons.add,
                  color: Colors.white,
                ),
              ),
            ),
            SizedBox(width: 5),
            Expanded(
              child: Stack(
                alignment: Alignment.centerRight,
                children: [
                  TextField(
                    enabled: !_isSendingMessage,
                    maxLines: 10,
                    minLines: 1,
                    controller: _textController,
                    focusNode: _textFieldFocus,
                    onTap: () => hideEmojiContainer(),
                    textInputAction: TextInputAction.send,
                    onSubmitted: (s) => _submitTextMessage(),
                    style: TextStyle(
                      color: darkTheme() ? Colors.white : Colors.black,
                    ),
                    onChanged: (s) {
                      _sendWritingEvent();
                      setState(() {});
                    },
                    decoration: InputDecoration(
                      hintText: tr("New message..."),
                      hintStyle: TextStyle(
                        color: _greyColor,
                      ),
                      border: OutlineInputBorder(
                          borderRadius: const BorderRadius.all(
                            const Radius.circular(50.0),
                          ),
                          borderSide: BorderSide.none),
                      contentPadding: EdgeInsets.fromLTRB(20, 8, 32, 8),
                      filled: true,
                      fillColor: _separatorColor,
                    ),
                  ),
                  IconButton(
                    splashColor: Colors.transparent,
                    highlightColor: Colors.transparent,
                    onPressed: () {
                      if (!_showEmojiPicker) {
                        // keyboard is visible
                        hideKeyboard();
                        Future.delayed(Duration(milliseconds: 100),
                            () => showEmojiContainer());
                      } else {
                        //keyboard is hidden
                        showKeyboard();
                        hideEmojiContainer();
                      }
                    },
                    icon: Icon(
                      Icons.face,
                      color: _showEmojiPicker
                          ? (_conversation.color ?? Colors.blue)
                          : null,
                    ),
                  ),
                ],
              ),
            ),
            SizedBox(width: 5),
            GestureDetector(
              onTap: _isMessageValid ? _submitTextMessage : null,
              child: Container(
                padding: EdgeInsets.all(8),
                decoration: BoxDecoration(
                  gradient: !_isMessageValid ? _disabledGradient : _fabGradient,
                  shape: BoxShape.circle,
                ),
                child: Icon(
                  Icons.send,
                  color: Colors.white,
                ),
              ),
            ),
          ],
        ),
      );

  Widget _buildEmojiContainer() => EmojiPicker(
        bgColor: _conversation.color ?? Colors.blue.shade900,
        indicatorColor: _conversation.color ?? Colors.blue.shade900,
        rows: 3,
        columns: 7,
        onEmojiSelected: (emoji, category) {
          _textController.text = _textController.text + emoji.emoji;
        },
        recommendKeywords: ["face", "happy", "party", "sad"],
        numRecommended: 50,
      );

  Widget _buildSendingWidget() => Container(
        height: 68,
        color: _senderColor,
        child: Row(
          children: <Widget>[
            Spacer(flex: 1),
            Flexible(
              child: LinearProgressIndicator(value: _sendProgress),
              flex: 5,
            ),
            Spacer(flex: 1),
            Text("${(_sendProgress * 100).toInt()}%"),
            Spacer(flex: 1),
            OutlinedButton(
              onPressed: () => _sendCancel.cancel(),
              child: Text(tr("Cancel").toUpperCase()),
            ),
            Spacer(flex: 1),
          ],
        ),
      );

  @override
  Widget build(BuildContext context) {
    if (_error == ErrorLevel.MAJOR) return _buildError();

    if (_messages == null) return buildCenteredProgressBar();

    return Column(
      children: <Widget>[
        Container(
          child: _error == ErrorLevel.MINOR ? _buildError() : null,
        ),
        Container(
          child: _loadingOlderMessages == _OlderMessagesLevel.LOADING
              ? _buildLoadingOlderMessage()
              : null,
        ),
        _messages.length == 0 ? _buildNoMessagesNotice() : _buildMessagesList(),
        UserWritingInConvNotifier(convID: _conversation.id),
        _sendCancel != null ? _buildSendingWidget() : _buildSendMessageForm(),
        _showEmojiPicker ? _buildEmojiContainer() : Container(),
      ],
    );
  }

  void _sendWritingEvent() async {
    try {
      if (textMessage.isEmpty) return;
      final t = time();

      if (t - _lastWritingEventSent <
          srvConfig.conversationsPolicy.writingEventInterval) return;

      _lastWritingEventSent = t;
      await ConversationsHelper.sendWritingEvent(_conversation.id);
    } catch (e, s) {
      logError(e, s);
    }
  }

  /// Request message statistics
  void _requestMessageStats(ConversationMessage message) async {
    MainController.of(context)
        .openConversationMessageStats(_conversation, message);
  }

  /// Request message content update
  Future<void> _updateMessage(ConversationMessage message) async {
    final newContent = await askUserString(
      context: context,
      title: tr("Update message"),
      message: tr("Please enter new message content:"),
      defaultValue: message.message.content,
      hint: tr("New message"),
      minLength:
          ServerConfigurationHelper.config.conversationsPolicy.minMessageLen,
      maxLength:
          ServerConfigurationHelper.config.conversationsPolicy.maxMessageLen,
    );

    if (newContent == null) return;

    if (!await _conversationsHelper.updateMessage(message.id, newContent)) {
      showSimpleSnack(context, tr("Could not update message content!"));
      return;
    }
  }

  /// Request message deletion
  Future<void> _deleteMessage(ConversationMessage message) async {
    final choice = await showDialog<bool>(
      context: context,
      builder: (c) => AlertDialog(
        title: Text(tr("Confirm deletion")),
        content: Text(
          tr("Do you really want to delete this message ? The operation can not be cancelled !"),
          textAlign: TextAlign.justify,
        ),
        actions: <Widget>[
          TextButton(
            child: Text(
              tr("Cancel").toUpperCase(),
            ),
            onPressed: () => Navigator.pop(c, false),
          ),
          TextButton(
            child: Text(
              tr("Confirm").toUpperCase(),
              style: TextStyle(color: Colors.red),
            ),
            onPressed: () => Navigator.pop(c, true),
          ),
        ],
      ),
    );

    if (choice == null || !choice) return;

    // Execute the request
    if (!await _conversationsHelper.deleteMessage(message.id))
      showSimpleSnack(context, tr("Could not delete conversation message!"));
  }
}