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/profile_screen.dart';

void main() {
  testWidgets('le profil déconnecté invite sans bloquer le reste', (
    tester,
  ) async {
    final harness = _AuthHarness();

    await tester.pumpWidget(
      MaterialApp(
        home: ProfileScreen(
          authUseCases: harness.useCases,
          syncUseCases: harness.syncUseCases,
          shareUseCases: harness.shareUseCases,
        ),
      ),
    );
    await tester.pump();

    expect(find.text('Compte optionnel'), findsOneWidget);
    expect(find.text('Créer un compte'), findsOneWidget);
    expect(find.text('Se connecter'), findsOneWidget);
    expect(find.byType(AlertDialog), findsNothing);
  });

  testWidgets('la connexion invalide affiche une erreur inline', (
    tester,
  ) async {
    final harness = _AuthHarness()
      ..remote.loginFailure = RemoteAuthFailure.invalidCredentials;

    await tester.pumpWidget(
      MaterialApp(
        home: ProfileScreen(
          authUseCases: harness.useCases,
          syncUseCases: harness.syncUseCases,
          shareUseCases: harness.shareUseCases,
        ),
      ),
    );
    await tester.pump();

    await tester.tap(find.text('Se connecter'));
    await tester.pumpAndSettle();
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Email'),
      'a@b.fr',
    );
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Mot de passe'),
      'password1',
    );
    await tester.tap(find.widgetWithText(FilledButton, 'Se connecter'));
    await tester.pumpAndSettle();

    expect(find.text('Email ou mot de passe incorrect.'), findsOneWidget);
    expect(find.byType(AlertDialog), findsNothing);
  });

  testWidgets('la connexion réussie affiche le profil connecté', (
    tester,
  ) async {
    final harness = _AuthHarness();

    await tester.pumpWidget(
      MaterialApp(
        home: ProfileScreen(
          authUseCases: harness.useCases,
          syncUseCases: harness.syncUseCases,
          shareUseCases: harness.shareUseCases,
        ),
      ),
    );
    await tester.pump();

    await tester.tap(find.text('Se connecter'));
    await tester.pumpAndSettle();
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Email'),
      'alex@example.com',
    );
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Mot de passe'),
      'password1',
    );
    await tester.tap(find.widgetWithText(FilledButton, 'Se connecter'));
    await tester.pumpAndSettle();

    expect(find.text('alex@example.com'), findsOneWidget);
    expect(find.text('Synchronisation'), findsOneWidget);
    expect(find.text('Partages reçus'), findsOneWidget);
    expect(harness.syncRemote.pullCalls, 1);
  });

  testWidgets('la déconnexion repasse au profil déconnecté', (tester) async {
    final harness = _AuthHarness(
      initialSession: UserAccountSession(
        id: 'account-1',
        serverUserId: 'server-user-1',
        email: 'alex@example.com',
        displayName: 'Alex',
        isLoggedIn: true,
        createdAt: DateTime.utc(2026, 7, 17),
        updatedAt: DateTime.utc(2026, 7, 17),
      ),
      initialToken: 'token-1',
    );

    await tester.pumpWidget(
      MaterialApp(
        home: ProfileScreen(
          authUseCases: harness.useCases,
          syncUseCases: harness.syncUseCases,
          shareUseCases: harness.shareUseCases,
        ),
      ),
    );
    await tester.pump();

    expect(find.text('Alex'), findsOneWidget);

    await tester.tap(find.text('Se déconnecter'));
    await tester.pumpAndSettle();
    await tester.tap(find.widgetWithText(FilledButton, 'Se déconnecter'));
    await tester.pumpAndSettle();

    expect(harness.tokenStore.token, isNull);
    expect(harness.accountRepository.session?.isLoggedIn, isFalse);
    expect(find.text('Compte optionnel'), findsOneWidget);
  });

  testWidgets(
    'le bouton de synchronisation lance une synchronisation manuelle',
    (tester) async {
      final harness = _AuthHarness.connected();

      await tester.pumpWidget(
        MaterialApp(
          home: ProfileScreen(
            authUseCases: harness.useCases,
            syncUseCases: harness.syncUseCases,
            shareUseCases: harness.shareUseCases,
          ),
        ),
      );
      await tester.pump();

      expect(find.text('Synchroniser maintenant'), findsOneWidget);

      await tester.tap(find.text('Synchroniser maintenant'));
      await tester.pump();
      await tester.pump();
      await tester.pump();

      expect(harness.syncRemote.pullCalls, 1);
    },
  );

  testWidgets('un souci de synchronisation reste neutre dans le profil', (
    tester,
  ) async {
    final harness = _AuthHarness.connected()..syncRemote.throwOnPull = true;

    await tester.pumpWidget(
      MaterialApp(
        home: ProfileScreen(
          authUseCases: harness.useCases,
          syncUseCases: harness.syncUseCases,
          shareUseCases: harness.shareUseCases,
        ),
      ),
    );
    await tester.pump();

    await tester.tap(find.text('Synchroniser maintenant'));
    await tester.pump();
    await tester.pump();
    await tester.pump();

    expect(harness.syncMetadata.metadata.status, OnlineSyncStatus.failure);
    expect(find.text('Synchronisation en attente'), findsOneWidget);
    expect(find.textContaining('Erreur'), findsNothing);
    expect(find.byType(AlertDialog), findsNothing);
  });

  testWidgets('le statut se met à jour après une synchronisation réussie', (
    tester,
  ) async {
    final harness = _AuthHarness.connected();

    await tester.pumpWidget(
      MaterialApp(
        home: ProfileScreen(
          authUseCases: harness.useCases,
          syncUseCases: harness.syncUseCases,
          shareUseCases: harness.shareUseCases,
        ),
      ),
    );
    await tester.pump();

    expect(find.text('Synchronisation en attente'), findsOneWidget);

    await tester.tap(find.text('Synchroniser maintenant'));
    await tester.pump();
    await tester.pump();
    await tester.pump();

    expect(find.textContaining('Dernière synchro :'), findsOneWidget);
  });

  testWidgets('partages reçus ouvre la boîte de réception', (tester) async {
    final harness = _AuthHarness.connected();

    await tester.pumpWidget(
      MaterialApp(
        home: ProfileScreen(
          authUseCases: harness.useCases,
          syncUseCases: harness.syncUseCases,
          shareUseCases: harness.shareUseCases,
        ),
      ),
    );
    await tester.pump();

    await tester.tap(find.text('Partages reçus'));
    await tester.pumpAndSettle();

    expect(find.text('Aucun partage reçu'), findsOneWidget);
  });
}

