import {
    DefaultContext,
    FetchResult,
    MutationFunctionOptions,
} from '@apollo/client';
import * as toast from '@theorchard/suite-components';
import * as mockContractWithLifecycle from 'src/__fixtures__/graphql/contract-with-lifecycle-response.json';
import { AbacusContractLifecycleScheduleRenewalType } from 'src/apollo/definitions/globalTypes';
import * as abacusCreateContractWithLifecycle from 'src/apollo/mutations/contract';
import * as abacusContract from 'src/apollo/mutations/contract';
import { CONTRACT_TYPES } from 'src/constants';
import { Contract } from 'src/types/abacus-contract-lifecycle';
import * as contractLifecycleSchedules from 'src/utils/contract-lifecycle-schedule';
import {
    CreateContractMutation,
    CreateContractMutationVariables,
} from '../../apollo/mutations/contract/__generated__/create-contract';
import { useSaveHandler } from '../create-contract-with-lifecycle';
import type {
    CreateContractWithLifecycleMutation,
    CreateContractWithLifecycleMutationVariables,
} from 'src/apollo/mutations/contract/__generated__/create-contract-with-lifecycle';

describe('useSaveHandler', () => {
    /* eslint-disable no-unused-vars */
    let formatContractLifecycleSchedulesSpy: any;

    let createContractWithLifecycleSpy: jest.SpyInstance<
        (
            options?:
                | MutationFunctionOptions<
                      CreateContractWithLifecycleMutation,
                      CreateContractWithLifecycleMutationVariables,
                      DefaultContext
                  >
                | undefined
        ) => Promise<FetchResult<CreateContractWithLifecycleMutation>>,
        []
    >;
    let createContractSpy: jest.SpyInstance<
        (
            options?:
                | MutationFunctionOptions<
                      CreateContractMutation,
                      CreateContractMutationVariables,
                      DefaultContext
                  >
                | undefined
        ) => Promise<FetchResult<CreateContractMutation>>,
        []
    >;

    let updateContractSpy: any;

    let useToastSpy: jest.SpyInstance<(message: string) => void>;
    /* eslint-enable no-unused-vars */

    let mockCreateContractWithLifecycle = jest.fn();
    let mockCreateContract = jest.fn();
    const mockUpdateContract = jest.fn();

    const mockContract: Contract = {
        accountId: undefined,
        contractName: null,
        contractType: CONTRACT_TYPES.DISTRIBUTION,
        referencePaymentEntityId: null,
        referenceSigningEntityId: null,
        executionDate: null,
        runControllerId: null,
        generalNote: null,
        isPrimaryContract: false,
    };
    const mockContractLifecycle = { lifecycleTermStart: '2024-01-01' };
    const mockContractLifecycleSchedulesContinuouslyActive = [
        {
            renewalType:
                AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE,
            terminationNoticeDetailInterval: 5,
            scheduleEnd: null,
            terminationNoticeDetailType: 'DAY',
            renewalOffsetDetailInterval: null,
            renewalOffsetDetailType: 'MONTH',
        },
    ];
    const mockContractLifecycleSchedulesRenewPeriodically = [
        {
            renewalType:
                AbacusContractLifecycleScheduleRenewalType.RENEW_PERIODICALLY,
            terminationNoticeDetailInterval: 5,
            scheduleEnd: null,
            terminationNoticeDetailType: 'DAY',
            renewalOffsetDetailInterval: 4,
            renewalOffsetDetailType: 'MONTH',
        },
    ];

    afterEach(jest.restoreAllMocks);

    beforeEach(() => {
        createContractWithLifecycleSpy = jest.spyOn(
            abacusCreateContractWithLifecycle,
            'useCreateContractWithLifecycle'
        );
        createContractSpy = jest.spyOn(abacusContract, 'useCreateContract');
        updateContractSpy = jest.spyOn(abacusContract, 'useUpdateContract');
        useToastSpy = jest.spyOn(toast, 'useToast').mockReturnValue(jest.fn());
        formatContractLifecycleSchedulesSpy = jest.spyOn(
            contractLifecycleSchedules,
            'formatContractLifecycleSchedules'
        );
        createContractSpy.mockReturnValue(mockCreateContract);
        updateContractSpy.mockReturnValue(mockUpdateContract);
    });

    it('should format contract lifecycle schedules', async () => {
        const hasCollectionPeriod: boolean = false;
        mockCreateContractWithLifecycle = jest
            .fn()
            .mockResolvedValue(
                mockContractWithLifecycle.CONTINUOUSLY_ACTIVE
                    .abacusCreateContractWithLifecycles
            );
        createContractWithLifecycleSpy.mockReturnValue(
            mockCreateContractWithLifecycle
        );

        const saveHandler = useSaveHandler();
        await saveHandler(
            'testAccount',
            mockContract,
            mockContractLifecycle,
            mockContractLifecycleSchedulesContinuouslyActive,
            hasCollectionPeriod,
            false,
            'testName'
        );
        expect(formatContractLifecycleSchedulesSpy).toHaveBeenCalledWith(
            mockContractLifecycleSchedulesContinuouslyActive,
            mockContract.contractType,
            hasCollectionPeriod
        );
    });

    it('should format contract lifecycle schedules when schedules have a collection period', async () => {
        const hasCollectionPeriod: boolean = true;
        const nrContract = {
            ...mockContract,
            contractType: CONTRACT_TYPES.NEIGHBOURING_RIGHTS,
        };
        mockCreateContractWithLifecycle = jest
            .fn()
            .mockResolvedValue(
                mockContractWithLifecycle.CONTINUOUSLY_ACTIVE
                    .abacusCreateContractWithLifecycles
            );
        createContractWithLifecycleSpy.mockReturnValue(
            mockCreateContractWithLifecycle
        );

        const saveHandler = useSaveHandler();
        await saveHandler(
            'testAccount',
            nrContract,
            mockContractLifecycle,
            mockContractLifecycleSchedulesContinuouslyActive,
            hasCollectionPeriod,
            false,
            'testName'
        );
        expect(formatContractLifecycleSchedulesSpy).toHaveBeenCalledWith(
            mockContractLifecycleSchedulesContinuouslyActive,
            nrContract.contractType,
            hasCollectionPeriod
        );
    });

    it('should call useSaveHandler and return a contract id for continuously active', async () => {
        const hasCollectionPeriod: boolean = false;
        mockCreateContractWithLifecycle = jest.fn().mockResolvedValue({
            data: {
                abacusCreateContractWithLifecycles:
                    mockContractWithLifecycle.CONTINUOUSLY_ACTIVE
                        .abacusCreateContractWithLifecycles,
            },
        });
        createContractWithLifecycleSpy.mockReturnValue(
            mockCreateContractWithLifecycle
        );

        const { abacusCreateContractWithLifecycles } =
            mockContractWithLifecycle.CONTINUOUSLY_ACTIVE;

        const saveHandler = useSaveHandler();
        const result = await saveHandler(
            'testAccount',
            mockContract,
            mockContractLifecycle,
            mockContractLifecycleSchedulesContinuouslyActive,
            hasCollectionPeriod,
            false,
            'testName'
        );

        expect(createContractWithLifecycleSpy).toHaveBeenCalled();
        expect(formatContractLifecycleSchedulesSpy).toHaveBeenCalledWith(
            mockContractLifecycleSchedulesContinuouslyActive,
            mockContract.contractType,
            hasCollectionPeriod
        );
        expect(result).toEqual(abacusCreateContractWithLifecycles.contractId);
        expect(useToastSpy).toHaveBeenCalled();
    });

    it('should call useSaveHandler and return a contract id for renew periodically', async () => {
        const hasCollectionPeriod: boolean = false;
        mockCreateContractWithLifecycle = jest.fn().mockResolvedValue({
            data: {
                abacusCreateContractWithLifecycles:
                    mockContractWithLifecycle.RENEW_PERIODICALLY
                        .abacusCreateContractWithLifecycles,
            },
        });
        createContractWithLifecycleSpy.mockReturnValue(
            mockCreateContractWithLifecycle
        );
        mockCreateContract = jest.fn().mockResolvedValue(mockContract);

        const { abacusCreateContractWithLifecycles } =
            mockContractWithLifecycle.RENEW_PERIODICALLY;

        const saveHandler = useSaveHandler();
        const result = await saveHandler(
            'testAccount',
            mockContract,
            mockContractLifecycle,
            mockContractLifecycleSchedulesRenewPeriodically,
            hasCollectionPeriod,
            true,
            'testName'
        );

        expect(createContractWithLifecycleSpy).toHaveBeenCalled();
        expect(formatContractLifecycleSchedulesSpy).toHaveBeenCalledWith(
            mockContractLifecycleSchedulesRenewPeriodically,
            mockContract.contractType,
            hasCollectionPeriod
        );
        expect(result).toEqual(abacusCreateContractWithLifecycles.contractId);
        expect(useToastSpy).toHaveBeenCalled();
    });

    it('calls createContract when there are no lifecycle schedules', async () => {
        const hasCollectionPeriod: boolean = false;
        const { abacusCreateContract } =
            mockContractWithLifecycle.NO_LIFECYCLE_SCHEDULES;
        createContractWithLifecycleSpy.mockReturnValue(
            mockCreateContractWithLifecycle
        );
        mockCreateContract = jest.fn().mockResolvedValue({
            data: { abacusCreateContract },
        });
        createContractSpy.mockReturnValue(mockCreateContract);

        const saveHandler = useSaveHandler();
        const result = await saveHandler(
            'testAccount',
            mockContract,
            mockContractLifecycle,
            [],
            hasCollectionPeriod,
            false,
            'testName'
        );

        expect(createContractSpy).toHaveBeenCalled();
        expect(formatContractLifecycleSchedulesSpy).not.toHaveBeenCalled();
        expect(result).toEqual(abacusCreateContract.contractId);
        expect(useToastSpy).toHaveBeenCalled();
    });

    it('calls updateContract when there is an existing contract id', async () => {
        const hasCollectionPeriod: boolean = false;
        const { abacusCreateContract } =
            mockContractWithLifecycle.NO_LIFECYCLE_SCHEDULES;
        createContractWithLifecycleSpy.mockReturnValue(
            mockCreateContractWithLifecycle
        );
        mockCreateContract = jest.fn().mockResolvedValue({
            data: { abacusCreateContract },
        });
        createContractSpy.mockReturnValue(mockCreateContract);

        const saveHandler = useSaveHandler();
        const result = await saveHandler(
            'testAccount',
            mockContract,
            mockContractLifecycle,
            [],
            hasCollectionPeriod,
            false,
            'testName',
            '1'
        );

        expect(createContractSpy).toHaveBeenCalled();
        expect(formatContractLifecycleSchedulesSpy).not.toHaveBeenCalled();
        expect(result).toEqual(abacusCreateContract.contractId);
        expect(useToastSpy).toHaveBeenCalled();
    });
});
