import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:drift/drift.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 roomId;

  setUp(() async {
    db = AppDatabase(NativeDatabase.memory());
    // Create a room for task tests
    roomId = await db.roomsDao.insertRoom(
      RoomsCompanion.insert(name: 'Kueche', iconName: 'kitchen'),
    );
  });

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

  group('TasksDao', () {
    test('insertTask returns a valid id', () async {
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Abspuelen',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 15),
        ),
      );
      expect(id, greaterThan(0));
    });

    test('watchTasksInRoom emits tasks sorted by nextDueDate ascending', () async {
      // Insert tasks with different due dates (out of order)
      await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Later Task',
          intervalType: IntervalType.weekly,
          effortLevel: EffortLevel.medium,
          nextDueDate: DateTime(2026, 3, 20),
        ),
      );
      await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Early Task',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 10),
        ),
      );
      await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Middle Task',
          intervalType: IntervalType.biweekly,
          effortLevel: EffortLevel.high,
          nextDueDate: DateTime(2026, 3, 15),
        ),
      );

      final tasks = await db.tasksDao.watchTasksInRoom(roomId).first;

      expect(tasks.length, 3);
      expect(tasks[0].name, 'Early Task');
      expect(tasks[1].name, 'Middle Task');
      expect(tasks[2].name, 'Later Task');
    });

    test('updateTask changes name, description, interval, effort', () async {
      await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Abspuelen',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 15),
        ),
      );

      // Get the task, modify it, and update
      final tasks = await db.tasksDao.watchTasksInRoom(roomId).first;
      final task = tasks.first;
      final updated = task.copyWith(
        name: 'Geschirr spuelen',
        description: Value('Mit Spuelmittel'),
        intervalType: IntervalType.weekly,
        effortLevel: EffortLevel.medium,
      );
      await db.tasksDao.updateTask(updated);

      final result = await db.tasksDao.watchTasksInRoom(roomId).first;
      expect(result.first.name, 'Geschirr spuelen');
      expect(result.first.description, 'Mit Spuelmittel');
      expect(result.first.intervalType, IntervalType.weekly);
      expect(result.first.effortLevel, EffortLevel.medium);
    });

    test('deleteTask removes the task', () async {
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Abspuelen',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 15),
        ),
      );

      await db.tasksDao.deleteTask(id);

      final tasks = await db.tasksDao.watchTasksInRoom(roomId).first;
      expect(tasks, isEmpty);
    });

    test('completeTask records completion and updates nextDueDate', () async {
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Abspuelen',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 15),
        ),
      );

      await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 15));

      // Check that the next due date was updated (daily: +1 day)
      final tasks = await db.tasksDao.watchTasksInRoom(roomId).first;
      expect(tasks.first.nextDueDate, DateTime(2026, 3, 16));
    });

    test('completeTask with overdue task catches up to present', () async {
      // Task was due 2 weeks ago with weekly interval
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Staubsaugen',
          intervalType: IntervalType.weekly,
          effortLevel: EffortLevel.medium,
          nextDueDate: DateTime(2026, 3, 1),
        ),
      );

      // Complete on March 15
      await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 15));

      // Should catch up: Mar 1 -> Mar 8 (still past) -> Mar 15 (equals today, done)
      final tasks = await db.tasksDao.watchTasksInRoom(roomId).first;
      final nextDue = tasks.first.nextDueDate;
      // Next due should be on or after Mar 15
      expect(
        nextDue.isAfter(DateTime(2026, 3, 14)) ||
            nextDue.isAtSameMomentAs(DateTime(2026, 3, 15)),
        isTrue,
      );
    });

    test('tasks with nextDueDate before today are detected as overdue', () async {
      // Insert an overdue task
      await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Overdue Task',
          intervalType: IntervalType.weekly,
          effortLevel: EffortLevel.medium,
          nextDueDate: DateTime(2026, 3, 10),
        ),
      );

      // Insert a task due today (not overdue)
      await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Today Task',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 15),
        ),
      );

      // Insert a future task (not overdue)
      await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Future Task',
          intervalType: IntervalType.monthly,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 20),
        ),
      );

      final overdueCount = await db.tasksDao.getOverdueTaskCount(
        roomId,
        today: DateTime(2026, 3, 15),
      );
      // Only the task due Mar 10 is overdue (before Mar 15)
      expect(overdueCount, 1);
    });

    test('softDeleteTask sets isActive to false without removing the task', () async {
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Abspuelen',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 15),
        ),
      );

      await db.tasksDao.softDeleteTask(id);

      // Task should still exist in DB but isActive == false
      final allTasks = await (db.select(db.tasks)).get();
      expect(allTasks.length, 1);
      expect(allTasks.first.isActive, isFalse);
    });

    test('getCompletionCount returns 0 for task with no completions', () async {
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Abspuelen',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 15),
        ),
      );

      final count = await db.tasksDao.getCompletionCount(id);
      expect(count, 0);
    });

    test('getCompletionCount returns correct count after completions', () async {
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Abspuelen',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 13),
        ),
      );

      await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 13));
      await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 14));
      await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 15));

      final count = await db.tasksDao.getCompletionCount(id);
      expect(count, 3);
    });

    test('watchTasksInRoom excludes soft-deleted (isActive=false) tasks', () async {
      final activeId = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Active Task',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 15),
        ),
      );
      final inactiveId = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Inactive Task',
          intervalType: IntervalType.weekly,
          effortLevel: EffortLevel.medium,
          nextDueDate: DateTime(2026, 3, 10),
        ),
      );

      await db.tasksDao.softDeleteTask(inactiveId);

      final tasks = await db.tasksDao.watchTasksInRoom(roomId).first;
      expect(tasks.length, 1);
      expect(tasks.first.id, activeId);
      expect(tasks.first.name, 'Active Task');
    });

    test('getOverdueTaskCount excludes soft-deleted (isActive=false) tasks', () async {
      // Active overdue task
      await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Active Overdue',
          intervalType: IntervalType.weekly,
          effortLevel: EffortLevel.medium,
          nextDueDate: DateTime(2026, 3, 10),
        ),
      );
      // Inactive overdue task
      final inactiveId = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Inactive Overdue',
          intervalType: IntervalType.weekly,
          effortLevel: EffortLevel.medium,
          nextDueDate: DateTime(2026, 3, 5),
        ),
      );

      await db.tasksDao.softDeleteTask(inactiveId);

      final overdueCount = await db.tasksDao.getOverdueTaskCount(
        roomId,
        today: DateTime(2026, 3, 15),
      );
      expect(overdueCount, 1);
    });

    test('completeTask on due date preserves rhythm', () async {
      // Weekly task due 2026-03-24, completed on 2026-03-24: next due = 2026-03-31
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Weekly On Due Day',
          intervalType: IntervalType.weekly,
          effortLevel: EffortLevel.medium,
          nextDueDate: DateTime(2026, 3, 24),
        ),
      );

      await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 24));

      final tasks = await db.tasksDao.watchTasksInRoom(roomId).first;
      expect(tasks.first.nextDueDate, DateTime(2026, 3, 31));
    });

    test('completeTask before due date recalculates from today', () async {
      // Weekly task due 2026-03-28, completed on 2026-03-24 (Tuesday): next due = 2026-03-31 (7 days from today)
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Weekly Before Due Day',
          intervalType: IntervalType.weekly,
          effortLevel: EffortLevel.medium,
          nextDueDate: DateTime(2026, 3, 28),
        ),
      );

      await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 24));

      final tasks = await db.tasksDao.watchTasksInRoom(roomId).first;
      expect(tasks.first.nextDueDate, DateTime(2026, 3, 31));
    });

    test('completeTask daily task on non-due day recalculates from today', () async {
      // Daily task due 2026-03-26, completed on 2026-03-24: next due = 2026-03-25 (tomorrow)
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Daily Non-Due Day',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 26),
        ),
      );

      await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 24));

      final tasks = await db.tasksDao.watchTasksInRoom(roomId).first;
      expect(tasks.first.nextDueDate, DateTime(2026, 3, 25));
    });

    test('completeTask monthly task early preserves anchor', () async {
      // Monthly task due 2026-03-28 anchorDay=28, completed on 2026-03-24: next due = 2026-04-28
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Monthly Early Completion',
          intervalType: IntervalType.monthly,
          effortLevel: EffortLevel.high,
          nextDueDate: DateTime(2026, 3, 28),
          anchorDay: const Value(28),
        ),
      );

      await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 24));

      final tasks = await db.tasksDao.watchTasksInRoom(roomId).first;
      expect(tasks.first.nextDueDate, DateTime(2026, 4, 28));
    });

    test('hard deleteTask still removes task and its completions', () async {
      final id = await db.tasksDao.insertTask(
        TasksCompanion.insert(
          roomId: roomId,
          name: 'Abspuelen',
          intervalType: IntervalType.daily,
          effortLevel: EffortLevel.low,
          nextDueDate: DateTime(2026, 3, 13),
        ),
      );
      await db.tasksDao.completeTask(id, now: DateTime(2026, 3, 13));

      await db.tasksDao.deleteTask(id);

      final tasks = await (db.select(db.tasks)).get();
      final completions = await (db.select(db.taskCompletions)).get();
      expect(tasks, isEmpty);
      expect(completions, isEmpty);
    });
  });
}
