import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { Alert, LoadingPageIndicator } from '@theorchard/suite-components';
import {
    PageHeader as SuitePageHeader,
    Section,
} from '@theorchard/suite-components';
import { Page } from '@theorchard/suite-frontend';
import {
    ABACUS_ACTION_STATUSES,
    ABACUS_ACTIONS,
} from '@theorchard/accounting-apps-shared';
import { get, isEmpty, some, pick } from 'lodash-es';
import { useParams, Link } from 'react-router-dom';
import { useAccountingPeriodBreadcrumbs } from 'src/hooks/breadcrumbs/accountingPeriod';
import { getStatementPeriodDetail } from 'src/urls/frontend-royalties';
import { useUpdateAccountingPeriodState } from 'src/apollo/mutations/accounting-period';
import { useUpdateAccountingRun } from 'src/apollo/mutations/accounting-run';
import { useAccountingPeriodFullDetail } from 'src/apollo/queries/accounting-period';
import {
    ACCOUNTING_PERIOD_STATUSES,
    ACCOUNTING_RUN_STATUSES,
    ACCOUNTING_RUN_POLLING_STATUSES,
    ACCOUNTING_RUN_POLLING_INTERVAL,
    CLOSING_PERIOD_POLLING_INTERVAL,
    CONTRACT_TYPES,
    RUN_MECH_DEDUCTIONS_POLLING_INTERVAL,
    SALES_FILE_PROCESSING_POLLING_INTERVAL,
    SALES_FILE_PROCESSING_POLLING_STATUSES,
} from 'src/constants';
import { CONTRACT_TYPE_MAP } from 'src/apollo/type-constants/contract';
import { AccountingRunContext } from 'src/contexts/accounting-run-context';
import { mapAccountingRuns } from 'src/utils/accounting-run';
import { getContractEndDateByPeriod } from 'src/utils/date-helpers';
import { getValuesMap } from 'src/utils/object-keys';
import AccountingRunsTable from 'src/components/accounting-run-list/accounting-runs-table';
import { AddEligibleSalesForm } from 'src/components/accounting-period/add-eligible-sales-form';
import { ApproveSalesFiles } from 'src/components/accounting-period/approve-sales-files';
import { ClosePeriodAction } from 'src/components/accounting-period/close-period-action';
import { ClosePeriodModal } from 'src/components/accounting-period/close-period-modal';
import { DeliverSalesFiles } from 'src/components/accounting-period/deliver-sales-files';
import { EligibleSalesTable } from 'src/components/accounting-period/eligible-sales-table';
import { ExchangeRates } from 'src/components/accounting-period/exchange-rates';
import { MarkRunsAsCompleteAction } from 'src/components/accounting-period/mark-runs-as-complete-action';
import { MechanicalDeductions } from 'src/components/accounting-period/mechanical-deductions';
import type {
    AbacusAccountingPeriod,
    AbacusAccountingPeriodActionState,
    AbacusFormattedAccountingRun,
    AbacusStatementPeriodActionStates,
    AbacusSalesFileActionState,
    AbacusSalesFiles,
    AbacusSalesFile,
    AbacusUpdateAccountingPeriodStateMutationVariables,
} from 'src/types/accounting-period';