final class _AuthHarness {
  _AuthHarness({UserAccountSession? initialSession, String? initialToken})
    : tokenStore = _FakeAuthTokenStore(initialToken),
      accountRepository = _FakeOnlineAccountRepository(initialSession),
      remote = _FakeRemoteAuthApi(),
      syncRemote = _FakeRemoteSyncApi(),
      syncMetadata = _FakeSyncMetadataRepository(),
      syncMappings = _FakeRemoteResourceMappingRepository(),
      syncChanges = _FakeLocalSyncChangeRepository(),
      shareRemote = _FakeRemoteShareApi(),
      shareInbox = _FakeShareInboxRepository(),
      pendingShares = _FakePendingShareActionRepository(),
      programRepository = _FakeProgramRepository(),
      templateRepository = _FakeWorkoutTemplateRepository() {
    useCases = AuthUseCases(
      tokenStore: tokenStore,
      accountRepository: accountRepository,
      remoteAuthApi: remote,
      clock: const _FakeClock(),
      ids: _FakeIds(),
    );
    syncUseCases = SyncUseCases(
      tokenStore: tokenStore,
      remoteSyncApi: syncRemote,
      metadataRepository: syncMetadata,
      mappingRepository: syncMappings,
      localChanges: syncChanges,
      clock: const _FakeClock(),
      deviceId: 'device-1',
    );
    shareUseCases = ShareUseCases(
      tokenStore: tokenStore,
      remoteShareApi: shareRemote,
      inboxRepository: shareInbox,
      pendingActionRepository: pendingShares,
      localChanges: syncChanges,
      programRepository: programRepository,
      templateRepository: templateRepository,
      clock: const _FakeClock(),
      ids: _FakeIds(),
      originDeviceId: 'device-1',
    );
  }

  factory _AuthHarness.connected() {
    return _AuthHarness(
      initialSession: UserAccountSession(
        id: 'account-1',
        serverUserId: 'server-user-1',
        email: 'alex@example.com',
        displayName: 'Alex',
        isLoggedIn: true,
        createdAt: DateTime.utc(2026, 7, 17),
        updatedAt: DateTime.utc(2026, 7, 17),
      ),
      initialToken: 'token-1',
    );
  }

  final _FakeAuthTokenStore tokenStore;
  final _FakeOnlineAccountRepository accountRepository;
  final _FakeRemoteAuthApi remote;
  final _FakeRemoteSyncApi syncRemote;
  final _FakeSyncMetadataRepository syncMetadata;
  final _FakeRemoteResourceMappingRepository syncMappings;
  final _FakeLocalSyncChangeRepository syncChanges;
  final _FakeRemoteShareApi shareRemote;
  final _FakeShareInboxRepository shareInbox;
  final _FakePendingShareActionRepository pendingShares;
  final _FakeProgramRepository programRepository;
  final _FakeWorkoutTemplateRepository templateRepository;
  late final AuthUseCases useCases;
  late final SyncUseCases syncUseCases;
  late final ShareUseCases shareUseCases;
}

