import 'package:drift/drift.dart';

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

part 'calendar_dao.g.dart';

/// DAO for calendar-based task queries.
///
/// Provides date-parameterized queries to answer:
/// - "What tasks are due on date X?"
/// - "What tasks are overdue relative to today?"
@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])
class CalendarDao extends DatabaseAccessor<AppDatabase>
    with _$CalendarDaoMixin {
  CalendarDao(super.attachedDatabase);

  /// Watch tasks whose [nextDueDate] falls on the given calendar day.
  ///
  /// Returns tasks sorted alphabetically by name.
  /// Does NOT include overdue carry-over — only tasks originally due on [date].
  Stream<List<TaskWithRoom>> watchTasksForDate(DateTime date) {
    final startOfDay = DateTime(date.year, date.month, date.day);
    final endOfDay = startOfDay.add(const Duration(days: 1));

    final query = select(tasks).join([
      innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
    ]);
    query.where(
      tasks.nextDueDate.isBiggerOrEqualValue(startOfDay) &
          tasks.nextDueDate.isSmallerThanValue(endOfDay) &
          tasks.isActive.equals(true),
    );
    query.orderBy([OrderingTerm.asc(tasks.name)]);

    return query.watch().map((rows) {
      return rows.map((row) {
        final task = row.readTable(tasks);
        final room = row.readTable(rooms);
        return TaskWithRoom(
          task: task,
          roomName: room.name,
          roomId: room.id,
        );
      }).toList();
    });
  }

  /// Returns the total count of active tasks across all rooms and dates.
  ///
  /// Used by the UI to distinguish first-run empty state from celebration state.
  Future<int> getTaskCount() async {
    final countExp = tasks.id.count();
    final query = selectOnly(tasks)
      ..addColumns([countExp])
      ..where(tasks.isActive.equals(true));
    final result = await query.getSingle();
    return result.read(countExp) ?? 0;
  }

  /// Watch tasks due on [date] within a specific [roomId].
  ///
  /// Same as [watchTasksForDate] but filtered to a single room.
  Stream<List<TaskWithRoom>> watchTasksForDateInRoom(
      DateTime date, int roomId) {
    final startOfDay = DateTime(date.year, date.month, date.day);
    final endOfDay = startOfDay.add(const Duration(days: 1));

    final query = select(tasks).join([
      innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
    ]);
    query.where(
      tasks.nextDueDate.isBiggerOrEqualValue(startOfDay) &
          tasks.nextDueDate.isSmallerThanValue(endOfDay) &
          tasks.roomId.equals(roomId) &
          tasks.isActive.equals(true),
    );
    query.orderBy([OrderingTerm.asc(tasks.name)]);

    return query.watch().map((rows) {
      return rows.map((row) {
        final task = row.readTable(tasks);
        final room = row.readTable(rooms);
        return TaskWithRoom(task: task, roomName: room.name, roomId: room.id);
      }).toList();
    });
  }

  /// Watch tasks whose [nextDueDate] is strictly before [referenceDate].
  ///
  /// Returns tasks sorted by [nextDueDate] ascending (oldest first).
  /// Does NOT include tasks due on [referenceDate] itself.
  Stream<List<TaskWithRoom>> watchOverdueTasks(DateTime referenceDate) {
    final startOfReferenceDay = DateTime(
      referenceDate.year,
      referenceDate.month,
      referenceDate.day,
    );

    final query = select(tasks).join([
      innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
    ]);
    query.where(
      tasks.nextDueDate.isSmallerThanValue(startOfReferenceDay) &
          tasks.isActive.equals(true),
    );
    query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]);

    return query.watch().map((rows) {
      return rows.map((row) {
        final task = row.readTable(tasks);
        final room = row.readTable(rooms);
        return TaskWithRoom(
          task: task,
          roomName: room.name,
          roomId: room.id,
        );
      }).toList();
    });
  }

  /// Watch overdue tasks (before [referenceDate]) within a specific [roomId].
  ///
  /// Same as [watchOverdueTasks] but filtered to a single room.
  Stream<List<TaskWithRoom>> watchOverdueTasksInRoom(
      DateTime referenceDate, int roomId) {
    final startOfReferenceDay = DateTime(
      referenceDate.year,
      referenceDate.month,
      referenceDate.day,
    );

    final query = select(tasks).join([
      innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
    ]);
    query.where(
      tasks.nextDueDate.isSmallerThanValue(startOfReferenceDay) &
          tasks.roomId.equals(roomId) &
          tasks.isActive.equals(true),
    );
    query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]);

    return query.watch().map((rows) {
      return rows.map((row) {
        final task = row.readTable(tasks);
        final room = row.readTable(rooms);
        return TaskWithRoom(
          task: task,
          roomName: room.name,
          roomId: room.id,
        );
      }).toList();
    });
  }

  /// Total active task count within a specific room.
  ///
  /// Used to distinguish first-run empty state from celebration state
  /// in the room calendar view.
  Future<int> getTaskCountInRoom(int roomId) async {
    final countExp = tasks.id.count();
    final query = selectOnly(tasks)
      ..addColumns([countExp])
      ..where(tasks.roomId.equals(roomId) & tasks.isActive.equals(true));
    final result = await query.getSingle();
    return result.read(countExp) ?? 0;
  }

  /// Watch ALL active tasks with their rooms.
  ///
  /// Used by the pre-population logic to determine which tasks should appear
  /// on days within their current interval window (before their next due date).
  Stream<List<TaskWithRoom>> watchAllActiveRecurringTasks() {
    final query = select(tasks).join([
      innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
    ]);
    query.where(tasks.isActive.equals(true));
    query.orderBy([OrderingTerm.asc(tasks.name)]);

    return query.watch().map((rows) {
      return rows.map((row) {
        final task = row.readTable(tasks);
        final room = row.readTable(rooms);
        return TaskWithRoom(task: task, roomName: room.name, roomId: room.id);
      }).toList();
    });
  }

  /// Watch ALL active tasks with their rooms, filtered to a specific [roomId].
  ///
  /// Room-scoped variant of [watchAllActiveRecurringTasks], used by
  /// [roomCalendarDayProvider] to pre-populate tasks for a single room.
  Stream<List<TaskWithRoom>> watchAllActiveRecurringTasksInRoom(int roomId) {
    final query = select(tasks).join([
      innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
    ]);
    query.where(tasks.isActive.equals(true) & tasks.roomId.equals(roomId));
    query.orderBy([OrderingTerm.asc(tasks.name)]);

    return query.watch().map((rows) {
      return rows.map((row) {
        final task = row.readTable(tasks);
        final room = row.readTable(rooms);
        return TaskWithRoom(task: task, roomName: room.name, roomId: room.id);
      }).toList();
    });
  }

  /// Watch completions for a given [taskId] within a date range [start]..[end].
  ///
  /// Used for period-completion filtering: if a task was completed in the
  /// current interval window, it should not appear as a pre-populated task.
  /// [start] is inclusive; [end] is exclusive.
  Stream<List<TaskCompletion>> watchCompletionsInRange(
      int taskId, DateTime start, DateTime end) {
    return (select(taskCompletions)
          ..where((c) =>
              c.taskId.equals(taskId) &
              c.completedAt.isBiggerOrEqualValue(start) &
              c.completedAt.isSmallerThanValue(end)))
        .watch();
  }
}