export const AccountingPeriodDetailView: React.FC = () => {
    const { periodId: accountingPeriodId } = useParams<{ periodId: string }>();
    const [accountingPeriodDetail, setAccountingPeriodDetail] =
        useState<AbacusAccountingPeriod>();
    const [areSalesDelivered, setAreSalesDelivered] = useState<boolean>(false);
    const [accountingRuns, setAccountingRuns] =
        useState<AbacusFormattedAccountingRun>({});
    const [actionStates, setActionStates] = useState<
        Record<string, AbacusAccountingPeriodActionState>
    >({});
    const [sales, setSales] = useState<AbacusSalesFiles>([]);
    const [isPeriodClosed, setIsPeriodClosed] = useState<boolean>(false);
    const [updateAccountingRuns, setUpdateAccountingRuns] =
        useState<AbacusFormattedAccountingRun>({});

    const [error, setError] = useState<string>('');
    const [accountingRunsInterval, setAccountingRunsInterval] =
        useState<ReturnType<typeof setInterval> | null>(null);
    const [closingPeriod, setClosingPeriod] = useState<boolean>(false);
    const [runMechDeductions, setRunMechDeductions] = useState<boolean>(false);
    const [runSalesProcessing, setRunSalesProcessing] =
        useState<boolean>(false);
    const [isUpdatingPeriodState, setIsUpdatingPeriodState] =
        useState<boolean>(false);

    const breadcrumbs: any = useAccountingPeriodBreadcrumbs();
    const { data, loading, refetch } =
        useAccountingPeriodFullDetail(accountingPeriodId);

    const updateAccountingRun = useUpdateAccountingRun(
        parseInt(accountingPeriodId, 10)
    );
    const updateAccountingPeriodState = useUpdateAccountingPeriodState(
        parseInt(accountingPeriodId, 10)
    );

    const isSalesFilesComplete = (sale: AbacusSalesFile | null) =>
        !isEmpty(sale?.actionStates) &&
        sale?.actionStates?.[0]?.actionStatus ===
            ABACUS_ACTION_STATUSES.COMPLETE;

    const checkSalesFilesStatus = () =>
        !isEmpty(sales) && sales.every(isSalesFilesComplete);

    const updateRunStatus = useCallback(
        (accountingRunId: string, accountingRunStatus: string) => {
            // mappedRunStatus is used because the backend uses
            // "committing" and "committed" in favor of
            // "approving" and "approved"
            let mappedRunStatus = accountingRunStatus;
            if (mappedRunStatus === ACCOUNTING_RUN_STATUSES.APPROVING)
                mappedRunStatus = ACCOUNTING_RUN_STATUSES.COMMITTING_DEPRECATED;
            else if (mappedRunStatus === ACCOUNTING_RUN_STATUSES.APPROVED)
                mappedRunStatus = ACCOUNTING_RUN_STATUSES.COMMITTED_DEPRECATED;

            const variables = {
                accountingRunId,
                accountingRunStatus: mappedRunStatus,
            };
            return updateAccountingRun({ variables })
                .then(({ data }) => {
                    if (!data) return;
                    const { abacusUpdateAccountingRun } = data;
                    const accountingRunsData = mapAccountingRuns(
                        abacusUpdateAccountingRun
                    );
                    setUpdateAccountingRuns(accountingRunsData);
                })
                .catch(err => setError(err));
        },
        [updateAccountingRun]
    );

    const getActionStatus = (action: string) => {
        const actionState = get(actionStates, action, {});
        return get(actionState, 'actionStatus', '');
    };

    const getAbacusStateId = (action: string) => {
        const actionState = get(actionStates, action, {});
        return get(actionState, 'abacusStateId', '');
    };

    const isActionComplete = (action: string) => {
        if (!action) return true;
        return getActionStatus(action) === ABACUS_ACTION_STATUSES.COMPLETE;
    };

    const updatePeriodState = (
        newStates: Partial<AbacusAccountingPeriodActionState>[]
    ) => {
        const clearedStates: Pick<
            AbacusAccountingPeriodActionState,
            'abacusStateId' | 'actionStatus'
        >[] = [];
        newStates
            .filter(
                (item): item is AbacusAccountingPeriodActionState =>
                    item !== null
            )
            .forEach((item: AbacusAccountingPeriodActionState) =>
                clearedStates.push(
                    pick(item, ['abacusStateId', 'actionStatus'])
                )
            );
        const variables: AbacusUpdateAccountingPeriodStateMutationVariables = {
            accountingPeriodId,
            actionStates: clearedStates,
        };
        setIsUpdatingPeriodState(true);
        return updateAccountingPeriodState({ variables })
            .then(({ data }) => {
                if (!data) return;

                const { abacusUpdateAccountingPeriodState } = data;

                const newState = getValuesMap(
                    abacusUpdateAccountingPeriodState,
                    'actionName'
                );
                setActionStates({
                    ...actionStates,
                    ...newState,
                } as Record<string, AbacusAccountingPeriodActionState>);
            })
            .catch(err => setError(err))
            .finally(() => setIsUpdatingPeriodState(false));
    };

    const contextValue = useMemo(
        () => ({
            accountingRuns,
            arePeriodActionsComplete:
                isActionComplete(ABACUS_ACTIONS.UPLOAD_EXCHANGE_RATES) &&
                isActionComplete(ABACUS_ACTIONS.APPROVE_SALES_FILES) &&
                (accountingPeriodDetail?.contractType ===
                    CONTRACT_TYPES.NEIGHBOURING_RIGHTS ||
                    isActionComplete(
                        ABACUS_ACTIONS.PREP_MECHANICAL_DEDUCTIONS
                    )),
            updateRunStatus,
            accountingPeriodDetail,
            areRunsComplete: isActionComplete(
                ABACUS_ACTIONS.MARK_RUNS_AS_COMPLETE
            ),
            closePeriodActionStatus: getActionStatus(
                ABACUS_ACTIONS.CLOSE_PERIOD
            ),
        }),
        [accountingRuns, actionStates, updateRunStatus, accountingPeriodDetail]
    );

    useEffect(() => {
        if (!isEmpty(data) && !isEmpty(data.abacusAccountingPeriod))
            setAccountingPeriodDetail(data?.abacusAccountingPeriod);
    }, [data]);

    useEffect(() => {
        if (!isEmpty(accountingPeriodDetail)) {
            let abacusActionStates: AbacusStatementPeriodActionStates = [];
            setIsPeriodClosed(
                Boolean(accountingPeriodDetail.closedDate) &&
                    accountingPeriodDetail.accountingPeriodStatus ===
                        ACCOUNTING_PERIOD_STATUSES.CLOSED
            );
            if (
                accountingPeriodDetail.accountingRuns &&
                !isEmpty(accountingPeriodDetail.accountingRuns.items)
            )
                setAccountingRuns(
                    mapAccountingRuns(
                        accountingPeriodDetail.accountingRuns.items
                    )
                );

            if (accountingPeriodDetail.salesFile) {
                setSales(accountingPeriodDetail.salesFile);
            }

            if (accountingPeriodDetail.actionStates)
                abacusActionStates = [...accountingPeriodDetail.actionStates];

            if (
                accountingPeriodDetail.statementPeriod &&
                accountingPeriodDetail.statementPeriod.actionStates
            )
                abacusActionStates = [
                    ...abacusActionStates,
                    ...accountingPeriodDetail.statementPeriod.actionStates,
                ];

            setActionStates(
                getValuesMap(abacusActionStates, 'actionName') as Record<
                    string,
                    AbacusAccountingPeriodActionState
                >
            );
        }
        return () => {
            if (accountingRunsInterval) clearInterval(accountingRunsInterval);
        };
    }, [accountingPeriodDetail]);

    useEffect(() => {
        if (!isEmpty(accountingRuns))
            setAccountingRuns({ ...updateAccountingRuns });
    }, [updateAccountingRuns]);

    useEffect(() => {
        let mechDeductionInterval: ReturnType<typeof setInterval> | undefined =
            undefined;
        let closingPeriodInterval: ReturnType<typeof setInterval> | undefined =
            undefined;
        if (!isEmpty(actionStates)) {
            setAreSalesDelivered(
                isActionComplete(ABACUS_ACTIONS.DELIVER_SALES_FILES)
            );
            if (
                getActionStatus(ABACUS_ACTIONS.PREP_MECHANICAL_DEDUCTIONS) ===
                ABACUS_ACTION_STATUSES.RUNNING
            ) {
                if (!runMechDeductions)
                    mechDeductionInterval = setInterval(() => {
                        refetch();
                    }, RUN_MECH_DEDUCTIONS_POLLING_INTERVAL);
            } else {
                clearInterval(mechDeductionInterval);
                if (mechDeductionInterval) setRunMechDeductions(true);
            }

            if (
                getActionStatus(ABACUS_ACTIONS.CLOSE_PERIOD) ===
                ABACUS_ACTION_STATUSES.RUNNING
            ) {
                if (!closingPeriod)
                    closingPeriodInterval = setInterval(() => {
                        refetch();
                    }, CLOSING_PERIOD_POLLING_INTERVAL);
            } else {
                clearInterval(closingPeriodInterval);
                if (closingPeriodInterval) setClosingPeriod(true);
            }
        }
        return () => {
            if (closingPeriodInterval) clearInterval(closingPeriodInterval);

            if (mechDeductionInterval) clearInterval(mechDeductionInterval);
        };
    }, [actionStates]);

    useEffect(() => {
        let salesInterval: ReturnType<typeof setInterval> | undefined =
            undefined;
        if (!isEmpty(sales)) {
            const isSalesFileRunning = sales.find(
                (sale: AbacusSalesFile | null) =>
                    sale!.actionStates!.some(
                        (state: AbacusSalesFileActionState | null) =>
                            state?.actionStatus &&
                            SALES_FILE_PROCESSING_POLLING_STATUSES.includes(
                                state.actionStatus
                            )
                    ) ?? false
            );

            if (isSalesFileRunning && !runSalesProcessing)
                salesInterval = setInterval(() => {
                    refetch();
                }, SALES_FILE_PROCESSING_POLLING_INTERVAL);
            else {
                clearInterval(salesInterval);
                if (salesInterval) setRunSalesProcessing(true);
            }
        }
        return () => {
            if (salesInterval) clearInterval(salesInterval);
        };
    }, [sales]);

    useEffect(() => {
        if (
            some(
                Object.values(
                    accountingRuns
                ) as AbacusFormattedAccountingRun[string][],
                (item: AbacusFormattedAccountingRun[string]) =>
                    ACCOUNTING_RUN_POLLING_STATUSES.includes(item.status ?? '')
            )
        ) {
            if (!accountingRunsInterval)
                setAccountingRunsInterval(
                    setInterval(() => {
                        refetch();
                    }, ACCOUNTING_RUN_POLLING_INTERVAL)
                );
        } else {
            if (accountingRunsInterval) clearInterval(accountingRunsInterval);
            setAccountingRunsInterval(null);
        }
    }, [accountingRuns]);

    const isAllSalesDeliveredButtonDisabled =
        !isActionComplete(ABACUS_ACTIONS.UPLOAD_EXCHANGE_RATES) ||
        !checkSalesFilesStatus();

    const validClosePeriodStatuses = [
        ABACUS_ACTION_STATUSES.RUNNING,
        ABACUS_ACTION_STATUSES.COMPLETE,
    ];

    const isClosePeriodLinkDisabled = () => {
        const previousActionStatus = !isActionComplete(
            ABACUS_ACTIONS.MARK_RUNS_AS_COMPLETE
        );
        return previousActionStatus || isPeriodClosed;
    };

    if (loading || isEmpty(actionStates)) return <LoadingPageIndicator />;

    return (
        <div
            className="AccountingPeriodDetail"
            data-testid="AccountingPeriodDetail"
        >
            <SuitePageHeader breadcrumbs={breadcrumbs}>
                <SuitePageHeader.Metadata>
                    <span className="suite-text-small">
                        <b style={{ color: 'var(--text-secondary)' }}>
                            Contract Type:{' '}
                        </b>{' '}
                        {accountingPeriodDetail &&
                            CONTRACT_TYPE_MAP[
                                accountingPeriodDetail?.contractType
                            ]}
                        <span className="mx-2 text-secondary">|</span>
                        <b style={{ color: 'var(--text-secondary)' }}>
                            Statement Period:{' '}
                        </b>
                        <Link
                            to={getStatementPeriodDetail(
                                accountingPeriodDetail?.statementPeriod
                                    ?.statementPeriodId
                            )}
                        >
                            {
                                accountingPeriodDetail?.statementPeriod
                                    .statementPeriodName
                            }
                        </Link>
                        <span className="mx-2 text-secondary">|</span>
                        <b style={{ color: 'var(--text-secondary)' }}>
                            Period ID:
                        </b>{' '}
                        {
                            accountingPeriodDetail?.statementPeriod
                                ?.statementPeriodId
                        }
                    </span>
                </SuitePageHeader.Metadata>

                <SuitePageHeader.MainContent>
                    <SuitePageHeader.Title>
                        {accountingPeriodDetail?.accountingPeriodName}
                    </SuitePageHeader.Title>
                    <div className="ml-4">
                        Contract End Date:{' '}
                        {getContractEndDateByPeriod(
                            parseInt(
                                accountingPeriodDetail?.statementPeriod
                                    ?.statementPeriodId ?? '',
                                10
                            )
                        )}
                    </div>
                </SuitePageHeader.MainContent>
            </SuitePageHeader>
            <Page.Grid className="AccountingPeriodDetail-body">
                {error && (
                    <Page.Col>
                        <Alert variant="error" text={error} />
                    </Page.Col>
                )}
                <Page.Col lg={9}>
                    <Section>
                        <Section.Header title="Eligible Sales" />
                        <Section.Body>
                            <Section>
                                {!isPeriodClosed && !areSalesDelivered && (
                                    <AddEligibleSalesForm
                                        periodId={accountingPeriodId}
                                        refetchDetailsOnSalesAdd={refetch}
                                    />
                                )}
                                <EligibleSalesTable
                                    sales={sales}
                                    isPeriodLocked={
                                        accountingPeriodDetail?.accountingPeriodStatus ===
                                        ACCOUNTING_PERIOD_STATUSES.LOCKED
                                    }
                                    onStatusUpdate={refetch}
                                    setError={setError}
                                />
                            </Section>
                        </Section.Body>
                    </Section>
                </Page.Col>
                <Page.Col lg={3}>
                    <Section>
                        <Section.Header title="Status / Actions" />
                        <Section.Body>
                            <Section>
                                <>
                                    <ExchangeRates
                                        exchangeRatesUploaded={isActionComplete(
                                            ABACUS_ACTIONS.UPLOAD_EXCHANGE_RATES
                                        )}
                                    />
                                    <DeliverSalesFiles
                                        abacusStateId={getAbacusStateId(
                                            ABACUS_ACTIONS.DELIVER_SALES_FILES
                                        )}
                                        updatePeriodState={updatePeriodState}
                                        areSalesDelivered={areSalesDelivered}
                                        isButtonDisabled={
                                            isAllSalesDeliveredButtonDisabled
                                        }
                                        setError={setError}
                                    />
                                    <ApproveSalesFiles
                                        actionStatus={getActionStatus(
                                            ABACUS_ACTIONS.APPROVE_SALES_FILES
                                        )}
                                    />
                                    <MechanicalDeductions
                                        abacusStateId={getAbacusStateId(
                                            ABACUS_ACTIONS.PREP_MECHANICAL_DEDUCTIONS
                                        )}
                                        actionStatus={getActionStatus(
                                            ABACUS_ACTIONS.PREP_MECHANICAL_DEDUCTIONS
                                        )}
                                        accountingPeriodId={parseInt(
                                            accountingPeriodId,
                                            10
                                        )}
                                        contractType={
                                            accountingPeriodDetail!.contractType
                                        }
                                        isButtonDisabled={
                                            !isActionComplete(
                                                ABACUS_ACTIONS.APPROVE_SALES_FILES
                                            ) || isUpdatingPeriodState
                                        }
                                        updatePeriodState={updatePeriodState}
                                        setError={setError}
                                    />
                                    <MarkRunsAsCompleteAction
                                        abacusStateId={getAbacusStateId(
                                            ABACUS_ACTIONS.MARK_RUNS_AS_COMPLETE
                                        )}
                                        actionStatus={getActionStatus(
                                            ABACUS_ACTIONS.MARK_RUNS_AS_COMPLETE
                                        )}
                                        updatePeriodState={updatePeriodState}
                                        accountingRuns={accountingRuns}
                                        setError={setError}
                                    />
                                    {!validClosePeriodStatuses.includes(
                                        getActionStatus(
                                            ABACUS_ACTIONS.CLOSE_PERIOD
                                        )
                                    ) ? (
                                        <ClosePeriodModal
                                            isButtonDisabled={
                                                isClosePeriodLinkDisabled() ||
                                                isUpdatingPeriodState
                                            }
                                            updatePeriodState={
                                                updatePeriodState
                                            }
                                            actionStatus={getActionStatus(
                                                ABACUS_ACTIONS.CLOSE_PERIOD
                                            )}
                                            closeState={
                                                get(
                                                    actionStates,
                                                    ABACUS_ACTIONS.CLOSE_PERIOD,
                                                    {}
                                                ) as AbacusSalesFileActionState
                                            }
                                            accountingPeriodId={parseInt(
                                                accountingPeriodId,
                                                10
                                            )}
                                            setError={setError}
                                        />
                                    ) : (
                                        <ClosePeriodAction
                                            accountingPeriodStatus={
                                                accountingPeriodDetail?.accountingPeriodStatus
                                            }
                                            periodClosed={getActionStatus(
                                                ABACUS_ACTIONS.CLOSE_PERIOD
                                            )}
                                        />
                                    )}
                                </>
                            </Section>
                        </Section.Body>
                    </Section>
                </Page.Col>
                <Page.Col>
                    <AccountingRunContext.Provider value={contextValue}>
                        <AccountingRunsTable />
                    </AccountingRunContext.Provider>
                </Page.Col>
                <Page.Col>
                    {isPeriodClosed && (
                        <span
                            className="accountingPeriodCloseMsg"
                            data-testid="close-date"
                        >
                            {`This accounting period was closed on
                                ${accountingPeriodDetail?.closedDate || ''}.`}
                        </span>
                    )}
                </Page.Col>
            </Page.Grid>
        </div>
    );
};

export default AccountingPeriodDetailView;
