import 'package:drift/drift.dart' show Value;
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:household_keeper/core/database/database.dart';
import 'package:household_keeper/features/tasks/domain/effort_level.dart';
import 'package:household_keeper/features/tasks/domain/frequency.dart';

void main() {
  late AppDatabase db;
  late int room1Id;
  late int room2Id;

  setUp(() async {
    db = AppDatabase(NativeDatabase.memory());
    room1Id = await db.roomsDao.insertRoom(
      RoomsCompanion.insert(name: 'Kueche', iconName: 'kitchen'),
    );
    room2Id = await db.roomsDao.insertRoom(
      RoomsCompanion.insert(name: 'Badezimmer', iconName: 'bathroom'),
    );
  });

  tearDown(() async {
    await db.close();
  });

  group('CalendarDao.watchTasksForDate', () {
    test('returns empty list when no tasks exist', () async {
      final result = await db.calendarDao
          .watchTasksForDate(DateTime(2026, 3, 16))
          .first;
      expect(result, isEmpty);
    });

    test('returns only tasks due on the queried date', () async {
      // Task due on March 16
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Abspuelen',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 16, 9, 30),
      ));
      // Task due on March 15 (should NOT appear)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Staubsaugen',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 15),
      ));
      // Task due on March 17 (should NOT appear)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Fenster putzen',
        intervalType: IntervalType.monthly,
        effortLevel: EffortLevel.high,
        nextDueDate: DateTime(2026, 3, 17),
      ));

      final result = await db.calendarDao
          .watchTasksForDate(DateTime(2026, 3, 16))
          .first;
      expect(result.length, 1);
      expect(result.first.task.name, 'Abspuelen');
    });

    test('returns tasks from multiple rooms with correct room pairing',
        () async {
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Kueche Aufgabe',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 20),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room2Id,
        name: 'Bad Aufgabe',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 20, 14),
      ));

      final result = await db.calendarDao
          .watchTasksForDate(DateTime(2026, 3, 20))
          .first;
      expect(result.length, 2);

      final names = result.map((t) => t.task.name).toList();
      expect(names, contains('Kueche Aufgabe'));
      expect(names, contains('Bad Aufgabe'));

      final kuecheTask =
          result.firstWhere((t) => t.task.name == 'Kueche Aufgabe');
      expect(kuecheTask.roomName, 'Kueche');
      expect(kuecheTask.roomId, room1Id);

      final badTask =
          result.firstWhere((t) => t.task.name == 'Bad Aufgabe');
      expect(badTask.roomName, 'Badezimmer');
      expect(badTask.roomId, room2Id);
    });

    test('returns tasks sorted alphabetically by name', () async {
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Zitrone putzen',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 18),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Abspuelen',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 18, 10),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room2Id,
        name: 'Moppen',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 18, 8),
      ));

      final result = await db.calendarDao
          .watchTasksForDate(DateTime(2026, 3, 18))
          .first;
      expect(result.length, 3);
      expect(result[0].task.name, 'Abspuelen');
      expect(result[1].task.name, 'Moppen');
      expect(result[2].task.name, 'Zitrone putzen');
    });

    test('does NOT include overdue carry-over for past dates', () async {
      // Task due on March 10 (overdue relative to March 16)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Alte Aufgabe',
        intervalType: IntervalType.monthly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 10),
      ));
      // Task due on March 15 (queried date)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Richtige Aufgabe',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 15),
      ));

      // Querying March 15 should only return the task due on March 15
      final result = await db.calendarDao
          .watchTasksForDate(DateTime(2026, 3, 15))
          .first;
      expect(result.length, 1);
      expect(result.first.task.name, 'Richtige Aufgabe');
    });
  });

  group('CalendarDao.watchOverdueTasks', () {
    test('returns empty list when no overdue tasks exist', () async {
      final result = await db.calendarDao
          .watchOverdueTasks(DateTime(2026, 3, 16))
          .first;
      expect(result, isEmpty);
    });

    test('returns tasks whose nextDueDate is before referenceDate', () async {
      // Task due March 15 — overdue relative to March 16
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Ueberfaelliges Task',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 15),
      ));
      // Task due March 10 — also overdue
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Sehr altes Task',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 10),
      ));

      final result = await db.calendarDao
          .watchOverdueTasks(DateTime(2026, 3, 16))
          .first;
      expect(result.length, 2);
    });

    test('does NOT include tasks due ON the referenceDate', () async {
      // Task due exactly on reference date (March 16) — should NOT appear
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Heutiges Task',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 16),
      ));
      // Task due yesterday (March 15) — should appear
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Gestriges Task',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 15),
      ));

      final result = await db.calendarDao
          .watchOverdueTasks(DateTime(2026, 3, 16))
          .first;
      expect(result.length, 1);
      expect(result.first.task.name, 'Gestriges Task');
    });

    test('does NOT include tasks due in the future', () async {
      // Future task — should NOT appear
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Zukuenftiges Task',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 20),
      ));

      final result = await db.calendarDao
          .watchOverdueTasks(DateTime(2026, 3, 16))
          .first;
      expect(result, isEmpty);
    });

    test('returns overdue tasks sorted by nextDueDate ascending', () async {
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Neues Overdue',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 15),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Altes Overdue',
        intervalType: IntervalType.monthly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 1),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room2Id,
        name: 'Mittleres Overdue',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 10),
      ));

      final result = await db.calendarDao
          .watchOverdueTasks(DateTime(2026, 3, 16))
          .first;
      expect(result.length, 3);
      expect(result[0].task.name, 'Altes Overdue');
      expect(result[1].task.name, 'Mittleres Overdue');
      expect(result[2].task.name, 'Neues Overdue');
    });

    test('returns overdue task with correct room pairing', () async {
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room2Id,
        name: 'Bad Overdue',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 14),
      ));

      final result = await db.calendarDao
          .watchOverdueTasks(DateTime(2026, 3, 16))
          .first;
      expect(result.length, 1);
      expect(result.first.task.name, 'Bad Overdue');
      expect(result.first.roomName, 'Badezimmer');
      expect(result.first.roomId, room2Id);
    });
  });

  group('CalendarDao.watchTasksForDateInRoom', () {
    test('returns empty list when no tasks exist in room', () async {
      final result = await db.calendarDao
          .watchTasksForDateInRoom(DateTime(2026, 3, 16), room1Id)
          .first;
      expect(result, isEmpty);
    });

    test('returns only tasks due on the queried date in the specified room',
        () async {
      // Task due on March 16 in room1
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Abspuelen',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 16, 9, 30),
      ));
      // Task due on March 15 in room1 (should NOT appear)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Staubsaugen',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 15),
      ));
      // Task due on March 17 in room1 (should NOT appear)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Fenster putzen',
        intervalType: IntervalType.monthly,
        effortLevel: EffortLevel.high,
        nextDueDate: DateTime(2026, 3, 17),
      ));

      final result = await db.calendarDao
          .watchTasksForDateInRoom(DateTime(2026, 3, 16), room1Id)
          .first;
      expect(result.length, 1);
      expect(result.first.task.name, 'Abspuelen');
    });

    test('does not return tasks from other rooms on the same date', () async {
      // Task due on March 16 in room1
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Kueche Aufgabe',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 16),
      ));
      // Task due on March 16 in room2 (should NOT appear when querying room1)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room2Id,
        name: 'Bad Aufgabe',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 16),
      ));

      final result = await db.calendarDao
          .watchTasksForDateInRoom(DateTime(2026, 3, 16), room1Id)
          .first;
      expect(result.length, 1);
      expect(result.first.task.name, 'Kueche Aufgabe');
      expect(result.first.roomId, room1Id);
    });

    test('returns tasks sorted alphabetically by name', () async {
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Zitrone putzen',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 18),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Abspuelen',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 18, 10),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Moppen',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 18, 8),
      ));

      final result = await db.calendarDao
          .watchTasksForDateInRoom(DateTime(2026, 3, 18), room1Id)
          .first;
      expect(result.length, 3);
      expect(result[0].task.name, 'Abspuelen');
      expect(result[1].task.name, 'Moppen');
      expect(result[2].task.name, 'Zitrone putzen');
    });
  });

  group('CalendarDao.watchOverdueTasksInRoom', () {
    test('returns empty list when no overdue tasks in room', () async {
      final result = await db.calendarDao
          .watchOverdueTasksInRoom(DateTime(2026, 3, 16), room1Id)
          .first;
      expect(result, isEmpty);
    });

    test('returns only overdue tasks in the specified room', () async {
      // Task due March 15 in room1 — overdue relative to March 16
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Ueberfaelliges Task',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 15),
      ));
      // Task due March 10 in room1 — also overdue
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Sehr altes Task',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 10),
      ));
      // Task due on March 16 in room1 — NOT overdue (should NOT appear)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Heutiges Task',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 16),
      ));

      final result = await db.calendarDao
          .watchOverdueTasksInRoom(DateTime(2026, 3, 16), room1Id)
          .first;
      expect(result.length, 2);
    });

    test('does not return overdue tasks from other rooms', () async {
      // Overdue task in room1
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Kueche Overdue',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 14),
      ));
      // Overdue task in room2 (should NOT appear when querying room1)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room2Id,
        name: 'Bad Overdue',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 14),
      ));

      final result = await db.calendarDao
          .watchOverdueTasksInRoom(DateTime(2026, 3, 16), room1Id)
          .first;
      expect(result.length, 1);
      expect(result.first.task.name, 'Kueche Overdue');
      expect(result.first.roomId, room1Id);
    });

    test('returns overdue tasks sorted by nextDueDate ascending', () async {
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Neues Overdue',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 15),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Altes Overdue',
        intervalType: IntervalType.monthly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 1),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Mittleres Overdue',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 10),
      ));

      final result = await db.calendarDao
          .watchOverdueTasksInRoom(DateTime(2026, 3, 16), room1Id)
          .first;
      expect(result.length, 3);
      expect(result[0].task.name, 'Altes Overdue');
      expect(result[1].task.name, 'Mittleres Overdue');
      expect(result[2].task.name, 'Neues Overdue');
    });
  });

  group('CalendarDao.watchAllActiveRecurringTasks', () {
    test('returns all active tasks', () async {
      // Insert 2 active tasks and 1 inactive task
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Aktive Aufgabe 1',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 20),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room2Id,
        name: 'Aktive Aufgabe 2',
        intervalType: IntervalType.monthly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 25),
      ));
      // Insert inactive task (isActive defaults to true; manually set false via update)
      final inactiveId = await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Inaktive Aufgabe',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 16),
      ));
      await (db.update(db.tasks)..where((t) => t.id.equals(inactiveId)))
          .write(const TasksCompanion(isActive: Value(false)));

      final result =
          await db.calendarDao.watchAllActiveRecurringTasks().first;
      expect(result.length, 2);
      final names = result.map((t) => t.task.name).toList();
      expect(names, contains('Aktive Aufgabe 1'));
      expect(names, contains('Aktive Aufgabe 2'));
      expect(names, isNot(contains('Inaktive Aufgabe')));
    });

    test('returns tasks sorted alphabetically by name', () async {
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Zuletzt',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 20),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room2Id,
        name: 'Als erstes',
        intervalType: IntervalType.monthly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 25),
      ));

      final result =
          await db.calendarDao.watchAllActiveRecurringTasks().first;
      expect(result.length, 2);
      expect(result[0].task.name, 'Als erstes');
      expect(result[1].task.name, 'Zuletzt');
    });

    test('returns empty list when no active tasks exist', () async {
      final result =
          await db.calendarDao.watchAllActiveRecurringTasks().first;
      expect(result, isEmpty);
    });
  });

  group('CalendarDao.watchAllActiveRecurringTasksInRoom', () {
    test('filters tasks by room', () async {
      // Tasks in room1
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Kueche Aufgabe',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 20),
      ));
      // Task in room2 (should NOT appear for room1)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room2Id,
        name: 'Bad Aufgabe',
        intervalType: IntervalType.monthly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 25),
      ));

      final result =
          await db.calendarDao.watchAllActiveRecurringTasksInRoom(room1Id).first;
      expect(result.length, 1);
      expect(result.first.task.name, 'Kueche Aufgabe');
      expect(result.first.roomId, room1Id);
    });

    test('returns empty list when room has no active tasks', () async {
      final result =
          await db.calendarDao.watchAllActiveRecurringTasksInRoom(room1Id).first;
      expect(result, isEmpty);
    });
  });

  group('CalendarDao.watchCompletionsInRange', () {
    test('returns completions within date range', () async {
      final taskId = await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Woechentliche Aufgabe',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 23),
      ));

      // Completion within range (March 18)
      await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
        taskId: taskId,
        completedAt: DateTime(2026, 3, 18, 10),
      ));
      // Completion within range (March 20)
      await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
        taskId: taskId,
        completedAt: DateTime(2026, 3, 20, 14),
      ));
      // Completion OUTSIDE range (before start — March 15)
      await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
        taskId: taskId,
        completedAt: DateTime(2026, 3, 15),
      ));
      // Completion OUTSIDE range (after end — March 25)
      await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
        taskId: taskId,
        completedAt: DateTime(2026, 3, 25),
      ));

      // Range: March 16 (inclusive) to March 24 (exclusive)
      final result = await db.calendarDao
          .watchCompletionsInRange(
            taskId,
            DateTime(2026, 3, 16),
            DateTime(2026, 3, 24),
          )
          .first;
      expect(result.length, 2);
    });

    test('returns empty list when no completions in range', () async {
      final taskId = await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Keine Completions',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 23),
      ));

      final result = await db.calendarDao
          .watchCompletionsInRange(
            taskId,
            DateTime(2026, 3, 16),
            DateTime(2026, 3, 24),
          )
          .first;
      expect(result, isEmpty);
    });

    test('returns empty list for wrong taskId', () async {
      final taskId = await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Aufgabe mit Completion',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 23),
      ));
      // Insert completion for taskId
      await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
        taskId: taskId,
        completedAt: DateTime(2026, 3, 18),
      ));

      // Query for a different task ID
      final result = await db.calendarDao
          .watchCompletionsInRange(
            taskId + 999,
            DateTime(2026, 3, 16),
            DateTime(2026, 3, 24),
          )
          .first;
      expect(result, isEmpty);
    });

    test('start is inclusive, end is exclusive', () async {
      final taskId = await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Grenzen Test',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 23),
      ));

      // Completion exactly at start boundary (inclusive)
      await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
        taskId: taskId,
        completedAt: DateTime(2026, 3, 16),
      ));
      // Completion exactly at end boundary (exclusive — should NOT be included)
      await db.into(db.taskCompletions).insert(TaskCompletionsCompanion.insert(
        taskId: taskId,
        completedAt: DateTime(2026, 3, 24),
      ));

      final result = await db.calendarDao
          .watchCompletionsInRange(
            taskId,
            DateTime(2026, 3, 16),
            DateTime(2026, 3, 24),
          )
          .first;
      // Only the start-boundary completion should be included
      expect(result.length, 1);
      expect(result.first.completedAt, DateTime(2026, 3, 16));
    });
  });

  group('CalendarDao.getTaskCountInRoom', () {
    test('returns 0 when room has no tasks', () async {
      final count = await db.calendarDao.getTaskCountInRoom(room1Id);
      expect(count, 0);
    });

    test('returns correct count for room', () async {
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Aufgabe 1',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 16),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Aufgabe 2',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 20),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Aufgabe 3',
        intervalType: IntervalType.monthly,
        effortLevel: EffortLevel.high,
        nextDueDate: DateTime(2026, 3, 25),
      ));

      final count = await db.calendarDao.getTaskCountInRoom(room1Id);
      expect(count, 3);
    });

    test('does not count tasks from other rooms', () async {
      // Two tasks in room1
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Kueche Aufgabe 1',
        intervalType: IntervalType.daily,
        effortLevel: EffortLevel.low,
        nextDueDate: DateTime(2026, 3, 16),
      ));
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room1Id,
        name: 'Kueche Aufgabe 2',
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
        nextDueDate: DateTime(2026, 3, 20),
      ));
      // One task in room2 (should NOT be counted for room1)
      await db.tasksDao.insertTask(TasksCompanion.insert(
        roomId: room2Id,
        name: 'Bad Aufgabe',
        intervalType: IntervalType.monthly,
        effortLevel: EffortLevel.high,
        nextDueDate: DateTime(2026, 3, 18),
      ));

      final count = await db.calendarDao.getTaskCountInRoom(room1Id);
      expect(count, 2);
    });
  });
}
