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

import '../services/social_service.dart';
import '../services/social_settings.dart';

/// Trust standing of one peer: how many people vouch for them, and whether YOU
/// do. Kept simple on purpose — the full "known member" rule (threshold +
/// distance) and its bootstrap set are a policy decision still open
/// (network-trust.md §2). Vouching + a live count is the useful, honest first
/// step.
class TrustState extends Equatable {
  const TrustState({
    this.certifierCount = 0,
    this.iVouch = false,
    this.knownToYou = false,
    this.loading = true,
    this.busy = false,
  });

  /// How many people (network-wide) 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 of trust — you vouch for
  /// them, or someone you vouch for does (friend-of-a-friend). This is the
  /// meaningful signal; a raw network count invites spam.
  final bool knownToYou;

  final bool loading;
  final bool busy;

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

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

/// Reads and toggles this user's vouch for [peerPubkey] over a [TrustTransport].
class TrustCubit extends Cubit<TrustState> {
  TrustCubit(
    this._transport, {
    required this.peerPubkey,
    required this.selfPubkey,
    Future<void> Function()? onDispose,
  })  : _onDispose = onDispose,
        super(const TrustState());

  final TrustTransport? _transport;
  final String peerPubkey;
  final String selfPubkey;
  final Future<void> Function()? _onDispose;

  bool get isOnline => _transport != null;

  /// Bootstrap "circle" rule: one vouch from your side, out to a friend-of-a-
  /// friend. Deliberately loose (the full member policy — threshold/distance —
  /// is still open, network-trust.md §2); this is just "people near you".
  static const _circleThreshold = 1;
  static const _circleDistance = 2;

  /// Loads the peer's certifiers and whether they're within your circle.
  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,
    );
    emit(state.copyWith(
      certifierCount: certifiers.length,
      iVouch: certifiers.contains(selfPubkey),
      knownToYou: circle.contains(peerPubkey),
      loading: false,
    ));
  }

  /// Adds or removes this user's vouch, then reloads. Never vouches for self.
  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);
    }
    await load();
    emit(state.copyWith(busy: false));
  }

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

/// Opens a [TrustCubit] wired to the social layer for [peerPubkey], or an
/// offline one.
Future<TrustCubit> createTrustCubit(
  SocialService social,
  SocialSettings settings, {
  required String peerPubkey,
}) async {
  final relays = await settings.relayUrls();
  if (relays.isEmpty) {
    return TrustCubit(null,
        peerPubkey: peerPubkey, selfPubkey: social.publicKeyHex);
  }
  try {
    final session = await social.openSession(relays);
    return TrustCubit(
      session.trust,
      peerPubkey: peerPubkey,
      selfPubkey: social.publicKeyHex,
      onDispose: session.close,
    );
  } catch (_) {
    return TrustCubit(null,
        peerPubkey: peerPubkey, selfPubkey: social.publicKeyHex);
  }
}
