import React, { useContext, useEffect, useState } from 'react';
import {
    Alert,
    Col,
    Form,
    Row,
    Sidecar,
    useToast,
} from '@theorchard/suite-components';
import { useFeatureFlag } from '@theorchard/suite-frontend';
import { isEmpty } from 'lodash-es';
import {
    AbacusContractLifecycleStatus,
    AbacusContractLifecycleSchedulePeriodType,
    AbacusContractLifecycleScheduleRenewalType,
} from 'src/apollo/definitions/globalTypes';
import {
    useCreateContractLifecycleSchedules,
    useTerminateContract,
    useUpdateContractLifecycleSchedules,
    useUpdateContractLifecycleTermStart,
    useUpdateContract,
} from 'src/apollo/mutations/contract';
import { updateContractMessage } from 'src/apollo/type-constants/contract';
import { ContractLifecycleTermination } from 'src/components/contract-lifecycle-edit/contract-lifecycle-termination';
import { CurrentPeriodFormFields } from 'src/components/contract-lifecycle-shared/current-period-form-fields';
import { SubsequentPeriodFormFields } from 'src/components/contract-lifecycle-shared/subsequent-period-form-fields';
import { CONTRACT_TYPES, USER_FEATURES } from 'src/constants';
import {
    formatContractLifecycleSchedules,
    formatContractLifecycleSchedulesDetailInput,
} from 'src/utils/contract-lifecycle-schedule';
import {
    contractLifecycleScheduleFormValidation,
    contractTerminationFormValidation,
} from 'src/utils/form-validations/contract-form-validation';
import { ContractLifecycleContext } from '../../contexts/contract-lifecycle-context';
import type {
    AbacusContractLifecycleInput,
    AbacusContractLifecycleScheduleUpdate,
} from 'src/apollo/definitions/globalTypes';
import type { UpdateContractLifecycleSchedulesMutationVariables } from 'src/apollo/mutations/contract/__generated__/update-contract-lifecycle-schedules';
import type { UpdateContractLifecycleTermStartMutationVariables } from 'src/apollo/mutations/contract/__generated__/update-contract-lifecycle-term-start';
import type {
    ContractLifecycle,
    ContractLifecycleSchedule,
} from 'src/types/abacus-contract-lifecycle';

export interface ContractLifecycleEditRenewalPropTypes {
    isSidecarOpen: boolean;
    setIsSidecarOpen: (is: boolean) => void;
}

export const ContractLifecycleEditRenewal: React.FC<
    ContractLifecycleEditRenewalPropTypes
