import 'package:commons_core/commons_core.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

/// Where a peer stands, from strongest to weakest signal.
enum TrustTier {
  /// Passes the full Duniter membership rule (sigQty certifications from members,
  /// within stepMax of a bootstrap referent).
  networkMember,

  /// In your personal circle (you vouch, or a friend-of-a-friend does).
  inYourCircle,

  /// Vouched for by someone, but not (yet) a member nor in your circle.
  vouched,

  /// No certifications seen.
  unknown,
}

/// Trust standing of one peer: the full Duniter membership verdict, your own
/// circle, and the raw certifier count — computed from the public certification
/// graph with the active [WotParams] and bootstrap referents.
class TrustState extends Equatable {
  const TrustState({
    this.certifierCount = 0,
    this.iVouch = false,
    this.knownToYou = false,
    this.isNetworkMember = false,
    this.networkBootstrapped = false,
    this.loading = true,
    this.busy = false,
  });

  /// How many people (network-wide) currently vouch for the peer.
  final int certifierCount;

  /// Whether this user vouches for the peer.
  final bool iVouch;

  /// Whether the peer is within the user's own circle (you, or a
  /// friend-of-a-friend, vouch). Spam-resistant and works from day one.
  final bool knownToYou;

  /// Whether the peer is a full member per the Duniter rule (sigQty + stepMax
  /// from the bootstrap referents).
  final bool isNetworkMember;

  /// Whether any bootstrap referents exist at all. When false, network
  /// membership can't be determined yet (the trust net isn't seeded) and the UI
  /// should say so instead of implying the peer failed the rule.
  final bool networkBootstrapped;

  final bool loading;
  final bool busy;

  /// The strongest applicable signal, for the badge.
  TrustTier get tier {
    if (isNetworkMember) return TrustTier.networkMember;
    if (knownToYou) return TrustTier.inYourCircle;
    if (certifierCount > 0) return TrustTier.vouched;
    return TrustTier.unknown;
  }

  TrustState copyWith({
    int? certifierCount,
    bool? iVouch,
    bool? knownToYou,
    bool? isNetworkMember,
    bool? networkBootstrapped,
    bool? loading,
    bool? busy,
  }) =>
      TrustState(
        certifierCount: certifierCount ?? this.certifierCount,
        iVouch: iVouch ?? this.iVouch,
        knownToYou: knownToYou ?? this.knownToYou,
        isNetworkMember: isNetworkMember ?? this.isNetworkMember,
        networkBootstrapped: networkBootstrapped ?? this.networkBootstrapped,
        loading: loading ?? this.loading,
        busy: busy ?? this.busy,
      );

  @override
  List<Object?> get props => [
        certifierCount,
        iVouch,
        knownToYou,
        isNetworkMember,
        networkBootstrapped,
        loading,
        busy,
      ];
}

/// Reads and toggles this user's vouch for [peerPubkey] over a [TrustTransport],
/// and computes the full Duniter membership verdict against the bootstrap
/// [referents] and active [params].
class TrustCubit extends Cubit<TrustState> {
  TrustCubit(
    this._transport, {
    required this.peerPubkey,
    required this.selfPubkey,
    this.referents = const {},
    this.params = WotParams.duniter,
    Future<void> Function()? onDispose,
  })  : _onDispose = onDispose,
        super(const TrustState());

  final TrustTransport? _transport;
  final String peerPubkey;
  final String selfPubkey;

  /// Bootstrap referents (Duniter seeds) the membership rule trusts by
  /// construction. Empty = the network isn't seeded yet.
  final Set<String> referents;

  /// Active web-of-trust parameters (sigQty / stepMax / validity).
  final WotParams params;
  final Future<void> Function()? _onDispose;

  bool get isOnline => _transport != null;

  /// Personal "circle" rule: one vouch from your side, out to a friend-of-a-
  /// friend. Loose by design — "people near you", separate from formal
  /// membership.
  static const _circleThreshold = 1;
  static const _circleDistance = 2;

  /// Loads the certification graph and computes: the peer's certifier count,
  /// whether you vouch, whether they're in your circle, and whether they pass
  /// the full Duniter membership rule against the bootstrap referents.
  Future<void> load() async {
    final transport = _transport;
    if (transport == null) {
      emit(state.copyWith(loading: false));
      return;
    }
    emit(state.copyWith(loading: true));
    final wot = WebOfTrust.fromCertifications(
      await transport.allCertifications(),
      now: DateTime.now(),
    );
    final certifiers = wot.certifiersOf(peerPubkey);
    final circle = wot.members(
      seeds: {selfPubkey},
      threshold: _circleThreshold,
      maxDistance: _circleDistance,
    );
    // Full membership: only meaningful once the trust net has referents.
    final members = referents.isEmpty
        ? const <String>{}
        : wot.membersWith(seeds: referents, params: params);
    emit(state.copyWith(
      certifierCount: certifiers.length,
      iVouch: certifiers.contains(selfPubkey),
      knownToYou: circle.contains(peerPubkey),
      isNetworkMember: members.contains(peerPubkey),
      networkBootstrapped: referents.isNotEmpty,
      loading: false,
    ));
  }

  /// Adds or removes this user's vouch, then reloads. Never vouches for self.
  /// The certification is issued with the active validity so it expires and
  /// must be renewed (Duniter rule).
  Future<void> toggleVouch() async {
    final transport = _transport;
    if (transport == null || peerPubkey == selfPubkey) return;
    emit(state.copyWith(busy: true));
    if (state.iVouch) {
      await transport.revoke(subjectPubkey: peerPubkey);
    } else {
      await transport.certify(
        subjectPubkey: peerPubkey,
        validity: params.sigValidity,
      );
    }
    await load();
    emit(state.copyWith(busy: false));
  }

  @override
  Future<void> close() async {
    await _onDispose?.call();
    return super.close();
  }
}
