import 'package:flutter/material.dart' hide Visibility;
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';

import '../data/interfaces/circles_repo_intf.dart';
import '../friendica_client/friendica_client.dart';
import '../models/TimelineIdentifiers.dart';
import '../models/auth/profile.dart';
import '../models/circle_data.dart';
import '../models/entry_tree_item.dart';
import '../models/exec_error.dart';
import '../models/image_entry.dart';
import '../models/media_attachment_uploads/new_entry_media_items.dart';
import '../models/timeline.dart';
import '../models/timeline_entry.dart';
import '../models/visibility.dart';
import 'entry_manager_service.dart';

enum TimelineRefreshType {
  refresh,
  loadOlder,
  loadNewer,
}

class TimelineManager extends ChangeNotifier {
  static final _logger = Logger('$TimelineManager');

  final ICirclesRepo circlesRepo;
  final EntryManagerService entryManagerService;
  var circlesNotInitialized = true;
  final Profile profile;

  final cachedTimelines = <TimelineIdentifiers, Timeline>{};

  TimelineManager(this.profile, this.circlesRepo, this.entryManagerService);

  void clear() {
    circlesNotInitialized = true;
    cachedTimelines.clear();
    entryManagerService.clear();
    circlesRepo.clear();
    notifyListeners();
  }

  Result<List<CircleData>, ExecError> getCircles() {
    if (circlesNotInitialized) {
      _refreshCircleData();
      circlesNotInitialized = false;
      return Result.ok([]);
    }

    return Result.ok(circlesRepo.getMyCircles());
  }

  Future<void> _refreshCircleData() async {
    _logger.finer('Refreshing member circle data ');
    await CirclesClient(profile).getCircles().match(
      onSuccess: (circles) {
        circlesRepo.addAllCircles(circles);
        notifyListeners();
      },
      onError: (error) {
        _logger.severe('Error getting list data: $error');
      },
    );
  }

  FutureResult<bool, ExecError> createNewStatus(
    String text, {
    String spoilerText = '',
    String inReplyToId = '',
    required NewEntryMediaItems newMediaItems,
    required List<ImageEntry> existingMediaItems,
    required Visibility visibility,
  }) async {
    final result = await entryManagerService.createNewStatus(
      text,
      spoilerText: spoilerText,
      inReplyToId: inReplyToId,
      mediaItems: newMediaItems,
      existingMediaItems: existingMediaItems,
      visibility: visibility,
    );
    if (result.isSuccess) {
      _logger.finest('Notifying listeners of new status created');
      notifyListeners();
    }
    return result;
  }

  FutureResult<bool, ExecError> editStatus(
    String id,
    String text, {
    String spoilerText = '',
    String inReplyToId = '',
    required NewEntryMediaItems newMediaItems,
    required List<ImageEntry> existingMediaItems,
    required Visibility newMediaItemVisibility,
  }) async {
    final result = await entryManagerService.editStatus(id, text,
        spoilerText: spoilerText,
        mediaItems: newMediaItems,
        existingMediaItems: existingMediaItems,
        newMediaItemVisibility: newMediaItemVisibility);
    if (result.isSuccess) {
      _logger.finest('Notifying listeners of updated status');
      notifyListeners();
    }
    return result;
  }

  Result<TimelineEntry, ExecError> getEntryById(String id) {
    _logger.finest('Getting entry for $id');
    return entryManagerService.getEntryById(id);
  }

  FutureResult<bool, ExecError> deleteEntryById(String id) async {
    _logger.finest('Delete entry for $id');
    final result = await entryManagerService.deleteEntryById(id);

    if (result.isSuccess) {
      for (final t in cachedTimelines.values) {
        t.removeTimelineEntry(id);
      }
    }
    notifyListeners();
    return result;
  }

  Result<EntryTreeItem, ExecError> getPostTreeEntryBy(String id) {
    _logger.finest('Getting post for $id');
    return entryManagerService.getPostTreeEntryBy(id);
  }

  // refresh timeline gets statuses newer than the newest in that timeline
  List<EntryTreeItem> getTimeline(TimelineIdentifiers type) {
    _logger.finest('Getting timeline $type');
    return cachedTimelines.putIfAbsent(type, () => Timeline(type)).posts;
  }

  ///
  /// id is the id of a post or comment in the chain, including the original post.
  FutureResult<EntryTreeItem, ExecError> refreshStatusChain(String id) async {
    _logger.finest('Refreshing post $id');
    final result = await entryManagerService.refreshStatusChain(id);
    if (result.isSuccess) {
      for (final t in cachedTimelines.values) {
        t.addOrUpdate([result.value]);
      }
      notifyListeners();
    }
    return result;
  }

  FutureResult<EntryTreeItem, ExecError> resharePost(String id) async {
    final result = await entryManagerService.resharePost(id);
    final empty = EntryTreeItem.empty();
    if (result.isSuccess) {
      for (final t in cachedTimelines.values) {
        if (t.posts.firstWhere((p) => p.id == id, orElse: () => empty) !=
            empty) {
          t.addOrUpdate([result.value]);
        }
      }
      notifyListeners();
    }
    return result;
  }

  FutureResult<EntryTreeItem, ExecError> unResharePost(String id) async {
    final result = await entryManagerService.unResharePost(id);
    final empty = EntryTreeItem.empty();
    if (result.isSuccess) {
      for (final t in cachedTimelines.values) {
        if (t.posts.firstWhere((p) => p.id == id, orElse: () => empty) !=
            empty) {
          t.addOrUpdate([result.value]);
        }
      }
      notifyListeners();
    }
    return result;
  }

  Future<void> updateTimeline(
    TimelineIdentifiers type,
    TimelineRefreshType refreshType,
  ) async {
    _logger.finest('Updating w/$refreshType for timeline $type ');
    final timeline = cachedTimelines.putIfAbsent(type, () => Timeline(type));
    late final int lowestId;
    late final int highestId;
    switch (refreshType) {
      case TimelineRefreshType.refresh:
        timeline.clear();
        lowestId = 0;
        highestId = 0;
        break;
      case TimelineRefreshType.loadOlder:
        lowestId = timeline.lowestStatusId;
        highestId = 0;
        break;
      case TimelineRefreshType.loadNewer:
        lowestId = 0;
        highestId = timeline.highestStatusId;
        break;
    }
    (await entryManagerService.updateTimeline(type, lowestId, highestId)).match(
        onSuccess: (posts) {
      _logger.finest('Posts returned for adding to $type: ${posts.length}');
      timeline.addOrUpdate(posts);
      notifyListeners();
    }, onError: (error) {
      _logger.severe('Error getting timeline: $type}');
    });
  }

  FutureResult<EntryTreeItem, ExecError> toggleFavorited(
      String id, bool newStatus) async {
    _logger.finer('Attempting toggling favorite $id to $newStatus');
    final result = await entryManagerService.toggleFavorited(id, newStatus);
    if (result.isFailure) {
      _logger.info('Error toggling favorite $id: ${result.error}');
      return result;
    }

    final update = result.value;
    for (final timeline in cachedTimelines.values) {
      update.entry.parentId.isEmpty
          ? timeline.addOrUpdate([update])
          : timeline.tryUpdateComment(update);
    }

    notifyListeners();
    return result;
  }
// Should put backing store on timelines and entity manager so can recover from restart faster
// Have a purge caches button to start that over from scratch
// Should have a contacts manager with backing store as well
// If our own has delete
}
