import 'package:diameter/main.dart';
import 'package:diameter/models/basal_profile.dart';
import 'package:diameter/models/bolus_profile.dart';
import 'package:diameter/models/log_event_type.dart';
import 'package:objectbox/objectbox.dart';
import 'package:diameter/objectbox.g.dart' show LogEvent_, LogEventType_;

@Entity(uid: 4303325892753185970)
@Sync()
class LogEvent {
  static final Box<LogEvent> box = objectBox.store.box<LogEvent>();

  // properties
  int id;
  bool deleted;
  @Property(type: PropertyType.date)
  DateTime time;
  @Property(type: PropertyType.date)
  DateTime? endTime;
  bool hasEndTime;
  int? reminderDuration;
  String? notes;

  @Transient()
  String? title;
  @Transient()
  bool isEndEvent = false;

  // relations
  final eventType = ToOne<LogEventType>();
  final bolusProfile = ToOne<BolusProfile>();
  final basalProfile = ToOne<BasalProfile>();

  // constructor
  LogEvent({
    this.id = 0,
    this.deleted = false,
    required this.time,
    this.endTime,
    this.hasEndTime = false,
    this.reminderDuration,
    this.notes,
  });

  // methods
  static LogEvent? get(int id) => box.get(id);
  static void put(LogEvent logEvent) => box.put(logEvent);

  static void remove(int id) {
    final item = box.get(id);
    if (item != null) {
      item.deleted = true;
      box.put(item);
    }
  }

  static List<LogEvent> getAllOngoing() {
    QueryBuilder<LogEvent> query = box.query(LogEvent_.hasEndTime.equals(true) &
        LogEvent_.endTime.isNull() &
        LogEvent_.deleted.equals(false))
      ..order(LogEvent_.time);
    return query.build().find();
  }

  static List<LogEvent> getAllActiveForTime(DateTime? dateTime) {
    if (dateTime != null) {
      QueryBuilder<LogEvent> builder = box.query(
          LogEvent_.hasEndTime.equals(true) & LogEvent_.deleted.equals(false))
        ..order(LogEvent_.time, flags: Order.descending);
      final eventsWithEndTime = builder.build().find();
      return eventsWithEndTime.where((event) {
        return (!dateTime.isBefore(event.time)) &&
            !dateTime.isAfter(event.endTime ?? DateTime.now());
      }).toList();
    }
    return [];
  }

  static bool eventTypeExistsForTime(int id, DateTime? dateTime) {
    QueryBuilder<LogEvent> builder = box.query(
        LogEvent_.hasEndTime.equals(true) & LogEvent_.deleted.equals(false))
      ..order(LogEvent_.time, flags: Order.descending);
    builder.link(LogEvent_.eventType, LogEventType_.id.equals(id));
    final eventsWithEndTime = builder.build().find();
    if (dateTime != null) {
      return eventsWithEndTime.where((event) {
        return (!dateTime.isBefore(event.time)) &&
            !dateTime.isAfter(event.endTime ?? DateTime.now());
      }).isNotEmpty;
    }
    return eventsWithEndTime.isNotEmpty;
  }

  static Map<DateTime, List<LogEvent>> getDailyEntryMap() {
    Map<DateTime, List<LogEvent>> dateMap = <DateTime, List<LogEvent>>{};
    Map<DateTime, List<LogEvent>> sortedDateMap = <DateTime, List<LogEvent>>{};

    QueryBuilder<LogEvent> allByDate = box
        .query(LogEvent_.deleted.equals(false))
      ..order(LogEvent_.time, flags: Order.descending);
    List<LogEvent> events = allByDate.build().find();

    DateTime? date;

    for (LogEvent event in events) {
      date = DateTime.utc(event.time.year, event.time.month, event.time.day);
      LogEvent startEvent = event;
      startEvent.title =
          '${event.toString()} ${event.hasEndTime ? '(Start)' : ''}';
      dateMap.putIfAbsent(date, () => <LogEvent>[]).add(startEvent);
    }

    QueryBuilder<LogEvent> allByEndDate = box
        .query(LogEvent_.deleted.equals(false).and(LogEvent_.endTime.notNull()))
      ..order(LogEvent_.endTime, flags: Order.descending);
    List<LogEvent> endEvents = allByEndDate.build().find();

    for (LogEvent event in endEvents) {
      date = DateTime.utc(
          event.endTime!.year, event.endTime!.month, event.endTime!.day);
      LogEvent endEvent = event;
      endEvent.isEndEvent = true;
      endEvent.title = '${event.toString()} (End)';
      dateMap.putIfAbsent(date, () => <LogEvent>[]).add(endEvent);
    }

    final dates = dateMap.keys.toList();
    dates.sort();
    for (DateTime date in dates.reversed) {
      dateMap[date]!.sort((LogEvent a, LogEvent b) {
        final dateA = a.isEndEvent ? a.endTime : a.time;
        final dateB = b.isEndEvent ? b.endTime : b.time;
        return -(dateA!.compareTo(dateB!));
      });
      sortedDateMap
          .putIfAbsent(date, () => <LogEvent>[])
          .addAll(dateMap[date]!);
    }

    return sortedDateMap;
  }

  static List<LogEvent> getAllForDate(DateTime date) {
    DateTime startOfDay = DateTime(date.year, date.month, date.day);
    DateTime endOfDay = startOfDay.add(const Duration(days: 1));

    List<LogEvent> events = [];

    QueryBuilder<LogEvent> allByDate = box
        .query(LogEvent_.deleted.equals(false))
      ..order(LogEvent_.time, flags: Order.descending);
    List<LogEvent> startEvents = allByDate.build().find().where((event) {
      return (event.time.compareTo(startOfDay) >= 0 &&
          event.time.isBefore(endOfDay));
    }).toList();

    for (LogEvent event in startEvents) {
      date = DateTime.utc(event.time.year, event.time.month, event.time.day);
      LogEvent startEvent = event;
      startEvent.title =
          '${event.toString()} ${event.hasEndTime ? '(Start)' : ''}';
      events.add(startEvent);
    }

    QueryBuilder<LogEvent> allByEndDate = box
        .query(LogEvent_.deleted.equals(false).and(LogEvent_.endTime.notNull()))
      ..order(LogEvent_.endTime, flags: Order.descending);
    List<LogEvent> endEvents = allByEndDate.build().find().where((event) {
      return (event.endTime!.compareTo(startOfDay) >= 0 &&
          event.endTime!.isBefore(endOfDay));
    }).toList();

    for (LogEvent event in endEvents) {
      date = DateTime.utc(
          event.endTime!.year, event.endTime!.month, event.endTime!.day);
      LogEvent endEvent = event;
      endEvent.isEndEvent = true;
      endEvent.title = '${event.toString()} (End)';
      events.add(endEvent);
    }

    events.sort((LogEvent a, LogEvent b) {
      final dateA = a.isEndEvent ? a.endTime : a.time;
      final dateB = b.isEndEvent ? b.endTime : b.time;
      return -(dateA!.compareTo(dateB!));
    });

    return events;
  }

  @override
  String toString() {
    return eventType.target?.value ?? '';
  }
}
