import type { FC } from 'react';
import React, { useEffect, useRef } from 'react';
import { Alert, ErrorMessage } from '@theorchard/suite-components';
import FullscreenModalReview from 'src/components/fullscreenModalReview';
import {
    ROUTE_REVIEW_QUEUE,
    ROUTE_REVIEW_QUEUE_PRODUCT_MODAL,
} from 'src/constants';
import { useMoveReviewQueueItemToQueueMutation } from 'src/data/mutations/moveReviewQueueItemToQueue';
import { useUpsertTracksAudioAttributesEditsMutation } from 'src/data/mutations/upsertTracksAudioAttributesEdits';
import { useUpsertTracksRightsAttributesEditsMutation } from 'src/data/mutations/upsertTracksRightsAttributesEdits';
import {
    useGenreAndSubgenreQuery,
    useMetaLanguageListQuery,
    useProductQuery,
} from 'src/data/queries';
import { useProductValidationsQuery } from 'src/data/queries/productValidation';
import { MergedProduct } from 'src/product/product';
import { getAudioAttributes, getRightsAttributes } from 'src/utils/attributes';
import {
    useDisplayProductModalPageHeaderFF,
    usePreventApprovalWithBlockersFF,
} from 'src/utils/features';
import {
    isPotentialAudioInfringementVal,
    trackContentReviewEvent,
    trackProductClickEvent,
} from 'src/utils/product';
import { useChangeURLPath } from 'src/utils/routev2';
import ProductModalBody from './components/productModalBody';
import ProductModalBodyLoading from './components/productModalBodyLoading';
import type { FlattenedApolloError } from '@theorchard/suite-frontend';
import type { Product } from 'src/data/queries/product/types';
import type { TargetGroup } from 'src/data/queries/reviewQueueItem/types';
import type { SubmissionType } from 'src/types';
export const CLASS_NAME = 'ProductModal';
export const INTL_PREFIX = 'contentReview.productModal';

interface Props {
    reviewQueueItems: object | null;
    currentReviewQueueItemId: number;
    nextReviewQueueItemId: number | null;
    productId: number | undefined;
    submissionType: SubmissionType | undefined;
    productTargetGroup: TargetGroup | null;
    queueName: string | undefined;
    reviewQueueItemQueryError: FlattenedApolloError | undefined;
    isReviewQueueItemLocked: boolean;
    submittedByUserLanguage: string | null;
    lockedByUserName: string | undefined;
    wasExistingLockDetected: boolean;
}