final class _FakeClock implements Clock {
  const _FakeClock();

  @override
  DateTime now() => DateTime.utc(2026, 7, 17, 12);
}

final class _FakeIds implements IdGenerator {
  var _next = 0;

  @override
  String newId() {
    _next += 1;
    return 'local-account-$_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 _FakeOnlineAccountRepository implements OnlineAccountRepository {
  _FakeOnlineAccountRepository(this.session);

  UserAccountSession? session;

  @override
  Future<void> clearSession() async {
    session = null;
  }

  @override
  Future<UserAccountSession?> currentSession() async => session;

  @override
  Future<void> saveSession(UserAccountSession session) async {
    this.session = session;
  }
}

final class _FakeRemoteAuthApi implements RemoteAuthApi {
  RemoteAuthFailure? loginFailure;
  RemoteAuthFailure? registerFailure;

  @override
  Future<RemoteAuthResult> login({
    required String email,
    required String password,
  }) async {
    final failure = loginFailure;
    if (failure != null) {
      throw RemoteAuthException(failure);
    }
    return RemoteAuthResult(
      userId: 'server-user-1',
      email: email.trim(),
      token: 'token-1',
      expiresAt: DateTime.utc(2026, 7, 18),
    );
  }

  @override
  Future<void> logout(String token) async {}

  @override
  Future<RemoteAuthResult> register({
    required String email,
    required String password,
    String? displayName,
  }) async {
    final failure = registerFailure;
    if (failure != null) {
      throw RemoteAuthException(failure);
    }
    return RemoteAuthResult(
      userId: 'server-user-1',
      email: email.trim(),
      token: 'token-1',
      expiresAt: DateTime.utc(2026, 7, 18),
    );
  }
}

final class _FakeRemoteSyncApi implements RemoteSyncApi {
  var pullCalls = 0;
  var pushCalls = 0;
  var throwOnPull = false;

  @override
  Future<RemoteSyncPushResult> push({
    required String deviceId,
    required List<RemoteSyncPushItem> items,
    required String token,
  }) async {
    pushCalls += 1;
    return const RemoteSyncPushResult(serverCursor: null, results: []);
  }

  @override
  Future<RemoteSyncPullResult> pull({
    required String? since,
    required String token,
  }) async {
    pullCalls += 1;
    if (throwOnPull) {
      throw Exception('temporary unavailable');
    }
    return const RemoteSyncPullResult(serverCursor: 'cursor-1', items: []);
  }
}

final class _FakeSyncMetadataRepository implements SyncMetadataRepository {
  var metadata = const SyncMetadataSnapshot();

  @override
  Future<SyncMetadataSnapshot> read() async => metadata;

  @override
  Future<void> save(SyncMetadataSnapshot metadata) async {
    this.metadata = metadata;
  }
}

final class _FakeRemoteResourceMappingRepository
    implements RemoteResourceMappingRepository {
  final _mappings = <String, RemoteResourceMapping>{};

  @override
  Future<RemoteResourceMapping?> find({
    required SyncResourceType resourceType,
    required String clientId,
  }) async {
    return _mappings['${resourceType.name}:$clientId'];
  }

  @override
  Future<void> save(RemoteResourceMapping mapping) async {
    _mappings['${mapping.resourceType.name}:${mapping.clientId}'] = mapping;
  }
}

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 _FakeRemoteShareApi implements RemoteShareApi {
  @override
  Future<RemoteSyncedItem> acceptShare(String shareId, String token) async {
    return RemoteSyncedItem(
      resourceType: SyncResourceType.program,
      clientId: 'program-remote',
      serverId: 'program-server',
      schemaVersion: 1,
      clientUpdatedAt: DateTime.utc(2026, 7, 17),
      serverUpdatedAt: DateTime.utc(2026, 7, 17),
      deletedAt: null,
      payload: const {'name': 'Programme partagé'},
    );
  }

  @override
  Future<void> declineShare(String shareId, String token) async {}

  @override
  Future<List<ShareInboxItem>> fetchInbox(String token) async => const [];

  @override
  Future<void> revokeShare(String shareId, String token) async {}

  @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: [],
    );
  }
}

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 => actions;

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

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

final class _FakeProgramRepository implements ProgramRepository {
  @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 {}

  @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 {}
}
