import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gametime/application/application.dart';
import 'package:gametime/domain/domain.dart';
import 'package:gametime/presentation/share_inbox_screen.dart';

void main() {
  testWidgets('la boîte de réception affiche les partages et les actions', (
    tester,
  ) async {
    final remoteShareApi = _FakeRemoteShareApi()
      ..inboxItems = [
        _shareItem(
          shareId: 'share-program',
          resourceType: ShareResourceType.program,
          name: 'Programme tirs',
          status: ShareInboxStatus.pending,
        ),
        _shareItem(
          shareId: 'share-template',
          resourceType: ShareResourceType.workoutTemplate,
          name: 'Séance match',
          status: ShareInboxStatus.pending,
        ),
      ];
    final inboxRepository = _FakeShareInboxRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: ShareInboxScreen(
          shareUseCases: _shareUseCases(
            remoteShareApi: remoteShareApi,
            inboxRepository: inboxRepository,
          ),
        ),
      ),
    );
    await tester.pump();
    await tester.pump();

    expect(find.text('Programme tirs'), findsOneWidget);
    expect(find.text('Séance match'), findsOneWidget);
    expect(find.text('Accepter'), findsNWidgets(2));
    expect(find.text('Refuser'), findsNWidgets(2));

    await tester.tap(find.widgetWithText(FilledButton, 'Accepter').first);
    await tester.pump();
    await tester.pump();

    expect(remoteShareApi.acceptCalls, 1);
    expect(
      inboxRepository.items
          .firstWhere((item) => item.shareId == 'share-program')
          .status,
      ShareInboxStatus.accepted,
    );
    expect(find.text('Programme ajouté.'), findsOneWidget);

    await tester.tap(find.widgetWithText(OutlinedButton, 'Refuser').first);
    await tester.pump();
    await tester.pump();

    expect(remoteShareApi.declineCalls, 1);
    expect(
      inboxRepository.items
          .firstWhere((item) => item.shareId == 'share-template')
          .status,
      ShareInboxStatus.declined,
    );
  });

  testWidgets(
    'accepter un partage en cache hors connexion importe une copie locale',
    (tester) async {
      final remoteShareApi = _FakeRemoteShareApi();
      final inboxRepository = _FakeShareInboxRepository()
        ..items.add(
          _shareItem(
            shareId: 'share-program',
            resourceType: ShareResourceType.program,
            name: 'Programme tirs',
            status: ShareInboxStatus.pending,
          ),
        );
      final programRepository = _FakeProgramRepository();
      final pendingRepository = _FakePendingShareActionRepository();

      await tester.pumpWidget(
        MaterialApp(
          home: ShareInboxScreen(
            shareUseCases: _shareUseCases(
              tokenStore: _FakeAuthTokenStore(null),
              remoteShareApi: remoteShareApi,
              inboxRepository: inboxRepository,
              pendingActionRepository: pendingRepository,
              programRepository: programRepository,
            ),
          ),
        ),
      );
      await tester.pump();
      await tester.pump();

      await tester.tap(find.widgetWithText(FilledButton, 'Accepter'));
      await tester.pump();
      await tester.pump();

      expect(remoteShareApi.acceptCalls, 0);
      expect(programRepository.saved.single.name, 'Programme tirs');
      expect(
        programRepository.saved.single.metadata.id,
        isNot('share-program'),
      );
      expect(inboxRepository.items.single.status, ShareInboxStatus.accepted);
      expect(
        pendingRepository.actions.single.actionType,
        PendingShareActionType.accept,
      );
      expect(find.text('Programme ajouté.'), findsOneWidget);
    },
  );
}

ShareUseCases _shareUseCases({
  _FakeAuthTokenStore? tokenStore,
  required _FakeRemoteShareApi remoteShareApi,
  required _FakeShareInboxRepository inboxRepository,
  _FakePendingShareActionRepository? pendingActionRepository,
  _FakeProgramRepository? programRepository,
}) {
  return ShareUseCases(
    tokenStore: tokenStore ?? _FakeAuthTokenStore('token-1'),
    remoteShareApi: remoteShareApi,
    inboxRepository: inboxRepository,
    pendingActionRepository:
        pendingActionRepository ?? _FakePendingShareActionRepository(),
    localChanges: _FakeLocalSyncChangeRepository(),
    programRepository: programRepository ?? _FakeProgramRepository(),
    templateRepository: _FakeWorkoutTemplateRepository(),
    clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
    ids: _FakeIds(),
    originDeviceId: 'device-1',
  );
}

ShareInboxItem _shareItem({
  required String shareId,
  required ShareResourceType resourceType,
  required String name,
  required ShareInboxStatus status,
}) {
  final payload = switch (resourceType) {
    ShareResourceType.program => {
      'name': name,
      'exercises': const [
        {
          'exerciseNameSnapshot': 'Sprint',
          'setsCount': 3,
          'availableTimeSnapshot': false,
          'availableRepsSnapshot': true,
          'availableScoreSnapshot': false,
          'timeEnabled': false,
          'repsEnabled': true,
          'scoreEnabled': false,
          'targetReps': 10,
        },
      ],
    },
    ShareResourceType.workoutTemplate => {
      'name': name,
      'programs': [
        {
          'programSnapshotJson': jsonEncode({
            'exercises': const [
              {'id': 'exercise-1'},
            ],
          }),
        },
      ],
    },
  };
  return ShareInboxItem(
    shareId: shareId,
    senderUserId: 'Alex',
    resourceType: resourceType,
    payloadJson: jsonEncode(payload),
    status: status,
    createdAt: DateTime.utc(2026, 7, 17),
  );
}

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 _FakeAuthTokenStore implements AuthTokenStore {
  _FakeAuthTokenStore(this.token);

  String? token;

  @override
  Future<void> clearToken() async {
    token = null;
  }

  @override
  Future<String?> readToken() async => token;

  @override
  Future<void> saveToken(String token, DateTime expiresAt) async {
    this.token = token;
  }
}

