import React from 'react';
import { renderHook, act } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';

import masterpassRestReducer from '../redux/reducer';
import { getPaymentPending, setPaymentPending } from '../utils/payment-utils';

const handleVerificationMock = jest.fn();
const accountAccessMock = jest.fn();

jest.mock('../utils/response-handler', () => ({
  handleVerification: (...args: unknown[]) => handleVerificationMock(...args),
  handleMasterpassResponse: jest.fn(async (response) => response)
}));

jest.mock('../services/account', () => ({
  AccountService: class {
    accountAccess = accountAccessMock;
    linkToMerchant = jest.fn();
    removeCard = jest.fn();
    addCard = jest.fn();
  }
}));

jest.mock('./useMasterpassToken', () => ({
  useMasterpassToken: () => ({
    refetch: jest.fn(async () => ({ data: { token: 'fresh-token' } }))
  })
}));

// Imported after the mocks so the hook picks them up.
import { useMasterpassAccount } from './useMasterpassAccount';

const renderAccountHook = (otpContext: 'account' | 'payment') => {
  const store = configureStore({
    reducer: { masterpassRest: masterpassRestReducer },
    middleware: (getDefault) => getDefault({ serializableCheck: false })
  });

  store.dispatch({
    type: 'masterpassRest/setMasterpassRestTokenData',
    payload: {
      MerchantId: 'merchant-1',
      AccountKey: 'account-1',
      UserId: 'user-1'
    }
  });

  store.dispatch({
    type: 'masterpassRest/updateModalState',
    payload: { otpContext, showOTPModal: true }
  });

  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <Provider store={store}>{children}</Provider>
  );

  return { ...renderHook(() => useMasterpassAccount(), { wrapper }), store };
};

beforeEach(() => {
  accountAccessMock.mockResolvedValue({
    statusCode: 200,
    result: { cards: [] }
  });
});

describe('verification during a payment', () => {
  it('hands the verification result back so the caller can complete the order', async () => {
    handleVerificationMock.mockResolvedValue({
      success: true,
      data: { result: { responseCode: '0000', token: 'verified-token' } }
    });

    const { result } = renderAccountHook('payment');

    let outcome: any;
    await act(async () => {
      outcome = await result.current.handleOTPSubmit('123456');
    });

    expect(outcome).toEqual({
      success: true,
      isPaymentContext: true,
      verificationData: { result: { responseCode: '0000', token: 'verified-token' } }
    });
  });

  it('does not treat a refreshed card list as a completed payment', async () => {
    handleVerificationMock.mockResolvedValue({
      success: true,
      data: { result: { responseCode: '0000', token: 'verified-token' } }
    });

    const { result } = renderAccountHook('payment');
    accountAccessMock.mockClear();

    await act(async () => {
      await result.current.handleOTPSubmit('123456');
    });

    // Refreshing the account here used to be the only thing that happened, which
    // left the order uncompleted on the backend.
    expect(accountAccessMock).not.toHaveBeenCalled();
  });

  it('keeps the order number pending when verification sends the shopper to 3D', async () => {
    setPaymentPending('ORDER-1');
    handleVerificationMock.mockResolvedValue({
      requires3D: true,
      redirectUrl: 'https://bank.example/3d'
    });

    const location = window.location;
    delete (window as any).location;
    (window as any).location = { ...location, href: '' };

    const { result } = renderAccountHook('payment');

    await act(async () => {
      await result.current.handleOTPSubmit('123456');
    });

    expect(getPaymentPending()?.orderNo).toBe('ORDER-1');

    (window as any).location = location;
  });
});

describe('verification during an account operation', () => {
  it('refreshes the saved cards and reports plain success', async () => {
    handleVerificationMock.mockResolvedValue({
      success: true,
      data: { result: { responseCode: '0000' } }
    });

    const { result } = renderAccountHook('account');
    accountAccessMock.mockClear();

    let outcome: any;
    await act(async () => {
      outcome = await result.current.handleOTPSubmit('123456');
    });

    expect(outcome).toEqual({ success: true });
    expect(accountAccessMock).toHaveBeenCalled();
  });

  it('leaves the pending record untouched on a 3D redirect', async () => {
    setPaymentPending('ORDER-1');
    const before = getPaymentPending()?.startedAt;

    handleVerificationMock.mockResolvedValue({
      requires3D: true,
      redirectUrl: 'https://bank.example/3d'
    });

    const location = window.location;
    delete (window as any).location;
    (window as any).location = { ...location, href: '' };

    const { result } = renderAccountHook('account');

    await act(async () => {
      await result.current.handleOTPSubmit('123456');
    });

    expect(getPaymentPending()?.startedAt).toBe(before);

    (window as any).location = location;
  });
});

describe('the default verification context', () => {
  it('is the account flow, so an unset context never skips the card refresh', async () => {
    handleVerificationMock.mockResolvedValue({
      success: true,
      data: { result: {} }
    });

    const store = configureStore({
      reducer: { masterpassRest: masterpassRestReducer },
      middleware: (getDefault) => getDefault({ serializableCheck: false })
    });

    expect(store.getState().masterpassRest.modalState.otpContext).toBe('account');
  });
});
