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

/// Tests Backlog board controller behavior.
library;

import 'package:flutter_test/flutter_test.dart';
import 'package:focus_flow_flutter/controllers/backlog_board_controller.dart';
import 'package:focus_flow_flutter/controllers/selected_date_controller.dart';
import 'package:scheduler_core/scheduler_core.dart';

/// Runs Backlog board controller tests.
void main() {
  group('BacklogBoardController', () {
    test('load reads selected date and exposes ready data', () async {
      BacklogBoardReadRequest? capturedRequest;
      final selectedDates = SelectedDateController(
        initialDate: CivilDate(2026, 7, 7),
      );
      final controller = BacklogBoardController(
        selectedDates: selectedDates,
        dateLabelFor: (date) => date.toIsoString(),
        readBoard: (request) async {
          capturedRequest = request;
          return ApplicationResult.success(_board(taskCount: 1));
        },
        readTaskDetail: _detailReader,
      );
      addTearDown(controller.dispose);

      await controller.load();

      expect(capturedRequest!.localDate, CivilDate(2026, 7, 7));
      expect(capturedRequest!.sortKey, BacklogSortKey.custom);
      expect(capturedRequest!.boardFilters.isEmpty, isTrue);
      final state = controller.state;
      expect(state, isA<BacklogBoardReady>());
      expect((state as BacklogBoardReady).data.summary.totalTaskCount, 1);
    });

    test('load maps empty and failure states', () async {
      final selectedDates = SelectedDateController(
        initialDate: CivilDate(2026, 7, 7),
      );
      final controller = BacklogBoardController(
        selectedDates: selectedDates,
        dateLabelFor: (date) => date.toIsoString(),
        readBoard: (_) async {
          return ApplicationResult.success(_board(taskCount: 0));
        },
        readTaskDetail: _detailReader,
      );
      addTearDown(controller.dispose);

      await controller.load();
      expect(controller.state, isA<BacklogBoardEmpty>());

      final failing = BacklogBoardController(
        selectedDates: selectedDates,
        dateLabelFor: (date) => date.toIsoString(),
        readBoard: (_) async {
          return ApplicationResult.failure(
            const ApplicationFailure(
              code: ApplicationFailureCode.validation,
              detailCode: 'readFailed',
            ),
          );
        },
        readTaskDetail: _detailReader,
      );
      addTearDown(failing.dispose);

      await failing.load();
      final state = failing.state;
      expect(state, isA<BacklogBoardFailure>());
      expect((state as BacklogBoardFailure).message, 'readFailed');
    });

    test('selection loads detail without persisting selection', () async {
      final selectedDates = SelectedDateController(
        initialDate: CivilDate(2026, 7, 7),
      );
      var detailReads = 0;
      final controller = BacklogBoardController(
        selectedDates: selectedDates,
        dateLabelFor: (date) => date.toIsoString(),
        readBoard: (_) async {
          return ApplicationResult.success(_board(taskCount: 1));
        },
        readTaskDetail: (taskId, localDate) async {
          detailReads += 1;
          expect(taskId, 'task-1');
          expect(localDate, CivilDate(2026, 7, 7));
          return ApplicationResult.success(_detail());
        },
      );
      addTearDown(controller.dispose);

      await controller.load();
      await controller.selectTask('task-1');

      final state = controller.state as BacklogBoardReady;
      expect(detailReads, 1);
      expect(state.data.selectedTaskId, 'task-1');

      controller.clearSelection();

      final cleared = controller.state as BacklogBoardReady;
      expect(cleared.data.selectedTaskId, isNull);
    });

    test('detail failures stay local to the selected drawer', () async {
      final selectedDates = SelectedDateController(
        initialDate: CivilDate(2026, 7, 7),
      );
      final controller = BacklogBoardController(
        selectedDates: selectedDates,
        dateLabelFor: (date) => date.toIsoString(),
        readBoard: (_) async {
          return ApplicationResult.success(_board(taskCount: 1));
        },
        readTaskDetail: (taskId, localDate) async {
          return ApplicationResult.failure(
            const ApplicationFailure(
              code: ApplicationFailureCode.notFound,
              detailCode: 'taskNotFound',
            ),
          );
        },
      );
      addTearDown(controller.dispose);

      await controller.load();
      await controller.selectTask('task-1');

      expect(controller.state, isA<BacklogBoardReady>());
      expect(controller.selectedTaskId, 'task-1');
      expect(controller.selectedTaskDetail, isNull);
      expect(controller.isSelectedTaskDetailLoading, isFalse);
      expect(controller.selectedTaskDetailError, 'taskNotFound');
    });
  });
}

