import 'package:commons_core/commons_core.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tane/state/trust_cubit.dart';

/// In-memory [TrustTransport]: certify/revoke act as this device's identity.
class FakeTrustTransport implements TrustTransport {
  FakeTrustTransport(this.selfId);
  final String selfId;
  final Map<String, Set<String>> _certs = {}; // subject -> issuers

  /// Test helper: record an arbitrary issuer→subject vouch (to build a graph).
  void addCert(String issuer, String subject) =>
      (_certs[subject] ??= {}).add(issuer);

  @override
  Future<Set<String>> certifiersOf(String subjectPubkey) async =>
      _certs[subjectPubkey] ?? <String>{};

  @override
  Future<void> certify({
    required String subjectPubkey,
    Duration validity = const Duration(days: 365),
    String note = '',
  }) async =>
      addCert(selfId, subjectPubkey);

  @override
  Future<void> revoke({required String subjectPubkey}) async =>
      _certs[subjectPubkey]?.remove(selfId);

  @override
  Future<List<Certification>> allCertifications() async => [
        for (final entry in _certs.entries)
          for (final issuer in entry.value)
            Certification(
              issuer: issuer,
              subject: entry.key,
              issuedAt: DateTime(2026),
            ),
      ];

  @override
  Future<void> close() async {}
}

void main() {
  const me = 'me';
  const peer = 'peer';

  test('loads certifier count and whether I vouch', () async {
    final transport = FakeTrustTransport(me);
    await transport.certify(subjectPubkey: peer); // I already vouch
    final cubit = TrustCubit(transport, peerPubkey: peer, selfPubkey: me);
    await cubit.load();
    expect(cubit.state.certifierCount, 1);
    expect(cubit.state.iVouch, isTrue);
    expect(cubit.state.loading, isFalse);
    await cubit.close();
  });

  test('toggleVouch certifies then revokes', () async {
    final cubit =
        TrustCubit(FakeTrustTransport(me), peerPubkey: peer, selfPubkey: me);
    await cubit.load();
    expect(cubit.state.iVouch, isFalse);

    await cubit.toggleVouch();
    expect(cubit.state.iVouch, isTrue);
    expect(cubit.state.certifierCount, 1);

    await cubit.toggleVouch();
    expect(cubit.state.iVouch, isFalse);
    expect(cubit.state.certifierCount, 0);
    await cubit.close();
  });

  test('a friend-of-a-friend is in your circle; a stranger is not', () async {
    final transport = FakeTrustTransport(me)
      ..addCert(me, 'friend') // you vouch for a friend
      ..addCert('friend', peer) // your friend vouches for the peer
      ..addCert('stranger', 'other'); // unrelated to you

    final inCircle = TrustCubit(transport, peerPubkey: peer, selfPubkey: me);
    await inCircle.load();
    expect(inCircle.state.knownToYou, isTrue);
    await inCircle.close();

    final outside = TrustCubit(transport, peerPubkey: 'other', selfPubkey: me);
    await outside.load();
    expect(outside.state.knownToYou, isFalse);
    await outside.close();
  });

  test('network membership: enough referent vouches → member', () async {
    const params =
        WotParams(sigQty: 2, stepMax: 5, sigValidity: Duration(days: 365));
    final transport = FakeTrustTransport(me)
      ..addCert('r1', peer)
      ..addCert('r2', peer);
    final cubit = TrustCubit(
      transport,
      peerPubkey: peer,
      selfPubkey: me,
      referents: {'r1', 'r2'},
      params: params,
    );
    await cubit.load();
    expect(cubit.state.networkBootstrapped, isTrue);
    expect(cubit.state.isNetworkMember, isTrue);
    expect(cubit.state.tier, TrustTier.networkMember);
    await cubit.close();
  });

  test('with no referents, membership is undetermined (not a member)',
      () async {
    final transport = FakeTrustTransport(me)
      ..addCert('r1', peer)
      ..addCert('r2', peer);
    // referents default to {} → the trust net isn't seeded.
    final cubit = TrustCubit(transport, peerPubkey: peer, selfPubkey: me);
    await cubit.load();
    expect(cubit.state.networkBootstrapped, isFalse);
    expect(cubit.state.isNetworkMember, isFalse);
    await cubit.close();
  });

  test('tier falls back to vouched, then unknown', () async {
    final vouched = TrustCubit(
        FakeTrustTransport(me)..addCert('someone', peer),
        peerPubkey: peer,
        selfPubkey: me);
    await vouched.load();
    expect(vouched.state.tier, TrustTier.vouched);
    await vouched.close();

    final unknown = TrustCubit(FakeTrustTransport(me),
        peerPubkey: 'nobody', selfPubkey: me);
    await unknown.load();
    expect(unknown.state.tier, TrustTier.unknown);
    await unknown.close();
  });

  test('never vouches for self', () async {
    final cubit =
        TrustCubit(FakeTrustTransport(me), peerPubkey: me, selfPubkey: me);
    await cubit.load();
    await cubit.toggleVouch();
    expect(cubit.state.iVouch, isFalse);
    await cubit.close();
  });

  test('offline (no transport) never throws', () async {
    final cubit = TrustCubit(null, peerPubkey: peer, selfPubkey: me);
    expect(cubit.isOnline, isFalse);
    await cubit.load();
    await cubit.toggleVouch();
    expect(cubit.state.loading, isFalse);
    await cubit.close();
  });
}
