// SWE.4 unit-level slice for BackIntentService channel routing.
//
// Requirement trace:
//   SDD-028 (BackIntentService channel adapter) / SAD-018.
//   Verification-plan rows: SWE4-UV-042 (policy branches dispatched
//   through the channel handler), SWE5-IV-019 (BackIntentService ↔
//   Navigator stack — unit-level slice; full integration is in SWE.5).
//
// Strategy: use Flutter's `TestDefaultBinaryMessengerBinding` to drive
// inbound MethodCalls on `app.chanora/back_intent` and to capture
// outbound `popToSystem` calls. No real Android platform is involved.

import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:chanora_flutter/services/back_intent_service.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  late MethodChannel channel;
  late List<MethodCall> outgoingCalls;

  // Probe state. Mutating these lets each test express the shell
  // state the policy should observe at dispatch time.
  late bool pttActive;
  late bool modalOpen;
  late bool atRoot;

  // Effect counters.
  late int closeTopModalCount;
  late int popRouteCount;

  BackIntentService makeService() {
    return BackIntentService(
      pttActiveProbe: () => pttActive,
      modalOpenProbe: () => modalOpen,
      atRootProbe: () => atRoot,
      closeTopModal: () => closeTopModalCount++,
      popRoute: () => popRouteCount++,
      channel: channel,
    );
  }

  setUp(() {
    channel = const MethodChannel(backIntentChannelName);
    outgoingCalls = <MethodCall>[];
    pttActive = false;
    modalOpen = false;
    atRoot = false;
    closeTopModalCount = 0;
    popRouteCount = 0;

    // Capture outbound invokeMethod calls (e.g. popToSystem) by
    // installing a mock handler on the OUTGOING side of the channel.
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(channel, (call) async {
      outgoingCalls.add(call);
      return null;
    });
  });

  tearDown(() {
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(channel, null);
  });

  /// Helper: simulate the platform side sending a `backIntent` call
  /// into the Dart-installed handler. Mirrors what `BackIntentBridge.kt`
  /// does at runtime.
  Future<void> sendSystemBack() async {
    const codec = StandardMethodCodec();
    final encoded = codec.encodeMethodCall(
      const MethodCall(
        backIntentMethodFromPlatform,
        <String, dynamic>{
          backIntentPayloadKindKey: backIntentPayloadKindSystemBack,
        },
      ),
    );
    await TestDefaultBinaryMessengerBinding
        .instance.defaultBinaryMessenger
        .handlePlatformMessage(channel.name, encoded, (_) {});
  }

  test(
      'SWE4-UV-042 / SWE5-IV-019: pttActive swallows back; no effects, '
      'no outbound popToSystem', () async {
    final svc = makeService()..start();
    pttActive = true;
    modalOpen = true; // Should not matter — ptt wins.
    atRoot = true; //   Should not matter — ptt wins.

    await sendSystemBack();

    expect(closeTopModalCount, 0);
    expect(popRouteCount, 0);
    expect(
      outgoingCalls.where((c) => c.method == backIntentMethodPopToSystem),
      isEmpty,
    );

    svc.stop();
  });

  test(
      'SWE4-UV-042 / SWE5-IV-019: modalOpen routes to closeTopModal only',
      () async {
    final svc = makeService()..start();
    modalOpen = true;
    atRoot = true; // Irrelevant when modal is open.

    await sendSystemBack();

    expect(closeTopModalCount, 1);
    expect(popRouteCount, 0);
    expect(
      outgoingCalls.where((c) => c.method == backIntentMethodPopToSystem),
      isEmpty,
    );

    svc.stop();
  });

  test(
      'SWE4-UV-042 / SWE5-IV-019: !atRoot routes to popRoute only',
      () async {
    final svc = makeService()..start();
    atRoot = false;

    await sendSystemBack();

    expect(popRouteCount, 1);
    expect(closeTopModalCount, 0);
    expect(
      outgoingCalls.where((c) => c.method == backIntentMethodPopToSystem),
      isEmpty,
    );

    svc.stop();
  });

  test(
      'SWE4-UV-042 / SWE5-IV-019: atRoot with no modal/ptt invokes '
      'popToSystem back through the channel', () async {
    final svc = makeService()..start();
    atRoot = true;

    await sendSystemBack();

    expect(closeTopModalCount, 0);
    expect(popRouteCount, 0);
    final popToSystem = outgoingCalls
        .where((c) => c.method == backIntentMethodPopToSystem)
        .toList();
    expect(popToSystem, hasLength(1));

    svc.stop();
  });

  test(
      'SWE5-IV-019: stop() removes the handler so subsequent inbound '
      'calls have no effect', () async {
    final svc = makeService()..start();
    atRoot = false;

    // First call should route normally.
    await sendSystemBack();
    expect(popRouteCount, 1);

    svc.stop();

    // After stop(): an inbound backIntent must not increment effect
    // counters or produce outbound popToSystem. Because stop() removed
    // our handler, the test messenger's mock-outgoing handler is the
    // only thing left on the channel; sending a "backIntent" to it
    // would otherwise be captured in outgoingCalls. To verify the
    // handler is gone we instead drive the channel as if the platform
    // re-sent the same message and assert no effect counter moved.
    final priorOutgoing = outgoingCalls.length;
    await sendSystemBack();
    expect(popRouteCount, 1, reason: 'popRoute must not fire after stop()');
    expect(closeTopModalCount, 0);
    // The mock outgoing handler may have echoed the inbound call into
    // outgoingCalls (because with no inbound handler installed, the
    // test binding routes the message to the outbound side). That is
    // acceptable — the contract we care about is that the *service*
    // performed no work. Assert no NEW popToSystem was issued by the
    // service (the captured call, if any, has method 'backIntent', not
    // 'popToSystem').
    final newPopToSystem = outgoingCalls
        .skip(priorOutgoing)
        .where((c) => c.method == backIntentMethodPopToSystem);
    expect(newPopToSystem, isEmpty);
  });
}
