// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only

/// Tests Reminder Policy behavior for the Scheduler Core package.
library;

import 'package:scheduler_core/scheduler_core.dart';
import 'package:test/test.dart';

/// Entry point for this executable Dart file.
/// It wires the local command, test, or application behavior needed by this repository without exposing additional public API.
void main() {
  group('Effective reminder profile resolution', () {
    const service = ReminderPolicyService();
    final now = DateTime.utc(2026, 6, 25, 9);

    test('uses task override before project default and fallback', () {
      final project = ProjectProfile(
        id: 'home',
        name: 'Home',
        colorKey: 'green',
        defaultReminderProfile: ReminderProfile.gentle,
      );
      final task = scheduledTask(
        id: 'task',
        now: now,
        reminderOverride: ReminderProfile.strict,
      );

      final effective = service.resolveEffectiveProfile(
        task: task,
        project: project,
      );

      expect(effective.profile, ReminderProfile.strict);
      expect(effective.source, EffectiveReminderProfileSource.taskOverride);
    });

    test('uses project default before application fallback', () {
      final project = ProjectProfile(
        id: 'home',
        name: 'Home',
        colorKey: 'green',
        defaultReminderProfile: ReminderProfile.persistent,
      );
      final task = scheduledTask(id: 'task', now: now);

      final effective = service.resolveEffectiveProfile(
        task: task,
        project: project,
      );

      expect(effective.profile, ReminderProfile.persistent);
      expect(effective.source, EffectiveReminderProfileSource.projectDefault);
    });

    test('uses fallback when no task or project value exists', () {
      final task = scheduledTask(id: 'task', now: now);

      final effective = service.resolveEffectiveProfile(task: task);

      expect(effective.profile, ReminderProfile.gentle);
      expect(effective.source, EffectiveReminderProfileSource.fallback);
    });

    test('does not silently use learned project suggestions', () {
      final project = ProjectProfile(
        id: 'home',
        name: 'Home',
        colorKey: 'green',
        defaultReminderProfile: ReminderProfile.strict,
      );
      final statistics = ProjectStatistics(
        projectId: 'home',
        completedTaskCount: 3,
        reminderProfileCounts: const {ReminderProfile.gentle: 3},
      );
      final suggestionResolution = const ProjectSuggestionService()
          .resolveDefaults(project: project, statistics: statistics);
      final task = scheduledTask(id: 'task', now: now);

      final effective = service.resolveEffectiveProfile(
        task: task,
        project: suggestionResolution.project,
      );

      expect(suggestionResolution.suggestions.reminderProfile!.value,
          ReminderProfile.gentle);
      expect(effective.profile, ReminderProfile.strict);
      expect(effective.source, EffectiveReminderProfileSource.projectDefault);
    });
  });

  group('Reminder directives', () {
    const service = ReminderPolicyService();
    final now = DateTime.utc(2026, 6, 25, 9);

    test('all non-silent reminder profiles produce normal flexible delivery',
        () {
      for (final profile in [
        ReminderProfile.gentle,
        ReminderProfile.persistent,
        ReminderProfile.strict,
      ]) {
        final directive = service.directiveForTask(
          task: scheduledTask(
            id: 'task-${profile.name}',
            now: now,
            reminderOverride: profile,
          ),
          now: now,
        );

        expect(directive.action, ReminderDirectiveAction.deliver);
        expect(directive.reason, ReminderDirectiveReason.deliverNormal);
        expect(directive.effectiveProfile, profile);
      }
    });

    test('silent profile suppresses normal reminders', () {
      final directive = service.directiveForTask(
        task: scheduledTask(
          id: 'task',
          now: now,
          reminderOverride: ReminderProfile.silent,
        ),
        now: now,
      );

      expect(directive.action, ReminderDirectiveAction.suppress);
      expect(directive.reason, ReminderDirectiveReason.suppressSilentProfile);
    });

    test(
        'defers before the scheduled window and delivers at the start boundary',
        () {
      final task = scheduledTask(
        id: 'task',
        now: now,
        start: DateTime.utc(2026, 6, 25, 10),
      );

      final before = service.directiveForTask(
        task: task,
        now: DateTime.utc(2026, 6, 25, 9, 59),
      );
      final atStart = service.directiveForTask(
        task: task,
        now: DateTime.utc(2026, 6, 25, 10),
      );

      expect(before.action, ReminderDirectiveAction.defer);
      expect(before.reason, ReminderDirectiveReason.deferUntilTaskWindow);
      expect(before.nextEligibleAt, DateTime.utc(2026, 6, 25, 10));
      expect(atStart.action, ReminderDirectiveAction.deliver);
    });

    test('suppresses flexible reminders during active protected Free Slots',
        () {
      final freeSlot = freeSlotTask(
        id: 'rest',
        start: DateTime.utc(2026, 6, 25, 9),
        end: DateTime.utc(2026, 6, 25, 9, 30),
        createdAt: now,
      );
      final flexible = scheduledTask(id: 'flexible', now: now);

      final atStart = service.directiveForTask(
        task: flexible,
        now: DateTime.utc(2026, 6, 25, 9),
        contextTasks: [freeSlot, flexible],
      );
      final inside = service.directiveForTask(
        task: flexible,
        now: DateTime.utc(2026, 6, 25, 9, 29),
        contextTasks: [freeSlot, flexible],
      );
      final atEnd = service.directiveForTask(
        task: flexible,
        now: DateTime.utc(2026, 6, 25, 9, 30),
        contextTasks: [freeSlot, flexible],
      );

      expect(atStart.action, ReminderDirectiveAction.suppress);
      expect(atStart.reason, ReminderDirectiveReason.suppressProtectedRest);
      expect(atStart.protectedFreeSlotTaskId, 'rest');
      expect(atStart.nextEligibleAt, DateTime.utc(2026, 6, 25, 9, 30));
      expect(inside.action, ReminderDirectiveAction.suppress);
      expect(atEnd.action, ReminderDirectiveAction.deliver);
    });

    test('required reminders may pass protected rest by typed policy', () {
      final freeSlot = freeSlotTask(
        id: 'rest',
        start: DateTime.utc(2026, 6, 25, 9),
        end: DateTime.utc(2026, 6, 25, 10),
        createdAt: now,
      );
      final critical = scheduledTask(
        id: 'critical',
        now: now,
        type: TaskType.critical,
        reminderOverride: ReminderProfile.strict,
      );
      final inflexiblePersistent = scheduledTask(
        id: 'inflexible-persistent',
        now: now,
        type: TaskType.inflexible,
        reminderOverride: ReminderProfile.persistent,
      );
      final inflexibleGentle = scheduledTask(
        id: 'inflexible-gentle',
        now: now,
        type: TaskType.inflexible,
        reminderOverride: ReminderProfile.gentle,
      );

      final criticalDirective = service.directiveForTask(
        task: critical,
        now: now,
        contextTasks: [freeSlot, critical],
      );
      final persistentDirective = service.directiveForTask(
        task: inflexiblePersistent,
        now: now,
        contextTasks: [freeSlot, inflexiblePersistent],
      );
      final gentleDirective = service.directiveForTask(
        task: inflexibleGentle,
        now: now,
        contextTasks: [freeSlot, inflexibleGentle],
      );

      expect(
        criticalDirective.action,
        ReminderDirectiveAction.requireAcknowledgement,
      );
      expect(
        criticalDirective.reason,
        ReminderDirectiveReason.requireRequiredAcknowledgement,
      );
      expect(persistentDirective.action, ReminderDirectiveAction.deliver);
      expect(gentleDirective.action, ReminderDirectiveAction.defer);
      expect(
        gentleDirective.reason,
        ReminderDirectiveReason.deferRequiredDuringProtectedRest,
      );
      expect(gentleDirective.nextEligibleAt, DateTime.utc(2026, 6, 25, 10));
    });

    test('suppresses locked free-slot completed and unscheduled records', () {
      final locked = scheduledTask(
        id: 'locked',
        now: now,
        type: TaskType.locked,
      );
      final freeSlot = freeSlotTask(
        id: 'rest',
        start: now,
        end: now.add(const Duration(minutes: 30)),
        createdAt: now,
      );
      final completed = scheduledTask(
        id: 'completed',
        now: now,
        status: TaskStatus.completed,
      );
      final unscheduled = scheduledTask(id: 'unscheduled', now: now)
          .copyWith(clearSchedule: true);

      expect(
        service.directiveForTask(task: locked, now: now).reason,
        ReminderDirectiveReason.suppressHiddenLockedTime,
      );
      expect(
        service.directiveForTask(task: freeSlot, now: now).reason,
        ReminderDirectiveReason.suppressFreeSlotRecord,
      );
      expect(
        service.directiveForTask(task: completed, now: now).reason,
        ReminderDirectiveReason.suppressInactiveStatus,
      );
      expect(
        service.directiveForTask(task: unscheduled, now: now).reason,
        ReminderDirectiveReason.suppressNoScheduledWindow,
      );
    });
  });
}

/// Runs the `scheduledTask` top-level operation.
/// This function is part of the file-level API and gives callers a direct way to use the associated scheduler behavior.
Task scheduledTask({
  required String id,
  required DateTime now,
  TaskType type = TaskType.flexible,
  TaskStatus status = TaskStatus.planned,
  ReminderProfile? reminderOverride,
  DateTime? start,
}) {
  final scheduledStart = start ?? now;
  return Task(
    id: id,
    title: 'Task',
    projectId: 'home',
    type: type,
    status: status,
    durationMinutes: 30,
    scheduledStart: scheduledStart,
    scheduledEnd: scheduledStart.add(const Duration(minutes: 30)),
    reminderOverride: reminderOverride,
    createdAt: now,
    updatedAt: now,
  );
}

/// Runs the `freeSlotTask` top-level operation.
/// This function is part of the file-level API and gives callers a direct way to use the associated scheduler behavior.
Task freeSlotTask({
  required String id,
  required DateTime start,
  required DateTime end,
  required DateTime createdAt,
}) {
  return const FreeSlotService().create(
    id: id,
    title: 'Rest',
    start: start,
    end: end,
    createdAt: createdAt,
  );
}
