import 'dart:async';

import 'package:commons_core/commons_core.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tane/services/message_store.dart';
import 'package:tane/state/messages_cubit.dart';

import '../support/test_support.dart';

/// In-memory [MessageTransport]: records sends, lets a test push inbox messages.
class FakeMessageTransport implements MessageTransport {
  final List<({String to, String text})> sent = [];
  final StreamController<PrivateMessage> _inbox =
      StreamController<PrivateMessage>.broadcast();

  void receive(PrivateMessage message) => _inbox.add(message);

  @override
  Future<void> send({required String toPubkey, required String text}) async =>
      sent.add((to: toPubkey, text: text));

  @override
  Stream<PrivateMessage> inbox() => _inbox.stream;

  @override
  Future<void> close() async => _inbox.close();
}

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

  PrivateMessage msg(String from, String text) =>
      PrivateMessage(fromPubkey: from, text: text, at: DateTime(2026));

  test('receives messages from the peer only', () async {
    final transport = FakeMessageTransport();
    final cubit =
        MessagesCubit(transport, peerPubkey: peer, selfPubkey: me)..start();

    transport.receive(msg(peer, 'hola'));
    transport.receive(msg('cc', 'not for this chat')); // other conversation
    await pumpEventQueue();

    expect(cubit.state.messages, hasLength(1));
    expect(cubit.state.messages.single.text, 'hola');
    expect(cubit.isMine(cubit.state.messages.single), isFalse);
    await cubit.close();
  });

  test('send delivers to the peer and appends our own message', () async {
    final transport = FakeMessageTransport();
    final cubit = MessagesCubit(transport, peerPubkey: peer, selfPubkey: me);

    await cubit.send('  ¿cambiamos?  ');
    expect(transport.sent, [(to: peer, text: '¿cambiamos?')]);
    expect(cubit.state.messages, hasLength(1));
    expect(cubit.isMine(cubit.state.messages.single), isTrue);
    expect(cubit.state.sending, isFalse);
    await cubit.close();
  });

  test('a full exchange keeps order and sides', () async {
    final transport = FakeMessageTransport();
    final cubit =
        MessagesCubit(transport, peerPubkey: peer, selfPubkey: me)..start();

    await cubit.send('hi');
    transport.receive(msg(peer, 'hello'));
    await pumpEventQueue();

    expect(cubit.state.messages.map((m) => m.text), ['hi', 'hello']);
    expect(cubit.state.messages.map(cubit.isMine), [true, false]);
    await cubit.close();
  });

  test('empty/whitespace text is not sent', () async {
    final transport = FakeMessageTransport();
    final cubit = MessagesCubit(transport, peerPubkey: peer, selfPubkey: me);
    await cubit.send('   ');
    expect(transport.sent, isEmpty);
    expect(cubit.state.messages, isEmpty);
    await cubit.close();
  });

  test('loads saved history on start and persists across a new cubit',
      () async {
    final store = MessageStore(InMemorySecretStore());
    await store.append(
        peer, msg(peer, 'earlier')); // a message from a previous session

    final transport = FakeMessageTransport();
    final cubit = MessagesCubit(transport,
        peerPubkey: peer, selfPubkey: me, store: store);
    await cubit.start();
    expect(cubit.state.messages.map((m) => m.text), ['earlier']);

    await cubit.send('hi'); // persisted
    transport.receive(msg(peer, 'reply')); // persisted
    await pumpEventQueue();
    expect(cubit.state.messages.map((m) => m.text), ['earlier', 'hi', 'reply']);
    await cubit.close();

    // A fresh cubit (even offline) sees the saved conversation.
    final reopened =
        MessagesCubit(null, peerPubkey: peer, selfPubkey: me, store: store);
    await reopened.start();
    expect(reopened.state.messages.map((m) => m.text),
        ['earlier', 'hi', 'reply']);
    await reopened.close();
  });

  test('offline (no transport) never throws', () async {
    final cubit = MessagesCubit(null, peerPubkey: peer, selfPubkey: me)..start();
    expect(cubit.isOnline, isFalse);
    await cubit.send('hi');
    expect(cubit.state.messages, isEmpty);
    await cubit.close();
  });
}