/// Test detail reader that returns a stable detail model.
Future<ApplicationResult<BacklogTaskDetailReadModel>> _detailReader(
  String taskId,
  CivilDate localDate,
) async {
  return ApplicationResult.success(_detail());
}

/// Builds a Backlog board query result with [taskCount].
BacklogBoardQueryResult _board({required int taskCount}) {
  final items = [
    for (var index = 0; index < taskCount; index += 1)
      _item(id: 'task-${index + 1}'),
  ];
  final totalEstimatedMinutes = items.fold<int>(
    0,
    (sum, item) => sum + (item.durationMinutes ?? 0),
  );
  return BacklogBoardQueryResult(
    ownerId: 'owner-1',
    localDate: CivilDate(2026, 7, 7),
    columns: [
      BacklogBoardColumnReadModel(
        bucket: BacklogBoardBucket.doNext,
        title: 'Do Next',
        subtitle: 'Ready.',
        accentToken: 'green',
        items: items,
      ),
      BacklogBoardColumnReadModel(
        bucket: BacklogBoardBucket.needTimeBlock,
        title: 'Need Time Block',
        subtitle: 'Block.',
        accentToken: 'yellow',
        items: const [],
      ),
      BacklogBoardColumnReadModel(
        bucket: BacklogBoardBucket.breakUpFirst,
        title: 'Break Up First',
        subtitle: 'Split.',
        accentToken: 'purple',
        items: const [],
      ),
      BacklogBoardColumnReadModel(
        bucket: BacklogBoardBucket.notNow,
        title: 'Not Now',
        subtitle: 'Later.',
        accentToken: 'blue',
        items: const [],
      ),
    ],
    summary: BacklogBoardSummaryReadModel(
      totalTaskCount: taskCount,
      totalEstimatedMinutes: totalEstimatedMinutes,
      priorityDistribution: BacklogDistributionReadModel(
        title: 'Priority',
        entries: const [],
      ),
      projectDistribution: BacklogDistributionReadModel(
        title: 'Project',
        entries: const [],
      ),
      durationDistribution: BacklogDistributionReadModel(
        title: 'Duration',
        entries: const [],
      ),
      planningTip: 'Pick one task.',
    ),
    projectOptions: const [],
  );
}

/// Builds a Backlog board item for tests.
BacklogBoardItemReadModel _item({required String id}) {
  return BacklogBoardItemReadModel(
    taskId: id,
    title: 'Task $id',
    projectId: 'home',
    projectName: 'Home',
    projectColorToken: 'project-home',
    durationMinutes: 30,
    priority: PriorityLevel.medium,
    reward: RewardLevel.low,
    difficulty: DifficultyLevel.easy,
    bucket: BacklogBoardBucket.doNext,
    bucketReason: 'Ready.',
    createdAt: DateTime.utc(2026, 7, 7),
    updatedAt: DateTime.utc(2026, 7, 7),
    childPreview: const [],
    tags: const [],
  );
}

/// Builds a Backlog task detail model for tests.
BacklogTaskDetailReadModel _detail() {
  return BacklogTaskDetailReadModel(
    item: _item(id: 'task-1'),
    addedLabel: 'Added today',
    tags: const [],
    projectOptions: const [],
    suggestedSlots: const [],
  );
}
