import 'package:flutter_test/flutter_test.dart';
import 'package:gametime/application/application.dart';
import 'package:gametime/domain/domain.dart';

void main() {
  test('imports media and stores sync-ready asset metadata', () async {
    final mediaRepository = _FakeMediaAssetRepository();
    final useCase = MediaUseCases(
      mediaRepository: mediaRepository,
      exerciseRepository: _FakeExerciseRepository(),
      storage: _FakeMediaStorage(),
      clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
      ids: _FakeIds(),
      originDeviceId: 'device-1',
    );

    final asset = await useCase.importMedia(
      sourcePath: '/tmp/source.png',
      kind: MediaKind.image,
    );

    expect(asset.kind, MediaKind.image);
    expect(asset.localUri, 'file:///app/media/id-1.png');
    expect(asset.mimeType, 'image/png');
    expect(asset.sizeBytes, 128);
    expect(asset.width, 10);
    expect(asset.height, 20);
    expect(asset.remoteUri, isNull);
    expect(asset.checksum, isNull);
    expect(mediaRepository.saved.single.metadata.id, asset.metadata.id);
  });

  test('attaches imported image to exercise image field', () async {
    final exerciseRepository = _FakeExerciseRepository()
      ..exercise = Exercise(
        metadata: _metadata('exercise-1'),
        name: 'Squat',
        hasTimeMeasure: false,
        hasRepsMeasure: true,
        hasScoreMeasure: false,
        defaultTargetReps: 10,
      );
    final mediaRepository = _FakeMediaAssetRepository()
      ..asset = MediaAsset(
        metadata: _metadata('media-1'),
        kind: MediaKind.image,
        localUri: 'file:///app/media/media-1.png',
      );
    final useCase = MediaUseCases(
      mediaRepository: mediaRepository,
      exerciseRepository: exerciseRepository,
      storage: _FakeMediaStorage(),
      clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
      ids: _FakeIds(),
      originDeviceId: 'device-1',
    );

    final updated = await useCase.attachToExercise(
      exerciseId: 'exercise-1',
      mediaAssetId: 'media-1',
    );

    expect(updated.imageMediaId, 'media-1');
    expect(updated.videoMediaId, isNull);
    expect(exerciseRepository.saved.single.imageMediaId, 'media-1');
  });

  test('deletes managed files not referenced by active media assets', () async {
    final storage = _FakeMediaStorage()
      ..managedUris = {
        'file:///app/media/kept.png',
        'file:///app/media/orphan.png',
      };
    final mediaRepository = _FakeMediaAssetRepository()
      ..activeAssets = [
        MediaAsset(
          metadata: _metadata('media-1'),
          kind: MediaKind.image,
          localUri: 'file:///app/media/kept.png',
        ),
      ];
    final useCase = MediaUseCases(
      mediaRepository: mediaRepository,
      exerciseRepository: _FakeExerciseRepository(),
      storage: storage,
      clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
      ids: _FakeIds(),
      originDeviceId: 'device-1',
    );

    final deletedCount = await useCase.deleteOrphanedManagedFiles();

    expect(deletedCount, 1);
    expect(storage.deletedUris, ['file:///app/media/orphan.png']);
  });
}

EntityMetadata _metadata(String id) {
  return EntityMetadata(
    id: id,
    createdAt: DateTime.utc(2026, 7, 17),
    updatedAt: DateTime.utc(2026, 7, 17),
    originDeviceId: 'device-1',
  );
}

final class _FakeClock implements Clock {
  const _FakeClock(this.value);

  final DateTime value;

  @override
  DateTime now() => value;
}

final class _FakeIds implements IdGenerator {
  var _next = 0;

  @override
  String newId() {
    _next += 1;
    return 'id-$_next';
  }
}

final class _FakeMediaStorage implements LocalMediaStorage {
  Set<String> managedUris = const {};
  final deletedUris = <String>[];

  @override
  Future<StoredMediaFile> importFile({
    required String sourcePath,
    required MediaKind kind,
    required String stableFileName,
  }) async {
    return StoredMediaFile(
      localUri: 'file:///app/media/$stableFileName.png',
      mimeType: 'image/png',
      sizeBytes: 128,
      width: 10,
      height: 20,
    );
  }

  @override
  Future<Set<String>> listManagedLocalUris() async => managedUris;

  @override
  Future<void> deleteByLocalUri(String localUri) async {
    deletedUris.add(localUri);
  }
}

final class _FakeMediaAssetRepository implements MediaAssetRepository {
  MediaAsset? asset;
  List<MediaAsset> activeAssets = const [];
  final saved = <MediaAsset>[];

  @override
  Future<MediaAsset?> findById(String id) async => asset;

  @override
  Future<List<MediaAsset>> listActive() async => activeAssets;

  @override
  Future<void> save(MediaAsset mediaAsset) async {
    saved.add(mediaAsset);
    asset = mediaAsset;
  }
}

final class _FakeExerciseRepository implements ExerciseRepository {
  Exercise? exercise;
  final saved = <Exercise>[];

  @override
  Future<Exercise?> findById(String id) async => exercise;

  @override
  Future<List<Exercise>> listActive() async {
    return exercise == null ? const [] : [exercise!];
  }

  @override
  Future<bool> isReferencedByProgram(String id) async => false;

  @override
  Future<void> save(Exercise exercise) async {
    saved.add(exercise);
    this.exercise = exercise;
  }
}