final class _FakeRemoteShareApi implements RemoteShareApi {
  List<ShareInboxItem> inboxItems = const [];
  var acceptCalls = 0;
  var declineCalls = 0;

  @override
  Future<RemoteSyncedItem> acceptShare(String shareId, String token) async {
    acceptCalls += 1;
    _mark(shareId, ShareInboxStatus.accepted);
    return RemoteSyncedItem(
      resourceType: SyncResourceType.program,
      clientId: 'shared-program-1',
      serverId: 'server-program-1',
      schemaVersion: 1,
      clientUpdatedAt: DateTime.utc(2026, 7, 17),
      serverUpdatedAt: DateTime.utc(2026, 7, 17),
      deletedAt: null,
      payload: const {'name': 'Programme tirs'},
    );
  }

  @override
  Future<void> declineShare(String shareId, String token) async {
    declineCalls += 1;
    _mark(shareId, ShareInboxStatus.declined);
  }

  @override
  Future<List<ShareInboxItem>> fetchInbox(String token) async => inboxItems;

  @override
  Future<void> revokeShare(String shareId, String token) async {
    _mark(shareId, ShareInboxStatus.revoked);
  }

  @override
  Future<RemoteShareSendResult> sendShare({
    required ShareResourceType resourceType,
    required Map<String, Object?> payload,
    required List<String> recipientEmails,
    required String token,
  }) async {
    return const RemoteShareSendResult(
      shareId: 'share-1',
      recipientUserIds: ['recipient-1'],
      unresolvedEmails: [],
    );
  }

  void _mark(String shareId, ShareInboxStatus status) {
    inboxItems = [
      for (final item in inboxItems)
        if (item.shareId == shareId)
          item.copyWith(status: status, respondedAt: DateTime.utc(2026, 7, 17))
        else
          item,
    ];
  }
}

final class _FakeShareInboxRepository implements ShareInboxRepository {
  final items = <ShareInboxItem>[];

  @override
  Future<ShareInboxItem?> findByShareId(String shareId) async {
    return items.cast<ShareInboxItem?>().firstWhere(
      (item) => item?.shareId == shareId,
      orElse: () => null,
    );
  }

  @override
  Future<List<ShareInboxItem>> listAll() async => List.of(items);

  @override
  Future<void> markStatus(
    String shareId,
    ShareInboxStatus status,
    DateTime respondedAt,
  ) async {
    final index = items.indexWhere((item) => item.shareId == shareId);
    if (index == -1) return;
    items[index] = items[index].copyWith(
      status: status,
      respondedAt: respondedAt,
    );
  }

  @override
  Future<void> upsert(ShareInboxItem item) async {
    items.removeWhere((existing) => existing.shareId == item.shareId);
    items.add(item);
  }

  @override
  Future<void> upsertAll(List<ShareInboxItem> items) async {
    for (final item in items) {
      await upsert(item);
    }
  }
}

final class _FakePendingShareActionRepository
    implements PendingShareActionRepository {
  final actions = <PendingShareAction>[];

  @override
  Future<void> add(PendingShareAction action) async {
    actions.add(action);
  }

  @override
  Future<List<PendingShareAction>> listPending() async => const [];

  @override
  Future<void> markFailed(String id, DateTime attemptedAt) async {}

  @override
  Future<void> markSucceeded(String id, DateTime attemptedAt) async {}
}

final class _FakeLocalSyncChangeRepository
    implements LocalSyncChangeRepository {
  @override
  Future<bool> applyRemoteItem(RemoteSyncedItem item) async => true;

  @override
  Future<List<PendingSyncChange>> listPendingChanges() async => const [];

  @override
  Future<void> markChangesSynced(
    List<String> changeLogIds,
    DateTime syncedAt,
  ) async {}
}

final class _FakeProgramRepository implements ProgramRepository {
  final saved = <Program>[];

  @override
  Future<Program?> findById(String id) async => null;

  @override
  Future<List<Program>> listActive() async => const [];

  @override
  Future<void> replaceExercises(Program program, DateTime deletedAt) async {}

  @override
  Future<void> save(Program program) async {
    saved.add(program);
  }

  @override
  Future<void> saveExercise(ProgramExercise exercise) async {}
}

final class _FakeWorkoutTemplateRepository
    implements WorkoutTemplateRepository {
  @override
  Future<WorkoutTemplate?> findById(String id) async => null;

  @override
  Future<List<WorkoutTemplate>> listActive() async => const [];

  @override
  Future<void> replaceComposition(
    WorkoutTemplate template,
    DateTime deletedAt,
  ) async {}

  @override
  Future<void> save(WorkoutTemplate template) async {}

  @override
  Future<void> saveOverride(WorkoutTemplateExerciseOverride override) async {}

  @override
  Future<void> saveProgram(WorkoutTemplateProgram program) async {}
}
