import 'package:drift/drift.dart';

import '../../../core/database/database.dart';
import '../domain/scheduling.dart';

part 'tasks_dao.g.dart';

@DriftAccessor(tables: [Tasks, TaskCompletions])
class TasksDao extends DatabaseAccessor<AppDatabase> with _$TasksDaoMixin {
  TasksDao(super.attachedDatabase);

  /// Watch tasks in a room sorted by nextDueDate ascending.
  /// Only returns active tasks (isActive = true).
  Stream<List<Task>> watchTasksInRoom(int roomId) {
    return (select(tasks)
          ..where((t) => t.roomId.equals(roomId) & t.isActive.equals(true))
          ..orderBy([(t) => OrderingTerm.asc(t.nextDueDate)]))
        .watch();
  }

  /// Insert a new task. Returns the auto-generated id.
  Future<int> insertTask(TasksCompanion task) => into(tasks).insert(task);

  /// Update an existing task. Returns true if a row was updated.
  Future<bool> updateTask(Task task) => update(tasks).replace(task);

  /// Delete a task and its completions.
  Future<void> deleteTask(int taskId) {
    return transaction(() async {
      await (delete(taskCompletions)..where((c) => c.taskId.equals(taskId)))
          .go();
      await (delete(tasks)..where((t) => t.id.equals(taskId))).go();
    });
  }

  /// Mark a task as done: records completion and calculates next due date.
  ///
  /// Uses scheduling utility for date calculation. If completing on the due
  /// date, next due is calculated from the original due date (keeps rhythm).
  /// If completing on a different day (early or late), next due is calculated
  /// from today (per D-02: matches user mental model "I did it now, schedule next from now").
  /// If the calculated next due is in the past, catch-up advances to present.
  ///
  /// [now] parameter allows injection of current time for testing.
  Future<void> completeTask(int taskId, {DateTime? now}) {
    return transaction(() async {
      // 1. Get current task
      final task =
          await (select(tasks)..where((t) => t.id.equals(taskId))).getSingle();

      final currentTime = now ?? DateTime.now();

      // 2. Record completion
      await into(taskCompletions).insert(TaskCompletionsCompanion.insert(
        taskId: taskId,
        completedAt: currentTime,
      ));

      // 3. Calculate next due date
      // If completing on the due date, use original due date as base (keeps rhythm).
      // If completing on a different day (early or late), use today as base (per D-02).
      final todayStart = DateTime(currentTime.year, currentTime.month, currentTime.day);
      final taskDueDay = DateTime(task.nextDueDate.year, task.nextDueDate.month, task.nextDueDate.day);
      final baseDate = todayStart == taskDueDay ? task.nextDueDate : todayStart;

      var nextDue = calculateNextDueDate(
        currentDueDate: baseDate,
        intervalType: task.intervalType,
        intervalDays: task.intervalDays,
        anchorDay: task.anchorDay,
      );

      // 4. Catch up if next due is still in the past
      nextDue = catchUpToPresent(
        nextDue: nextDue,
        today: todayStart,
        intervalType: task.intervalType,
        intervalDays: task.intervalDays,
        anchorDay: task.anchorDay,
      );

      // 5. Update task with new due date
      await (update(tasks)..where((t) => t.id.equals(taskId)))
          .write(TasksCompanion(nextDueDate: Value(nextDue)));
    });
  }

  /// Watch all completions for a task, newest first.
  Stream<List<TaskCompletion>> watchCompletionsForTask(int taskId) {
    return (select(taskCompletions)
          ..where((c) => c.taskId.equals(taskId))
          ..orderBy([(c) => OrderingTerm.desc(c.completedAt)]))
        .watch();
  }

  /// Count overdue tasks in a room (nextDueDate before today).
  /// Only counts active tasks (isActive = true).
  Future<int> getOverdueTaskCount(int roomId, {DateTime? today}) async {
    final now = today ?? DateTime.now();
    final todayDateOnly = DateTime(now.year, now.month, now.day);

    final taskList = await (select(tasks)
          ..where((t) => t.roomId.equals(roomId) & t.isActive.equals(true)))
        .get();

    return taskList.where((task) {
      final dueDate = DateTime(
        task.nextDueDate.year,
        task.nextDueDate.month,
        task.nextDueDate.day,
      );
      return dueDate.isBefore(todayDateOnly);
    }).length;
  }

  /// Soft-delete a task by setting isActive to false.
  /// The task and its completions remain in the database.
  Future<void> softDeleteTask(int taskId) {
    return (update(tasks)..where((t) => t.id.equals(taskId)))
        .write(const TasksCompanion(isActive: Value(false)));
  }

  /// Count completions for a task.
  Future<int> getCompletionCount(int taskId) async {
    final count = taskCompletions.id.count();
    final query = selectOnly(taskCompletions)
      ..addColumns([count])
      ..where(taskCompletions.taskId.equals(taskId));
    final result = await query.getSingle();
    return result.read(count) ?? 0;
  }
}
