import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';

import '../friendica_client/friendica_client.dart';
import '../friendica_client/paging_data.dart';
import '../globals.dart';
import '../models/auth/oauth_credentials.dart';
import '../models/auth/profile.dart';
import '../models/connection.dart';
import '../models/direct_message.dart';
import '../models/direct_message_thread.dart';
import '../models/exec_error.dart';
import 'feature_version_checker.dart';

class DirectMessageService extends ChangeNotifier {
  static final _logger = Logger('$DirectMessageService');
  final _threads = <String, DirectMessageThread>{};
  final Profile profile;
  var _firstLoading = true;

  DirectMessageService(this.profile);

  void clear() {
    _threads.clear();
    _firstLoading = true;
    notifyListeners();
  }

  List<DirectMessageThread> getThreads({bool unreadyOnly = false}) {
    if (_threads.isEmpty && _firstLoading) {
      updateThreads();
      _firstLoading = false;
    }

    if (unreadyOnly) {
      return _threads.values.where((t) => !t.allSeen).toList();
    }

    return _threads.values.toList();
  }

  Result<DirectMessageThread, ExecError> getThreadByParentUri(String uri) {
    if (_threads.containsKey(uri)) {
      return Result.ok(_threads[uri]!);
    }

    return buildErrorResult(
        type: ErrorType.notFound, message: 'Thread ID not found: $uri');
  }

  Future<void> updateThreads() async {
    await DirectMessagingClient(profile).getDirectMessages(PagingData()).match(
      onSuccess: (update) {
        final newThreads = DirectMessageThread.createThreads(update);
        _threads.clear();
        for (final t in newThreads) {
          //TODO do merge operation
          _threads[t.parentUri] = t;
        }
        _logger.fine(
            'Updated ${update.length} direct messages, across ${newThreads.length} threads');
        notifyListeners();
      },
      onError: (error) {
        _logger.severe('Error getting direct messages: $error');
      },
    );

    notifyListeners();
  }

  FutureResult<DirectMessage, ExecError> newThread(
      Connection receiver, String text) async {
    if (profile.credentials is OAuthCredentials) {
      final result = getIt<FriendicaVersionChecker>()
          .canUseFeatureResult(RelaticaFeatures.directMessageCreation);
      if (result.isFailure) {
        return result.errorCast();
      }
    }

    final result = await DirectMessagingClient(profile).postDirectMessage(
      null,
      receiver.id,
      text,
    );
    result.match(onSuccess: (newMessage) {
      DirectMessageThread.createThreads([newMessage]).forEach((thread) {
        _threads[thread.parentUri] = thread;
      });
      notifyListeners();
    }, onError: (error) {
      _logger.severe('Error getting direct messages: $error');
    });

    return result.execErrorCast();
  }

  FutureResult<DirectMessage, ExecError> newReplyMessage(
      String threadId, DirectMessage original, String text) async {
    final thread = _threads[threadId];
    if (thread == null) {
      final error = 'Message is not for this thread: $threadId, $original';
      _logger.severe(error);
      return buildErrorResult(
        type: ErrorType.notFound,
        message: error,
      );
    }

    if (!thread.messages.contains(original)) {
      final error = 'Message is not for this thread: $threadId, $original';
      _logger.severe(error);
      return buildErrorResult(
        type: ErrorType.notFound,
        message: error,
      );
    }

    if (profile.credentials is OAuthCredentials) {
      final result = getIt<FriendicaVersionChecker>()
          .canUseFeatureResult(RelaticaFeatures.directMessageCreation);
      if (result.isFailure) {
        return result.errorCast();
      }
    }

    final result = await DirectMessagingClient(profile).postDirectMessage(
      original.id,
      original.senderId,
      text,
    );
    result.match(onSuccess: (newMessage) {
      thread.messages.add(newMessage);
      notifyListeners();
    }, onError: (error) {
      _logger.severe('Error getting direct messages: $error');
    });

    return result.execErrorCast();
  }

  Future<void> markMessageRead(String threadId, DirectMessage m) async {
    final thread = _threads[threadId];
    if (thread == null) {
      _logger.severe('Message is not for this thread: $threadId, $m');
      return;
    }
    final oldIndex = thread.messages.indexOf(m);
    if (oldIndex < 0) {
      _logger.severe('Message is not for this thread: $threadId, $m');
      return;
    }

    await DirectMessagingClient(profile).markDirectMessageRead(m).match(
      onSuccess: (update) {
        thread.messages.removeAt(oldIndex);
        thread.messages.insert(oldIndex, update);
        notifyListeners();
      },
      onError: (error) {
        _logger.severe('Error getting direct messages: $error');
      },
    );
  }
}