> = ({ isSidecarOpen, setIsSidecarOpen }) => {
    const {
        contract,
        isTerminatingContract,
        setIsTerminatingContract,
        setContractLifecycle: setParentContractLifecycle,
    } = useContext(ContractLifecycleContext);

    const [contractLifecycle, setContractLifecycle] =
        useState<ContractLifecycle>(defaultLifecycleFormValues);
    const [contractLifecycleSchedules, setContractLifecycleSchedules] =
        useState<ContractLifecycleSchedule[]>([
            defaultLifecycleScheduleFormValues,
        ]);
    const [currentPeriodErrors, setCurrentPeriodErrors] = useState<
        Partial<ContractLifecycle> & Partial<ContractLifecycleSchedule>
    >({});
    const [subsequentPeriodErrors, setSubsequentPeriodErrors] = useState<
        Partial<ContractLifecycle> & Partial<ContractLifecycleSchedule>
    >({});
    const [terminationErrors, setTerminationErrors] = useState<
        Partial<ContractLifecycle> & Partial<ContractLifecycleSchedule>
    >({});
    const [serverError, setServerError] = useState<string | null>(null);
    const [isCollectionPeriodVisible, setIsCollectionPeriodVisible] =
        useState<boolean>(false);
    const [isSaving, setIsSaving] = useState(false);
    const [isSubsequentFormDisabled, setIsSubsequentFormDisabled] =
        useState<boolean>(false);

    const updateContractLifecycleSchedules =
        useUpdateContractLifecycleSchedules(contract.contractId);
    const createContractLifecycleSchedules =
        useCreateContractLifecycleSchedules(contract.contractId);
    const updateContractLifecycleTermStart =
        useUpdateContractLifecycleTermStart(contract.contractId);
    const terminateContract = useTerminateContract(contract.contractId);
    const updateContract = useUpdateContract(contract.contractId);
    const toast = useToast();
    const isFeatureEditContractCurrentPeriodStartEnabled = useFeatureFlag(
        USER_FEATURES.ABACUS_CONTRACT_EDIT_CURRENT_PERIOD_START_DATE
    );
    const hasContractLifecycleSchedule = () =>
        !isEmpty(contractLifecycleSchedules?.[0]?.contractLifecycleScheduleId);
    const isCurrentLifecyclePeriodEditable =
        isFeatureEditContractCurrentPeriodStartEnabled &&
        (contract.lifecycleSchedules?.[0]?.renewalType ===
            AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE ||
            isEmpty(contractLifecycle.lastRenewed?.toString()));

    const headerText = () => {
        if (contract.contractType === CONTRACT_TYPES.NEIGHBOURING_RIGHTS)
            return 'Renewal & Collection Rules';
        return 'Current Period & Renewal Rules';
    };

    const isSaveButtonDisabled = () => {
        if (isSaving) return true;

        if (isTerminatingContract)
            return !contractLifecycle?.terminationEffective;

        const currentSchedule = contractLifecycleSchedules[0];

        if (
            !currentSchedule ||
            (isCollectionPeriodVisible &&
                !currentSchedule.collectionPeriodDetailInterval)
        )
            return true;

        let isFormComplete: boolean = true;

        switch (currentSchedule.renewalType) {
            case AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE:
                isFormComplete =
                    !!currentSchedule.terminationNoticeDetailInterval;
                break;
            case AbacusContractLifecycleScheduleRenewalType.RENEW_AFTER_CERTAIN_DATE:
                isFormComplete =
                    !!currentSchedule.terminationNoticeDetailInterval &&
                    !!currentSchedule.scheduleEnd;
                break;
            case AbacusContractLifecycleScheduleRenewalType.RENEW_PERIODICALLY:
                isFormComplete =
                    !!currentSchedule.terminationNoticeDetailInterval &&
                    !!currentSchedule.renewalOffsetDetailInterval;
                break;
            default:
                isFormComplete = false;
        }

        if (!isFormComplete) return true;

        if (
            currentSchedule.renewalType !==
            AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE
        ) {
            const subsequentSchedule = contractLifecycleSchedules[1];

            if (!subsequentSchedule || !subsequentSchedule.renewalType)
                return true;

            switch (subsequentSchedule.renewalType) {
                case AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE:
                    isFormComplete =
                        !!subsequentSchedule.terminationNoticeDetailInterval;
                    break;
                case AbacusContractLifecycleScheduleRenewalType.RENEW_PERIODICALLY:
                    isFormComplete =
                        !!subsequentSchedule.terminationNoticeDetailInterval &&
                        !!subsequentSchedule.renewalOffsetDetailInterval;
                    break;
            }

            return !isFormComplete;
        }
    };

    const validateContractLifecycleSchedules = () => {
        let isValid: boolean = true;

        const currentScheduleValidation =
            contractLifecycleScheduleFormValidation(
                contractLifecycleSchedules[0],
                contractLifecycle.lifecycleTermStart,
                isFeatureEditContractCurrentPeriodStartEnabled
            );

        if (Object.keys(currentScheduleValidation).length > 0) {
            setCurrentPeriodErrors(currentScheduleValidation);
            isValid = false;
        }

        if (
            contractLifecycleSchedules[0].renewalType !==
            AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE
        ) {
            const subsequentSchedule = contractLifecycleSchedules[1];

            if (!subsequentSchedule) return isValid;

            const subsequentScheduleValidation =
                contractLifecycleScheduleFormValidation(
                    subsequentSchedule,
                    contractLifecycle.lifecycleTermStart,
                    isFeatureEditContractCurrentPeriodStartEnabled
                );

            if (Object.keys(subsequentScheduleValidation).length > 0) {
                setSubsequentPeriodErrors(subsequentScheduleValidation);
                isValid = false;
            }
        }

        return isValid;
    };

    const handleCancel = () => {
        setIsSidecarOpen(false);
        setIsTerminatingContract(false);
        setIsCollectionPeriodVisible(
            contract.lifecycleSchedules.length > 0 &&
                !!contract.lifecycleSchedules[0].collectionPeriodDetail
        );
        setContractLifecycle(contract.lifecycle || defaultLifecycleFormValues);

        const schedulesInput = contract.lifecycleSchedules?.length
            ? contract.lifecycleSchedules
            : contractLifecycleSchedules;
        setContractLifecycleSchedules(
            formatContractLifecycleSchedulesDetailInput(
                // @ts-expect-error TODO: types of contract.lifecycleSchedules
                // and contractLifecycleSchedules don't align but the shape is
                // the same. Need to annotate them properly. The former comes
                // from a GQL query and the latter from a custom annotation.
                // Need to figure out which is correct and use that one for
                // both. Tech Debt: ACC-9203
                schedulesInput
            )
        );
    };

    const handleSave = async () => {
        setIsSaving(true);

        if (isTerminatingContract) await handleTerminationSave();
        else await handleLifecycleSave();
    };

    const handleLifecycleSave = async () => {
        const areLifecycleSchedulesAlreadyExist =
            hasContractLifecycleSchedule();
        const isFormValid: boolean = validateContractLifecycleSchedules();

        if (!isFormValid) return;

        const lifecycle: AbacusContractLifecycleInput = {
            lifecycleTermStart: contractLifecycle.lifecycleTermStart,
        };
        const formattedSchedules = formatContractLifecycleSchedules(
            contractLifecycleSchedules as AbacusContractLifecycleScheduleUpdate[],
            contract.contractType,
            isCollectionPeriodVisible,
            lifecycle,
            areLifecycleSchedulesAlreadyExist
        );

        const schedulesVariables: UpdateContractLifecycleSchedulesMutationVariables =
            {
                contractId: contract.contractId,
                lifecycleSchedules:
                    formattedSchedules as AbacusContractLifecycleScheduleUpdate[],
                // @ts-expect-error TODO: update GQL types
                contractLifecycle: lifecycle,
            };
        const schedulesInput = { variables: schedulesVariables };
        const termStartVariables: UpdateContractLifecycleTermStartMutationVariables =
            {
                contractId: contract.contractId,
                lifecycle,
            };

        try {
            if (hasContractLifecycleSchedule()) {
                await updateContractLifecycleSchedules(schedulesInput);
                await updateContractLifecycleTermStart({
                    variables: termStartVariables,
                });
            } else {
                await createContractLifecycleSchedules(schedulesInput);
                await updateContract({
                    variables: {
                        contractId: contract.contractId,
                        initialStartDate: lifecycle.lifecycleTermStart,
                    },
                });
            }

            toast(updateContractMessage(contract?.contractName));
            setContractLifecycle(contractLifecycle);
            setParentContractLifecycle(contractLifecycle);
            setIsSidecarOpen(false);
        } catch (e) {
            setServerError(
                `${(e as Error).message}, Failed to update ${headerText()}.`
            );
        } finally {
            setIsSaving(false);
        }
    };

    const handleTerminationSave = async () => {
        if (isTerminatingContract) {
            const errors = contractTerminationFormValidation(
                contract?.initialStartDate,
                contractLifecycle.lifecycleTermStart!,
                contract?.lifecycle?.lifecycleTermEnd,
                contractLifecycle?.terminationEffective
            );

            if (!isEmpty(errors)) {
                setTerminationErrors(errors);
                setIsSaving(false);
                return;
            }

            const variables = {
                contractId: contract?.contractId,
                terminationEffective: contractLifecycle?.terminationEffective,
                terminationNoticeReceived:
                    contractLifecycle?.terminationNoticeReceived,
            };

            return await terminateContract({ variables })
                .then(() => {
                    toast(updateContractMessage(contract?.contractName));
                    setIsSidecarOpen(false);
                    setIsSaving(false);
                })
                .catch(e => {
                    setServerError(
                        `Error ${e.message}, Failed to terminate contract.`
                    );
                    setIsSaving(false);
                });
        }
    };

    useEffect(() => {
        setTerminationErrors({});
        setServerError(null);
    }, [isTerminatingContract]);

    useEffect(() => {
        if (contract.lifecycle) setContractLifecycle(contract.lifecycle);
        if (contract.lifecycleSchedules?.length) {
            setContractLifecycleSchedules(
                formatContractLifecycleSchedulesDetailInput(
                    contract.lifecycleSchedules
                )
            );

            setIsCollectionPeriodVisible(
                !!contract.lifecycleSchedules[0].collectionPeriodDetail
            );
        }

        setCurrentPeriodErrors({});
        setSubsequentPeriodErrors({});
        setServerError(null);
    }, [contract]);

    useEffect(() => {
        if (
            contractLifecycleSchedules &&
            contractLifecycleSchedules.length > 0 &&
            contractLifecycleSchedules[0].renewalType
        ) {
            const isContinuouslyActive: boolean =
                contractLifecycleSchedules[0].renewalType ===
                `${AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE}`;

            setIsSubsequentFormDisabled(isContinuouslyActive);
        } else setIsSubsequentFormDisabled(true);
    }, [contractLifecycleSchedules]);

    return (
        <Sidecar
            className="EditContractRenewalSidecar"
            confirmDisabled={isSaveButtonDisabled()}
            isOpen={isSidecarOpen}
            onConfirm={handleSave}
            onRequestClose={handleCancel}
            testId="ContractLifecycleEditRenewalFormSidecar"
            title={headerText()}
        >
            <Form>
                <Row>
                    <Col>
                        <Form.Group>
                            <CurrentPeriodFormFields
                                contractLifecycle={contractLifecycle}
                                contractType={contract.contractType}
                                currentSchedule={contractLifecycleSchedules[0]}
                                defaultFormValues={
                                    defaultLifecycleScheduleFormValues
                                }
                                errors={currentPeriodErrors}
                                isCollectionPeriodVisible={
                                    isCollectionPeriodVisible
                                }
                                isEditForm={isCurrentLifecyclePeriodEditable}
                                isDisabled={isTerminatingContract}
                                setContractLifecycle={setContractLifecycle}
                                setContractLifecycleSchedules={
                                    setContractLifecycleSchedules
                                }
                                setErrors={setCurrentPeriodErrors}
                                setIsCollectionPeriodVisible={
                                    setIsCollectionPeriodVisible
                                }
                                subsequentSchedule={
                                    contractLifecycleSchedules[1]
                                }
                            />
                        </Form.Group>
                    </Col>
                </Row>
                <br />
                <br />
                <Row>
                    <Col>
                        <Form.Group className="EditContractRenewalSidecar-SubsequentPeriod">
                            <h4>Subsequent Period</h4>
                            <SubsequentPeriodFormFields
                                currentSchedule={
                                    contractLifecycleSchedules[1] ||
                                    defaultLifecycleScheduleFormValues
                                }
                                contractLifecycleSchedules={
                                    contractLifecycleSchedules
                                }
                                defaultFormValues={
                                    defaultLifecycleScheduleFormValues
                                }
                                errors={subsequentPeriodErrors}
                                isDisabled={
                                    isSubsequentFormDisabled ||
                                    isTerminatingContract
                                }
                                isEditForm={true}
                                setContractLifecycleSchedules={
                                    setContractLifecycleSchedules
                                }
                                setErrors={setSubsequentPeriodErrors}
                            />
                        </Form.Group>
                    </Col>
                </Row>
                <Row>
                    <Col>
                        <Form.Group>
                            {contractLifecycle?.lifecycleStatus ==
                                AbacusContractLifecycleStatus.ACTIVE && (
                                <ContractLifecycleTermination
                                    contractInitialStartDate={
                                        contract.initialStartDate
                                    }
                                    contractLifecycle={contractLifecycle}
                                    errors={terminationErrors}
                                    isPrimaryContract={
                                        contract.isPrimaryContract
                                    }
                                    isTerminatingContract={
                                        isTerminatingContract
                                    }
                                    setContractLifecycle={setContractLifecycle}
                                    setErrors={setTerminationErrors}
                                    setIsTerminatingContract={
                                        setIsTerminatingContract
                                    }
                                />
                            )}
                        </Form.Group>
                    </Col>
                </Row>
                <br />
                <Row>
                    <Col>
                        {serverError && (
                            <Alert
                                testId="error-alert"
                                variant="error"
                                text={serverError}
                            />
                        )}
                    </Col>
                </Row>
            </Form>
        </Sidecar>
    );
};

const defaultLifecycleFormValues = {
    lifecycleTermStart: null,
    terminationEffective: null,
    terminationNoticeReceived: null,
};

const defaultLifecycleScheduleFormValues: ContractLifecycleSchedule = {
    collectionPeriodDetailInterval: null,
    collectionPeriodDetailType: AbacusContractLifecycleSchedulePeriodType.DAY,
    renewalType: null,
    renewalOffsetDetailInterval: null,
    renewalOffsetDetailType: AbacusContractLifecycleSchedulePeriodType.MONTH,
    scheduleEnd: null,
    terminationNoticeDetailInterval: null,
    terminationNoticeDetailType: AbacusContractLifecycleSchedulePeriodType.DAY,
};
