import 'dart:collection';
import 'dart:convert';
import 'dart:io';

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

import '../models/exec_error.dart';
import '../models/filters/timeline_entry_filter.dart';
import '../models/timeline_entry.dart';
import '../utils/filter_runner.dart';

class TimelineEntryFilterService extends ChangeNotifier {
  static final _logger = Logger('$TimelineEntryFilterService');
  final String filePath;
  final _filters = <String, TimelineEntryFilter>{};
  final _entryCache = <String, _EntryCacheItem>{};

  List<TimelineEntryFilter> get filters =>
      UnmodifiableListView(_filters.values);

  TimelineEntryFilterService(this.filePath);

  void load() {
    final file = File(filePath);
    if (!file.existsSync()) {
      return;
    }

    try {
      final str = file.readAsStringSync();
      final json = jsonDecode(str) as List<dynamic>;
      final filters = json.map((j) => TimelineEntryFilter.fromJson(j)).toList();
      _filters.clear();
      _filters.addEntries(filters.map((f) => MapEntry(f.id, f)));
    } catch (e) {
      _logger.severe('Error parsing filters file $filePath: $e');
    }
  }

  void save() {
    try {
      final json = _filters.values.map((f) => f.toJson()).toList();
      final str = jsonEncode(json);
      File(filePath).writeAsStringSync(str);
    } catch (e) {
      _logger.severe('Error writing filters file $filePath: $e');
    }
  }

  Result<TimelineEntryFilter, ExecError> getForId(String id) {
    if (!_filters.containsKey(id)) {
      return buildErrorResult(
        type: ErrorType.notFound,
        message: 'No filter with id: $id',
      );
    }

    return Result.ok(_filters[id]!);
  }

  void upsertFilter(TimelineEntryFilter filter) {
    _filters[filter.id] = filter;
    _entryCache.clear();
    save();
    notifyListeners();
  }

  void removeById(String id) {
    _filters.remove(id);
    _entryCache.clear();
    save();
    notifyListeners();
  }

  FilterResult checkTimelineEntry(TimelineEntry entry) {
    if (entry == _entryCache[entry.id]?.entry) {
      return _entryCache[entry.id]!.result;
    }

    final result = runFilters(entry, _filters.values.toList());
    _entryCache[entry.id] = _EntryCacheItem(entry, result);

    return result;
  }
}

class _EntryCacheItem {
  final TimelineEntry entry;
  final FilterResult result;

  _EntryCacheItem(this.entry, this.result);
}
