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

/// Tests Today screen controller date-aware read behavior.
library;

import 'dart:async';

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

import 'package:focus_flow_flutter/controllers/selected_date_controller.dart';
import 'package:focus_flow_flutter/controllers/today_screen_controller.dart';

/// Runs Today screen controller tests.
void main() {
  test(
    'load reads the selected date and shows empty data for that date',
    () async {
      final selectedDates = SelectedDateController(initialDate: _july2);
      addTearDown(selectedDates.dispose);
      final reads = <CivilDate>[];
      final controller = TodayScreenController(
        selectedDates: selectedDates,
        dateLabelFor: _label,
        read: (date) async {
          reads.add(date);
          return ApplicationResult.success(_todayState(date));
        },
      );
      addTearDown(controller.dispose);

      await controller.load();

      expect(reads, [_july2]);
      final state = controller.state;
      expect(state, isA<TodayScreenEmpty>());
      expect((state as TodayScreenEmpty).data.dateLabel, 'July 2, 2026');
    },
  );

  test(
    'date changes trigger distinct reads and clear selected cards',
    () async {
      final selectedDates = SelectedDateController(initialDate: _july2);
      addTearDown(selectedDates.dispose);
      final reads = <CivilDate>[];
      final controller = TodayScreenController(
        selectedDates: selectedDates,
        dateLabelFor: _label,
        read: (date) async {
          reads.add(date);
          return ApplicationResult.success(
            _todayState(
              date,
              items: date == _july2 ? [_timelineItem('first-task', date)] : [],
            ),
          );
        },
      );
      addTearDown(controller.dispose);

      await controller.load();
      final ready = controller.state as TodayScreenReady;
      controller.selectCard(ready.data.cards.single);
      expect(controller.selectedCard?.id, 'first-task');

      selectedDates.selectNextDay();
      await _flushAsyncWork();

      expect(reads, [_july2, _july3]);
      expect(controller.selectedCard, isNull);
      final state = controller.state;
      expect(state, isA<TodayScreenEmpty>());
      expect((state as TodayScreenEmpty).data.dateLabel, 'July 3, 2026');
    },
  );

  test('read failures keep selected date without showing old data', () async {
    final selectedDates = SelectedDateController(initialDate: _july2);
    addTearDown(selectedDates.dispose);
    final controller = TodayScreenController(
      selectedDates: selectedDates,
      dateLabelFor: _label,
      read: (date) async {
        return ApplicationResult.failure(
          const ApplicationFailure(
            code: ApplicationFailureCode.persistenceFailure,
            detailCode: 'readFailed',
          ),
        );
      },
    );
    addTearDown(controller.dispose);

    await controller.load();

    expect(controller.selectedDate, _july2);
    final state = controller.state;
    expect(state, isA<TodayScreenFailure>());
    expect((state as TodayScreenFailure).message, 'readFailed');
    expect(state.data.dateLabel, 'July 2, 2026');
  });

  test(
    'older in-flight reads cannot overwrite the latest selected date',
    () async {
      final selectedDates = SelectedDateController(initialDate: _july2);
      addTearDown(selectedDates.dispose);
      final firstRead = Completer<ApplicationResult<TodayState>>();
      final secondRead = Completer<ApplicationResult<TodayState>>();
      final controller = TodayScreenController(
        selectedDates: selectedDates,
        dateLabelFor: _label,
        read: (date) {
          if (date == _july2) {
            return firstRead.future;
          }
          if (date == _july3) {
            return secondRead.future;
          }
          throw StateError('Unexpected read date: ${date.toIsoString()}');
        },
      );
      addTearDown(controller.dispose);

      final firstLoad = controller.load();
      selectedDates.selectNextDay();
      await _flushAsyncWork();
      secondRead.complete(ApplicationResult.success(_todayState(_july3)));
      await _flushAsyncWork();

      final latest = controller.state;
      expect(latest, isA<TodayScreenEmpty>());
      expect((latest as TodayScreenEmpty).data.dateLabel, 'July 3, 2026');

      firstRead.complete(ApplicationResult.success(_todayState(_july2)));
      await firstLoad;
      await _flushAsyncWork();

      final unchanged = controller.state;
      expect(unchanged, isA<TodayScreenEmpty>());
      expect((unchanged as TodayScreenEmpty).data.dateLabel, 'July 3, 2026');
    },
  );
}

/// First controller test date.
final _july2 = CivilDate(2026, 7, 2);

/// Next controller test date.
final _july3 = CivilDate(2026, 7, 3);

/// Formats [date] as a long month/day/year label.
String _label(CivilDate date) {
  const months = [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December',
  ];
  return '${months[date.month - 1]} ${date.day}, ${date.year}';
}

/// Waits for microtask-scheduled controller reloads.
Future<void> _flushAsyncWork() async {
  await Future<void>.delayed(Duration.zero);
  await Future<void>.delayed(Duration.zero);
}

/// Builds a lightweight Today state for [date].
TodayState _todayState(
  CivilDate date, {
  List<TodayTimelineItem> items = const [],
}) {
  final dayStart = DateTime.utc(date.year, date.month, date.day);
  return TodayState(
    date: date,
    ownerId: 'owner-1',
    timeZoneId: 'UTC',
    readAt: dayStart.add(const Duration(hours: 12)),
    dayStart: dayStart,
    dayEnd: dayStart.add(const Duration(days: 1)),
    ownerSettings: OwnerSettings(ownerId: 'owner-1', timeZoneId: 'UTC'),
    timelineItems: items,
    compactState: const TodayCompactState(
      manualCompactModeEnabled: true,
      fullTimelineExpanded: false,
    ),
    pendingNotices: const [],
  );
}

/// Builds one planned timeline item on [date].
TodayTimelineItem _timelineItem(String id, CivilDate date) {
  final start = DateTime.utc(date.year, date.month, date.day, 9);
  return TodayTimelineItem(
    item: TimelineItem(
      id: id,
      displayTitle: 'Task $id',
      taskType: TaskType.flexible,
      projectColorToken: 'project-home',
      backgroundToken: TimelineBackgroundToken.flexible,
      rewardIconToken: TimelineRewardIconToken.medium,
      difficultyIconToken: TimelineDifficultyIconToken.medium,
      showsExplicitTime: true,
      quickActions: const [],
      category: TimelineItemCategory.taskCard,
      start: start,
      end: start.add(const Duration(minutes: 30)),
      durationMinutes: 30,
    ),
    source: TodayTimelineItemSource.task,
    taskStatus: TaskStatus.planned,
    taskId: id,
  );
}