const ProductModal: FC<Props> = ({
    reviewQueueItems,
    currentReviewQueueItemId,
    nextReviewQueueItemId,
    productId,
    submissionType,
    productTargetGroup,
    queueName,
    reviewQueueItemQueryError,
    isReviewQueueItemLocked,
    submittedByUserLanguage,
    lockedByUserName,
    wasExistingLockDetected,
}) => {
    const {
        error,
        refetch,
        data: product,
        loading: isLoadingProduct,
    } = useProductQuery(Number(productId), currentReviewQueueItemId);

    const hasError = error || reviewQueueItemQueryError;

    const [isSubmittingReview, setIsSubmittingReview] =
        React.useState<boolean>(false);
    const [isOpen, setIsOpen] = React.useState<boolean>(
        Boolean(currentReviewQueueItemId)
    );
    const [isFailedReviewCompletion, setIsFailedReviewCompletion] =
        React.useState<boolean>(false);

    // Track entry time, current review item ID, and closing event type
    const sessionStartTime = useRef<number | null>(null);
    const reviewItemRef = useRef<number | null>(null);
    const eventTypeRef = useRef<string | null>(null);

    const {
        data: validations,
        loading: isLoadingValidations,
        error: validationsError,
    } = useProductValidationsQuery(product?.productId);
    const hasPotentialAudioInfringement =
        validations?.some(validation =>
            isPotentialAudioInfringementVal(validation.code)
        ) ?? false;

    const displayProductModalPageHeader = useDisplayProductModalPageHeaderFF();

    const navigateToReviewQueueV2 = useChangeURLPath({
        pathTemplate: ROUTE_REVIEW_QUEUE,
    });

    const navigateToNextReviewQueueItemV2 = useChangeURLPath({
        pathTemplate: ROUTE_REVIEW_QUEUE_PRODUCT_MODAL,
        pathParams: {
            reviewQueueId: nextReviewQueueItemId,
        },
    });

    const invalidateGqlCache = async () => {
        await refetch({ id: Number(productId).toString() });
    };

    const parseSubgenreRevision = (value: string) => {
        const decoded = JSON.parse(value) as number[];
        return decoded[0];
    };

    const getRevisedGenreIds = (
        productData: Product | null
    ): (number | undefined)[] => {
        const revisedGenre = productData?.releaseCorrection?.items.find(
            revision =>
                revision.fieldName === 'genre_id' &&
                revision.tableName === 'releases'
        );
        const revisedSubgenre = productData?.releaseCorrection?.items.find(
            revision =>
                revision.fieldName === 'release_subgenre' &&
                revision.tableName === 'releases'
        );
        let revisedGenreId: number | undefined;
        let revisedSubgenreId: number | undefined;
        if (revisedGenre) {
            revisedGenreId = parseInt(revisedGenre.keyValue, 10);
        }
        if (revisedSubgenre) {
            revisedSubgenreId = parseSubgenreRevision(revisedSubgenre.keyValue);
        }
        if (revisedSubgenreId && !revisedGenreId) {
            revisedGenreId = productData?.subgenre?.genre.id;
        }
        if (revisedGenreId && !revisedSubgenreId) {
            revisedSubgenreId = productData?.subgenre?.id;
        }
        return [revisedGenreId, revisedSubgenreId];
    };

    const getRevisedMetadataCodes = (productData: Product | null): string[] => {
        const revisedMetadataCodes: string[] = [];
        productData?.releaseCorrection?.items.forEach(correction => {
            if (correction.fieldName === 'meta_language') {
                revisedMetadataCodes.push(
                    JSON.parse(correction.keyValue) as string
                );
            }
        });
        return revisedMetadataCodes;
    };

    // supplement metadata revision data with calls to graphql
    const revisedMetadataCodes = getRevisedMetadataCodes(product);
    const { data: metadataLanguageMap, loading: isLoadingMetadataLanguageMap } =
        useMetaLanguageListQuery(revisedMetadataCodes);
    // supplement genre revision data with calls to graphql
    const revisedIds = getRevisedGenreIds(product);
    const { data: genreMap, loading: isLoadingGenreMap } =
        useGenreAndSubgenreQuery(revisedIds[0], revisedIds[1]);

    const mergedProduct: MergedProduct | null = product
        ? new MergedProduct(product, metadataLanguageMap, genreMap, validations)
        : null;

    const [isMergedProductFullyLoaded, setIsMergedProductFullyLoaded] =
        React.useState<boolean>(false);
    const [isPreloadingState, setIsPreLoadingState] =
        React.useState<boolean>(true);

    const [updateTracksAudioAttributesEdits] =
        useUpsertTracksAudioAttributesEditsMutation();

    const [updateTracksRightsAttributesEdits] =
        useUpsertTracksRightsAttributesEditsMutation();

    const resetAudioAttributesEdits = async () => {
        if (product) {
            await updateTracksAudioAttributesEdits({
                variables: {
                    input: product.tracks.map(({ tuid }) => ({
                        tuid,
                        audioAttributeIds: [],
                    })),
                },
            });
        }
    };

    const resetRightsAttributesEdits = async () => {
        if (product) {
            await updateTracksRightsAttributesEdits({
                variables: {
                    input: product.tracks.map(({ tuid }) => ({
                        tuid,
                        rightsAttributeIds: [],
                    })),
                },
            });
        }
    };

    useEffect(() => {
        // Start timing when the modal opens (currentReviewQueueItemId present)
        if (isOpen && currentReviewQueueItemId && product) {
            sessionStartTime.current = Date.now();
            reviewItemRef.current = currentReviewQueueItemId;
        }
        return () => {
            if (sessionStartTime.current && reviewItemRef.current && isOpen) {
                const sessionEndTime = Date.now();
                const totalDurationSeconds =
                    (sessionEndTime - sessionStartTime.current) / 1000;

                // Uses the stored review item ID from when timing started
                trackContentReviewEvent(
                    'Review Mode Duration',
                    eventTypeRef.current ?? '',
                    {
                        sessionStartTime: sessionStartTime.current,
                        sessionEndTime: sessionEndTime,
                        reviewQueueId: reviewItemRef.current,
                        totalDurationSeconds: totalDurationSeconds,
                    },
                    product,
                    totalDurationSeconds
                );
                eventTypeRef.current = null;
            }
        };
        // Run only when review item changes or modal closes
    }, [isOpen, currentReviewQueueItemId, product]);

    useEffect(() => {
        if (
            !isPreloadingState &&
            !isLoadingProduct &&
            !isLoadingMetadataLanguageMap &&
            !isLoadingGenreMap &&
            !isLoadingValidations
        ) {
            setIsMergedProductFullyLoaded(true);
        } else {
            setIsPreLoadingState(false);
        }
    }, [
        isLoadingProduct,
        isLoadingMetadataLanguageMap,
        isLoadingGenreMap,
        isLoadingValidations,
        isPreloadingState,
    ]);

    const scrollTo = document.getElementById('scrollTo');
    useEffect(() => {
        if (displayProductModalPageHeader) return;
        if (scrollTo?.scrollIntoView)
            setTimeout(() => {
                scrollTo.scrollIntoView({ behavior: 'smooth', block: 'start' });
            }, 300);
    }, [scrollTo, currentReviewQueueItemId, displayProductModalPageHeader]);

    useEffect(() => {
        setIsOpen(Boolean(currentReviewQueueItemId));
    }, [currentReviewQueueItemId]);

    const replicateTrackAudioAttributesToEditsToDB = async (
        tuids: Set<number>
    ) => {
        const mergedTracks = mergedProduct?.getTrackList();
        const filteredMergedTracks = mergedTracks?.filter(t =>
            tuids.has(t.tuid)
        );
        const filteredProductTracks = (product?.tracks || []).filter(t =>
            tuids.has(parseInt(t.tuid, 10))
        );
        await updateTracksAudioAttributesEdits({
            variables: {
                input: getAudioAttributes(
                    filteredMergedTracks,
                    filteredProductTracks
                ),
            },
        });
    };

    const replicateTrackRightsAttributesToEditsToDB = async (
        tuids: Set<number>
    ) => {
        const mergedTracks = mergedProduct?.getTrackList();
        const filteredMergedTracks = mergedTracks?.filter(t =>
            tuids.has(t.tuid)
        );
        const filteredProductTracks = (product?.tracks || []).filter(t =>
            tuids.has(parseInt(t.tuid, 10))
        );
        await updateTracksRightsAttributesEdits({
            variables: {
                input: getRightsAttributes(
                    filteredMergedTracks,
                    filteredProductTracks
                ),
            },
        });
    };

    const replicateAttributesToEdits = async (product: Product) => {
        const tuidsWithoutAudioEdits = new Set(
            product.tracks
                .filter(track => track.audioAttributesEdits.length === 0)
                .map(track => parseInt(track.tuid, 10))
        );
        const tuidsWithoutRightsEdits = new Set(
            product.tracks
                .filter(track => track.rightsAttributesEdits.length === 0)
                .map(track => parseInt(track.tuid, 10))
        );

        if (tuidsWithoutAudioEdits.size > 0) {
            await replicateTrackAudioAttributesToEditsToDB(
                tuidsWithoutAudioEdits
            );
        }

        if (tuidsWithoutRightsEdits.size > 0) {
            await replicateTrackRightsAttributesToEditsToDB(
                tuidsWithoutRightsEdits
            );
        }

        if (
            tuidsWithoutAudioEdits.size > 0 ||
            tuidsWithoutRightsEdits.size > 0
        ) {
            await invalidateGqlCache();
        }
    };

    useEffect(() => {
        // naive check if there were any edits already made
        // or this is just the first time the user opens Product for review
        // since there are separate tables for Audio and Rights,
        // to be 100% — check each entity separately
        if (!isLoadingProduct && product?.tracks.length) {
            replicateAttributesToEdits(product).catch(console.error);
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [isLoadingProduct, product?.tracks.length]);

    const closeModal = async (trackEvent = false) => {
        eventTypeRef.current = 'End Session';
        if (trackEvent)
            trackContentReviewEvent('Click', 'End Session', {}, product);
        setIsOpen(false);

        const modalCloseAnimationDurationMs = 333;
        await new Promise(resolve => {
            setTimeout(resolve, modalCloseAnimationDurationMs);
        });

        navigateToReviewQueueV2();

        await resetAudioAttributesEdits();
        await resetRightsAttributesEdits();
        await invalidateGqlCache();
    };

    const skipReviewQueueItem = () => {
        setIsFailedReviewCompletion(false);

        if (!nextReviewQueueItemId) {
            void closeModal();
            return;
        }

        navigateToNextReviewQueueItemV2();

        resetAudioAttributesEdits().catch(console.error);
        resetRightsAttributesEdits().catch(console.error);
    };

    const onClickSkip = () => {
        eventTypeRef.current = 'Skip Product';
        trackProductClickEvent('Skip', {}, product);
        skipReviewQueueItem();
    };

    const handleReviewCompleted = async (
        status: string,
        eventType?: string
    ) => {
        setIsSubmittingReview(false);
        if (eventType === 'Approval' || eventType == 'Rejection') {
            eventTypeRef.current = 'Complete Review';
        }

        if (status === 'error') {
            setIsFailedReviewCompletion(true);
            return;
        }

        await invalidateGqlCache();

        if (!nextReviewQueueItemId) {
            navigateToReviewQueueV2();
            return;
        }
        navigateToNextReviewQueueItemV2();
    };

    const triggerMoveReviewQueueItemToQueueMutation =
        useMoveReviewQueueItemToQueueMutation(
            currentReviewQueueItemId,
            handleReviewCompleted
        );

    const handleQueueMove = async (
        destinationQueueName: string,
        movedToTargetId?: number,
        moveNote?: string
    ) => {
        if (!destinationQueueName)
            throw new Error(
                `Expected queue name to move to, got: ${destinationQueueName}`
            );
        setIsSubmittingReview(true);
        eventTypeRef.current = `Change Queue To ${destinationQueueName}`;

        // Ensure tracking event is sent before navigation
        await trackProductClickEvent(
            'MoveToQueue',
            { currentQueueName: queueName, destinationQueueName },
            product
        );

        triggerMoveReviewQueueItemToQueueMutation(
            destinationQueueName,
            movedToTargetId,
            moveNote
        );
        await invalidateGqlCache();
    };

    const isLoadingItemsOrProduct =
        isLoadingProduct || !reviewQueueItems || !isReviewQueueItemLocked;
    const isProductAndQueueItemLoaded = Boolean(
        product && currentReviewQueueItemId
    );

    const isReviewSubmissionDisabled =
        isLoadingItemsOrProduct ||
        !isProductAndQueueItemLoaded ||
        isLoadingValidations ||
        validationsError ||
        isSubmittingReview;

    const hasValidationError =
        usePreventApprovalWithBlockersFF() &&
        validations?.some(v => v.severity === 'error');

    const renderModalBody = () => {
        if (hasError)
            return (
                <Alert
                    variant="error"
                    text={hasError?.name || $t('error.genericError')}
                />
            );

        if (isLoadingItemsOrProduct) return <ProductModalBodyLoading />;

        if (!product || !mergedProduct)
            // While the modal is closing product is null, so we just show nothing
            return null;

        if (isFailedReviewCompletion)
            return (
                <ErrorMessage
                    message={$t(`${INTL_PREFIX}.errorSavingReview`, {
                        email: 'ProductSupport@sonymusic-pde.com',
                    })}
                />
            );

        return (
            <ProductModalBody
                product={product}
                mergedProduct={mergedProduct}
                isMergedProductFullyLoaded={isMergedProductFullyLoaded}
                validations={validations}
                reviewQueueId={currentReviewQueueItemId}
                submissionType={submissionType}
            />
        );
    };

    return (
        <div
            className={`${CLASS_NAME}-Wrapper`}
            data-testid={`${CLASS_NAME}-Wrapper`}
        >
            <FullscreenModalReview
                product={product}
                mergedProduct={mergedProduct}
                isOpen={isOpen}
                isProductAndQueueItemLoaded={isProductAndQueueItemLoaded}
                isLoadingItemsOrProduct={isLoadingItemsOrProduct}
                productQueryError={hasError}
                submissionType={submissionType}
                onClose={closeModal}
                onClickSkip={onClickSkip}
                renderModalBody={renderModalBody}
                queueName={queueName}
                isSubmittingReview={isSubmittingReview}
                productReviewQueueId={currentReviewQueueItemId}
                isApprovalNoteRequired={hasPotentialAudioInfringement}
                isApprovalBlocked={Boolean(
                    isReviewSubmissionDisabled || hasValidationError
                )}
                isRejectionBlocked={Boolean(isReviewSubmissionDisabled)}
                isReviewHistoryDisabled={Boolean(
                    isLoadingItemsOrProduct || !isProductAndQueueItemLoaded
                )}
                onReviewCompleted={handleReviewCompleted}
                onQueueMove={handleQueueMove}
                productTargetGroup={productTargetGroup}
                submittedByUserLanguage={submittedByUserLanguage}
                currentReviewQueueItemId={currentReviewQueueItemId}
                lockedByUserName={lockedByUserName}
                wasExistingLockDetected={wasExistingLockDetected}
                validations={validations}
            />
        </div>
    );
};

export default ProductModal;
