import 'dart:collection';

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/paged_response.dart';
import '../friendica_client/paging_data.dart';
import '../globals.dart';
import '../models/auth/profile.dart';
import '../models/connection.dart';
import '../utils/active_profile_selector.dart';
import 'connections_manager.dart';

class BlocksManager extends ChangeNotifier {
  static final _logger = Logger('$BlocksManager');
  final Profile profile;
  final _blocks = <Connection>[];
  final _pages = <PagedResponse>[];

  var mayHaveMore = true;
  var initialized = false;

  BlocksManager(this.profile);

  void clear() {
    _blocks.clear();
    _pages.clear();
    mayHaveMore = true;
  }

  List<Connection> getBlocks() {
    if (!initialized) {
      updateBlocks(nextOnly: false);
    }

    return UnmodifiableListView(_blocks);
  }

  Future<void> blockConnection(Connection connection) async {
    _logger
        .finest('Attempting to block ${connection.name}: ${connection.status}');
    await RelationshipsClient(profile)
        .blockConnection(connection)
        .withResult((blockedUser) {
      getIt<ActiveProfileSelector<ConnectionsManager>>()
          .getForProfile(profile)
          .withResult(
            (cm) => cm.upsertConnection(blockedUser),
          );
    }).match(
      onSuccess: (blockedUser) {
        _logger.fine(
            'Successfully blocked ${blockedUser.name}: ${blockedUser.status}');
        final existingIndex = _blocks.indexOf(connection);
        if (existingIndex < 0) {
          _blocks.add(blockedUser);
          _sortBlocks();
        } else {
          _blocks.removeAt(existingIndex);
          _blocks.insert(existingIndex, blockedUser);
        }

        notifyListeners();
      },
      onError: (error) {
        _logger.severe('Error blocking ${connection.name}: $error');
      },
    );
  }

  Future<void> unblockConnection(Connection connection) async {
    _logger
        .fine('Attempting to unblock ${connection.name}: ${connection.status}');
    await RelationshipsClient(profile)
        .unblockConnection(connection)
        .withResult((blockedUser) {
      getIt<ActiveProfileSelector<ConnectionsManager>>()
          .getForProfile(profile)
          .withResult(
            (cm) => cm.upsertConnection(blockedUser),
          );
    }).match(
      onSuccess: (unblockedUser) {
        _logger.fine(
            'Successfully unblocked ${unblockedUser.name}: ${unblockedUser.status}');
        final existingIndex = _blocks.indexOf(connection);
        if (existingIndex >= 0) {
          _blocks.removeAt(existingIndex);
          _sortBlocks();
        }
        notifyListeners();
      },
      onError: (error) {
        _logger.severe('Error unblocking ${connection.name}: $error');
      },
    );
  }

  Future<void> updateBlock(Connection connection) async {
    final id = int.parse(connection.id);
    final page = PagingData(minId: id - 1, maxId: id + 1);
    await RelationshipsClient(profile).getBlocks(page).withResult((blocks) {
      final conBlock = blocks.data.where((b) => b.id == connection.id).toList();
      if (conBlock.isEmpty) {
        _blocks.remove(connection);
      } else {
        _blocks.add(conBlock.first);
        getIt<ActiveProfileSelector<ConnectionsManager>>()
            .getForProfile(profile)
            .withResult((cm) => cm.upsertConnection(conBlock.first));
      }

      notifyListeners();
    });
  }

  Future<void> updateBlocks({required bool nextOnly}) async {
    if (nextOnly) {
      clear();
    }
    final client = RelationshipsClient(profile);
    final bootstrapping = _pages.isEmpty;

    var page = bootstrapping ? PagingData() : _pages.last.next;

    while (page != null) {
      page = await client
          .getBlocks(page)
          .withResult((result) {
            _blocks.addAll(result.data);
            _pages.add(result);
          })
          .withError(
            (error) => _logger.severe('Error getting blocks data: $error'),
          )
          .fold<PagingData?>(
            onSuccess: (result) => result.next,
            onError: (error) => null,
          );

      if (nextOnly) {
        break;
      }
    }

    getIt<ActiveProfileSelector<ConnectionsManager>>()
        .getForProfile(profile)
        .withResult((cm) => cm.upsertAllConnections(_blocks));

    _sortBlocks();
    notifyListeners();
  }

  void _sortBlocks() {
    _blocks.sort(
      (b1, b2) => b1.name.toLowerCase().compareTo(
            b2.name.toLowerCase(),
          ),
    );
  }
}
