import { beforeEach, describe, expect, it, vi } from 'vitest';
import { NavigationAPI } from '../../../hooks';
import { handleNavigationAttempt } from './handleNavigationAttempt';

describe('handleNavigationAttempt', () => {
  let navigation: NavigationAPI;

  beforeEach(() => {
    navigation = {
      pause: vi.fn(),
      resume: vi.fn(),
      cancel: vi.fn(),
    } as unknown as NavigationAPI;
  });

  it(`allows navigation when 'dirty' is false by calling 'navigation.resume()'`, async () => {
    // Arrange
    const dirty = false;
    const isValid = true;
    const submitForm = vi.fn();
    const isSubmitting = false;
    const canSubmit = true;
    const setCanSubmit = vi.fn();
    const onNavigationCancelled = vi.fn();

    // Act
    await handleNavigationAttempt(
      navigation,
      dirty,
      isValid,
      submitForm,
      isSubmitting,
      canSubmit,
      setCanSubmit,
      onNavigationCancelled,
    );

    // Assert
    expect(navigation.resume).toHaveBeenCalledTimes(1);
    expect(navigation.pause).not.toHaveBeenCalled();
    expect(navigation.cancel).not.toHaveBeenCalled();
    expect(submitForm).not.toHaveBeenCalled();
    expect(setCanSubmit).not.toHaveBeenCalled();
    expect(onNavigationCancelled).not.toHaveBeenCalled();
  });

  it(`blocks navigation when 'isValid' is 'false' by calling 'navigation.cancel()'`, async () => {
    // Arrange
    const dirty = true;
    const isValid = false;
    const submitForm = vi.fn();
    const isSubmitting = false;
    const canSubmit = true;
    const setCanSubmit = vi.fn();
    const onNavigationCancelled = vi.fn();

    // Act
    await handleNavigationAttempt(
      navigation,
      dirty,
      isValid,
      submitForm,
      isSubmitting,
      canSubmit,
      setCanSubmit,
      onNavigationCancelled,
    );

    // Assert
    expect(navigation.resume).not.toHaveBeenCalled();
    expect(navigation.pause).not.toHaveBeenCalled();
    expect(navigation.cancel).toHaveBeenCalledTimes(1);
    expect(submitForm).not.toHaveBeenCalled();
    expect(setCanSubmit).not.toHaveBeenCalled();
    expect(onNavigationCancelled).toHaveBeenCalled();
  });

  it(`pauses further navigation when 'dirty' is 'true' and 'isValid' is 'true' by calling 'navigation.pause()'`, async () => {
    // Arrange
    const dirty = true;
    const isValid = true;
    const submitForm = vi.fn();
    const isSubmitting = true;
    const canSubmit = true;
    const setCanSubmit = vi.fn();
    const onNavigationCancelled = vi.fn();

    // Act
    await handleNavigationAttempt(
      navigation,
      dirty,
      isValid,
      submitForm,
      isSubmitting,
      canSubmit,
      setCanSubmit,
      onNavigationCancelled,
    );

    // Assert
    expect(navigation.pause).toHaveBeenCalledTimes(1);
    expect(navigation.resume).not.toHaveBeenCalled();
    expect(navigation.cancel).not.toHaveBeenCalled();
    expect(submitForm).not.toHaveBeenCalled();
    expect(setCanSubmit).not.toHaveBeenCalled();
    expect(onNavigationCancelled).not.toHaveBeenCalled();
  });

  it(`while 'isSubmitting' is 'true' navigation should continue to be paused by not calling 'navigation.resume()'`, async () => {
    // Arrange
    const dirty = true;
    const isValid = true;
    const submitForm = vi.fn();
    const isSubmitting = true;
    const canSubmit = true;
    const setCanSubmit = vi.fn();
    const onNavigationCancelled = vi.fn();

    // Act
    await handleNavigationAttempt(
      navigation,
      dirty,
      isValid,
      submitForm,
      isSubmitting,
      canSubmit,
      setCanSubmit,
      onNavigationCancelled,
    );

    // Assert
    expect(navigation.resume).not.toHaveBeenCalled();
    expect(navigation.pause).toHaveBeenCalledTimes(1);
    expect(navigation.cancel).not.toHaveBeenCalled();
    expect(submitForm).not.toHaveBeenCalled();
    expect(setCanSubmit).not.toHaveBeenCalled();
    expect(onNavigationCancelled).not.toHaveBeenCalled();
  });

  it(`while 'canSubmit' is 'false' navigation should continue to be paused by not calling 'navigation.resume()'`, async () => {
    // Arrange
    const dirty = true;
    const isValid = true;
    const submitForm = vi.fn();
    const isSubmitting = false;
    const canSubmit = false;
    const setCanSubmit = vi.fn();
    const onNavigationCancelled = vi.fn();

    // Act
    await handleNavigationAttempt(
      navigation,
      dirty,
      isValid,
      submitForm,
      isSubmitting,
      canSubmit,
      setCanSubmit,
      onNavigationCancelled,
    );

    // Assert
    expect(navigation.resume).not.toHaveBeenCalled();
    expect(navigation.pause).toHaveBeenCalledTimes(1);
    expect(navigation.cancel).not.toHaveBeenCalled();
    expect(submitForm).not.toHaveBeenCalled();
    expect(setCanSubmit).not.toHaveBeenCalled();
    expect(onNavigationCancelled).not.toHaveBeenCalled();
  });

  it(`navigation can resume after the submitForm promise was successfully resolved by calling 'navigation.resume()'`, async () => {
    // Arrange
    const dirty = true;
    const isValid = true;
    const submitForm = vi.fn().mockResolvedValueOnce(undefined);
    const isSubmitting = false;
    const canSubmit = true;
    const setCanSubmit = vi.fn();
    const onNavigationCancelled = vi.fn();

    // Act
    await handleNavigationAttempt(
      navigation,
      dirty,
      isValid,
      submitForm,
      isSubmitting,
      canSubmit,
      setCanSubmit,
      onNavigationCancelled,
    );

    // Assert
    expect(navigation.resume).toHaveBeenCalledTimes(1);
    expect(navigation.pause).toHaveBeenCalledTimes(1);
    expect(navigation.cancel).not.toHaveBeenCalled();
    expect(submitForm).toHaveBeenCalledTimes(1);
    expect(setCanSubmit).toHaveBeenCalledTimes(1);
    expect(setCanSubmit).toHaveBeenCalledWith(false);
    expect(onNavigationCancelled).not.toHaveBeenCalledWith(false);
  });

  it(`navigation is cancelled when the 'submitForm' promise was rejected by calling 'navigation.cancel()'`, async () => {
    // Arrange
    const dirty = true;
    const isValid = true;
    const submitForm = vi
      .fn()
      .mockRejectedValueOnce(new Error('Failed to submit form'));
    const isSubmitting = false;
    const canSubmit = true;
    const setCanSubmit = vi.fn();
    const onNavigationCancelled = vi.fn();

    // Act
    await handleNavigationAttempt(
      navigation,
      dirty,
      isValid,
      submitForm,
      isSubmitting,
      canSubmit,
      setCanSubmit,
      onNavigationCancelled,
    );

    // Assert
    expect(navigation.cancel).toHaveBeenCalledTimes(1);
    expect(navigation.resume).not.toHaveBeenCalled();
    expect(navigation.pause).toHaveBeenCalledTimes(1);
    expect(submitForm).toHaveBeenCalledTimes(1);
    expect(setCanSubmit).toHaveBeenCalledTimes(2);
    expect(setCanSubmit).toHaveBeenNthCalledWith(1, false);
    expect(setCanSubmit).toHaveBeenNthCalledWith(2, true);
    expect(onNavigationCancelled).not.toHaveBeenCalled();
  });
});
