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

/// Tests Document Migration behavior for the Scheduler Core package.
library;

import 'dart:convert';
import 'dart:io';

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('V0 to V1 document migrations', () {
    final migratedAt = DateTime.utc(2026, 6, 25, 12);
    const ownerId = 'owner-1';
    final service = V1DocumentMigrationService(
      ownerId: ownerId,
      migratedAt: migratedAt,
    );

    test('legacy minimum task migrates with backlog provenance', () {
      final legacy = fixture('task_v0_minimum.json');

      final result = service.migrateTask(legacy);
      final document = result.document!;
      final task = TaskDocumentMapping.fromDocument(document);

      expect(result.isSuccess, isTrue);
      expect(result.shouldWrite, isTrue);
      expect(result.report.status, DocumentMigrationStatus.migrated);
      expect(result.report.fromSchemaVersion, 0);
      expect(result.report.documentId, 'task-v0-minimum');
      expect(result.report.notes.single.code,
          DocumentMigrationNoteCode.approximatedBacklogEnteredAt);
      expect(legacy.containsKey(DocumentFields.schemaVersion), isFalse);
      expect(document, containsPair(DocumentFields.schemaVersion, 1));
      expect(document, containsPair(DocumentFields.ownerId, ownerId));
      expect(document, containsPair(DocumentFields.revision, 1));
      expect(document, containsPair(TaskDocumentFields.reward, 'not_set'));
      expect(document, containsPair(TaskDocumentFields.difficulty, 'not_set'));
      expect(
        document,
        containsPair(
          TaskDocumentFields.backlogEnteredAt,
          '2026-06-20T09:15:00.000Z',
        ),
      );
      expect(
        document,
        containsPair(
          TaskDocumentFields.backlogEnteredAtProvenance,
          DocumentMigrationProvenance.approximatedFromCreatedAt,
        ),
      );
      expect(task.id, 'task-v0-minimum');
      expect(task.status, TaskStatus.backlog);
      expect(task.stats.manuallyPushedCount, 0);
    });

    test('legacy full task migrates enum names to stable codes', () {
      final legacy = fixture('task_v0_full.json');

      final result = service.migrateTask(legacy);
      final document = result.document!;
      final task = TaskDocumentMapping.fromDocument(document);

      expect(result.isSuccess, isTrue);
      expect(document[TaskDocumentFields.type], 'free_slot');
      expect(document[TaskDocumentFields.status], 'no_longer_relevant');
      expect(document[TaskDocumentFields.priority], 'very_high');
      expect(document[TaskDocumentFields.reward], 'very_low');
      expect(document[TaskDocumentFields.difficulty], 'very_hard');
      expect(document[TaskDocumentFields.backlogTags], ['wishlist']);
      expect(document[TaskDocumentFields.backlogEnteredAt], isNull);
      expect(document[TaskDocumentFields.backlogEnteredAtProvenance], isNull);
      expect(task.type, TaskType.freeSlot);
      expect(task.status, TaskStatus.noLongerRelevant);
      expect(task.priority, PriorityLevel.veryHigh);
      expect(task.reward, RewardLevel.veryLow);
      expect(task.difficulty, DifficultyLevel.veryHard);
      expect(task.stats.totalPushesBeforeCompletion, 13);
    });

    test('legacy project and locked documents migrate to V1 shapes', () {
      final projectResult = service.migrateProject(fixture('project_v0.json'));
      final blockResult =
          service.migrateLockedBlock(fixture('locked_block_v0.json'));
      final overrideResult = service.migrateLockedBlockOverride(
        fixture('locked_override_v0.json'),
      );

      expect(projectResult.isSuccess, isTrue);
      expect(
        projectResult.report.notes.single.code,
        DocumentMigrationNoteCode.synthesizedMetadataTimestamp,
      );
      expect(
        projectResult.document![ProjectDocumentFields.defaultPriority],
        'very_high',
      );
      expect(
        projectResult.document![ProjectDocumentFields.defaultReward],
        'not_set',
      );
      expect(
        projectResult.document![ProjectDocumentFields.defaultDifficulty],
        'very_easy',
      );
      expect(
        projectResult.document![DocumentFields.createdAt],
        '2026-06-25T12:00:00.000Z',
      );
      expect(
        ProjectDocumentMapping.fromDocument(projectResult.document!).id,
        'project-v0-home',
      );

      final recurrence =
          blockResult.document![LockedBlockDocumentFields.recurrence]
              as Map<String, Object?>;
      expect(blockResult.isSuccess, isTrue);
      expect(
          blockResult.document![LockedBlockDocumentFields.startTime], '09:00');
      expect(blockResult.document![LockedBlockDocumentFields.endTime], '17:00');
      expect(
        recurrence[LockedBlockRecurrenceDocumentFields.type],
        'weekly',
      );
      expect(
        recurrence[LockedBlockRecurrenceDocumentFields.weekdays],
        ['monday', 'thursday'],
      );
      expect(
        LockedBlockDocumentMapping.fromDocument(blockResult.document!)
            .recurrence!
            .weekdays,
        {LockedWeekday.monday, LockedWeekday.thursday},
      );

      expect(overrideResult.isSuccess, isTrue);
      expect(
        overrideResult.document![LockedBlockOverrideDocumentFields.date],
        '2026-06-25',
      );
      expect(
        overrideResult.document![LockedBlockOverrideDocumentFields.startTime],
        '10:00',
      );
      expect(
        overrideResult.document![LockedBlockOverrideDocumentFields.endTime],
        '15:00',
      );
      expect(
        LockedBlockOverrideDocumentMapping.fromDocument(
          overrideResult.document!,
        ).date,
        CivilDate(2026, 6, 25),
      );
    });

    test('already-current V1 documents are validated without rewrite', () {
      final migrated = service
          .migrateTask(
            fixture('task_v0_minimum.json'),
          )
          .document!;

      final result = service.migrateTask(migrated);

      expect(result.isSuccess, isTrue);
      expect(result.shouldWrite, isFalse);
      expect(result.report.status, DocumentMigrationStatus.alreadyCurrent);
      expect(result.report.fromSchemaVersion, 1);
      expect(result.document, migrated);
    });

    test('repeated migration is idempotent', () {
      final first = service.migrateTask(fixture('task_v0_full.json'));
      final second = service.migrateTask(first.document!);

      expect(first.isSuccess, isTrue);
      expect(second.isSuccess, isTrue);
      expect(second.report.status, DocumentMigrationStatus.alreadyCurrent);
      expect(second.document, first.document);
    });

    test('partial corruption produces a typed failure report before writes',
        () {
      final legacy = fixture('task_v0_malformed_missing_title.json');

      final result = service.migrateTask(legacy);

      expect(result.isSuccess, isFalse);
      expect(result.shouldWrite, isFalse);
      expect(result.document, isNull);
      expect(result.report.status, DocumentMigrationStatus.failed);
      expect(
        result.report.failureCode,
        DocumentMigrationFailureCode.legacyDocumentFailure,
      );
      expect(result.report.fieldName, TaskDocumentFields.title);
      expect(result.report.detailCode, 'missingField');
      expect(legacy.containsKey(DocumentFields.schemaVersion), isFalse);
    });

    test('ambiguous legacy date-times fail instead of guessing a zone', () {
      final result = service.migrateTask(
        fixture('task_v0_ambiguous_datetime.json'),
      );

      expect(result.isSuccess, isFalse);
      expect(result.shouldWrite, isFalse);
      expect(result.document, isNull);
      expect(
        result.report.failureCode,
        DocumentMigrationFailureCode.legacyDocumentFailure,
      );
      expect(result.report.fieldName, TaskDocumentFields.createdAt);
      expect(result.report.detailCode, 'ambiguousDateTime');
    });

    test('unsupported future schema versions fail closed', () {
      final result = service.migrateTask(fixture('task_future_schema.json'));

      expect(result.isSuccess, isFalse);
      expect(result.shouldWrite, isFalse);
      expect(result.document, isNull);
      expect(
        result.report.failureCode,
        DocumentMigrationFailureCode.unsupportedSchemaVersion,
      );
      expect(result.report.fromSchemaVersion, 99);
      expect(result.report.fieldName, DocumentFields.schemaVersion);
    });
  });
}

/// Runs the `fixture` top-level operation.
/// This function is part of the file-level API and gives callers a direct way to use the associated scheduler behavior.
Map<String, Object?> fixture(String name) {
  var file = File('test/fixtures/migration/$name');
  if (!file.existsSync()) {
    file = File('packages/scheduler_core/test/fixtures/migration/$name');
  }
  final decoded = jsonDecode(file.readAsStringSync()) as Map<String, dynamic>;
  return Map<String, Object?>.from(decoded);
}
