// Mock react-native BEFORE importing the SDK — `../NamiFlowManager` resolves
// the native module via TurboModuleRegistry at import time, and the
// NativeEventEmitter constructor is called at module top-level.

const registerStepHandoff = jest.fn();
const unregisterStepHandoff = jest.fn();
const registerEventHandler = jest.fn();
const unregisterEventHandler = jest.fn();
const completeHandoff = jest.fn();
const finishMock = jest.fn();
const isFlowOpenMock = jest.fn().mockResolvedValue(false);
const resumeMock = jest.fn();
const pauseMock = jest.fn();

const subRemove = jest.fn();
// Capture the registered listener per event name so tests can fire native events.
const listeners: Record<string, (event: unknown) => void> = {};
const addListener = jest.fn(
  (eventName: string, handler: (e: unknown) => void) => {
    listeners[eventName] = handler;
    return { remove: subRemove };
  },
);

jest.mock('react-native', () => ({
  TurboModuleRegistry: {
    getEnforcing: jest.fn(() => ({
      registerStepHandoff,
      unregisterStepHandoff,
      registerEventHandler,
      unregisterEventHandler,
      completeHandoff,
      finish: finishMock,
      isFlowOpen: isFlowOpenMock,
      resume: resumeMock,
      pause: pauseMock,
    })),
  },
  NativeModules: {
    RNNamiFlowManager: {
      registerStepHandoff,
      unregisterStepHandoff,
      registerEventHandler,
      unregisterEventHandler,
      completeHandoff,
    },
  },
  NativeEventEmitter: jest.fn().mockImplementation(() => ({
    addListener,
  })),
}));

import { NamiFlowManager } from '../NamiFlowManager';
import type { NamiHandoffOutcome } from '../types';

/** Fire a native `Handoff` event into the captured emitter listener. */
function fireHandoff(event: {
  handoffId: string;
  handoffTag: string;
  handoffData?: Record<string, unknown>;
}) {
  listeners.Handoff?.(event);
}

describe('NamiFlowManager (React Native bridge)', () => {
  beforeEach(() => {
    registerStepHandoff.mockClear();
    unregisterStepHandoff.mockClear();
    registerEventHandler.mockClear();
    unregisterEventHandler.mockClear();
    completeHandoff.mockClear();
    resumeMock.mockClear();
    subRemove.mockClear();
    addListener.mockClear();
  });

  describe('registerStepHandoff', () => {
    it('returns a function and triggers native register', () => {
      const unsubscribe = NamiFlowManager.registerStepHandoff(() => {});
      expect(typeof unsubscribe).toBe('function');
      expect(registerStepHandoff).toHaveBeenCalledTimes(1);
      expect(addListener).toHaveBeenCalledTimes(1);
    });

    it('returned unsubscribe removes the JS listener AND calls native unregister', () => {
      const unsubscribe = NamiFlowManager.registerStepHandoff(() => {});
      unsubscribe();

      expect(subRemove).toHaveBeenCalledTimes(1);
      expect(unregisterStepHandoff).toHaveBeenCalledTimes(1);
    });
  });

  describe('handoff outcome token bridge', () => {
    it('delivers (tag, data, complete) and correlates complete() back to the handoffId', () => {
      const handler = jest.fn(
        (_tag: string, _data: unknown, complete: () => void) => complete(),
      );
      NamiFlowManager.registerStepHandoff(handler);

      fireHandoff({
        handoffId: 'h1',
        handoffTag: 'signin',
        handoffData: { a: 1 },
      });

      expect(handler).toHaveBeenCalledWith(
        'signin',
        { a: 1 },
        expect.any(Function),
      );
      // Bare complete() is normalized to {kind:'done'} before crossing the bridge.
      expect(completeHandoff).toHaveBeenCalledTimes(1);
      expect(completeHandoff).toHaveBeenCalledWith('h1', { kind: 'done' });
    });

    it('forwards an explicit outcome verbatim (incl. primitive login attributes)', () => {
      const outcome: NamiHandoffOutcome = {
        kind: 'login',
        result: 'success',
        details: {
          loginId: 'user-123',
          attributes: { subscriberStatus: 'active', seats: 3, trial: false },
        },
      };
      NamiFlowManager.registerStepHandoff((_t, _d, complete) =>
        complete(outcome),
      );

      fireHandoff({ handoffId: 'h2', handoffTag: 'signin' });

      expect(completeHandoff).toHaveBeenCalledWith('h2', outcome);
    });

    it('is once-only per handoff — a second complete() warns and does not re-cross the bridge', () => {
      const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
      NamiFlowManager.registerStepHandoff((_t, _d, complete) => {
        complete({ kind: 'purchase', result: 'cancelled' });
        complete(); // duplicate — must be ignored
      });

      fireHandoff({ handoffId: 'h3', handoffTag: 'buysku' });

      expect(completeHandoff).toHaveBeenCalledTimes(1);
      expect(completeHandoff).toHaveBeenCalledWith('h3', {
        kind: 'purchase',
        result: 'cancelled',
      });
      expect(warn).toHaveBeenCalledWith(
        expect.stringContaining('called more than once'),
      );
      warn.mockRestore();
    });

    it('correlates concurrent handoffs to their own ids independently', () => {
      const completes: Record<string, () => void> = {};
      NamiFlowManager.registerStepHandoff((tag, _d, complete) => {
        completes[tag] = complete;
      });

      fireHandoff({ handoffId: 'hA', handoffTag: 'push' });
      fireHandoff({ handoffId: 'hB', handoffTag: 'location' });

      completes.location();
      completes.push();

      expect(completeHandoff).toHaveBeenCalledWith('hB', { kind: 'done' });
      expect(completeHandoff).toHaveBeenCalledWith('hA', { kind: 'done' });
    });

    it('legacy (tag, data) handler that calls resume() still works (degrades natively, no completeHandoff)', () => {
      // Old-shape handler ignores the third arg and calls resume() directly.
      const legacy = (_tag: string, _data?: Record<string, unknown>) => {
        NamiFlowManager.resume();
      };
      // Cast at the call site only — the public signature is the 3-arg shape.
      NamiFlowManager.registerStepHandoff(legacy as never);

      fireHandoff({ handoffId: 'h4', handoffTag: 'complete' });

      expect(resumeMock).toHaveBeenCalledTimes(1);
      expect(completeHandoff).not.toHaveBeenCalled();
    });
  });

  describe('registerEventHandler', () => {
    it('returns a function and triggers native register', () => {
      const unsubscribe = NamiFlowManager.registerEventHandler(() => {});
      expect(typeof unsubscribe).toBe('function');
      expect(registerEventHandler).toHaveBeenCalledTimes(1);
      expect(addListener).toHaveBeenCalledTimes(1);
    });

    it('returned unsubscribe removes the JS listener AND calls native unregister', () => {
      const unsubscribe = NamiFlowManager.registerEventHandler(() => {});
      unsubscribe();

      expect(subRemove).toHaveBeenCalledTimes(1);
      expect(unregisterEventHandler).toHaveBeenCalledTimes(1);
    });
  });
});
