import { Form, Skeleton, Spin } from "antd";
import { observer } from "mobx-react";
import { useCallback, useEffect } from "react";
import { useNavigate } from "react-router-dom";

import type {
    EServiceTemplateDataProcessingInCountriesSpecialTreatment,
    EServiceTemplateGoogleConsentModeTypes,
} from "@devowl-wp/api-real-cookie-banner";
import type { VisualServiceTechnicalDefinition } from "@devowl-wp/react-cookie-banner";
import type { FormServiceValueProps, FormSettingsValueProps } from "@devowl-wp/react-cookie-banner-admin";
import {
    FormService,
    FormServiceLayout,
    useFormServiceHandler,
    useFormServiceProvider,
} from "@devowl-wp/react-cookie-banner-admin";
import { base64EncodeUnicodeSafe, scrollTo, slugify } from "@devowl-wp/react-utils";

import { useLocationQuery } from "../../../hooks/useLocationQuery.js";
import { useRouteCookie } from "../../../hooks/useRouteCookie.js";
import { CookieModel } from "../../../models/cookieModel.js";
import { useStores } from "../../../store/stores.js";
import { request } from "../../../utils/request.js";
import { locationRestForwardCookieGet } from "../../../wp-api/forwardCookie.get.js";
import { CodeMirror } from "../../codemirror.js";
import { ConfigContent } from "../content.js";

import type {
    ParamsRouteForwardCookieGet,
    RequestRouteForwardCookieGet,
    ResponseRouteForwardCookieGet,
} from "../../../wp-api/forwardCookie.get.js";
import type { FC } from "react";

const CookieEditForm: FC<{
    template?: {
        identifier: string;
        version: number;
    };
    overwriteAttributes?: Parameters<typeof useFormServiceHandler>["0"]["attributes"];
    navigateAfterCreation?: boolean | string;
    scrollToTop?: boolean;
    onCreated?: (model: CookieModel) => void;
}> = observer(({ template, overwriteAttributes, navigateAfterCreation = true, scrollToTop = true, onCreated }) => {
    const {
        routeGroup: { group, link },
        cookie,
        id,
        queried,
        fetched,
    } = useRouteCookie();
    const navigate = useNavigate();
    const { initiallyScrollToField } = useLocationQuery();
    const {
        cookieStore,
        optionStore: {
            isTcf,
            isGcm,
            isGcmShowRecommandationsWithoutConsent,
            isBannerLessConsent,
            isDataProcessingInUnsafeCountries,
            isConsentForwarding,
            setCookiesViaManager,
            createdTagManagers,
            territorialLegalBasis,
            others: {
                useEncodedStringForScriptInputs,
                activePlugins,
                iso3166OneAlpha2,
                frontend: { predefinedDataProcessingInSafeCountriesLists },
            },
        },
    } = useStores();
    const { essentialGroup } = cookieStore;
    const templateModel = cookie?.templateModel || cookieStore.templatesServices.get(template?.identifier);
    const groups = cookieStore.groups.sortedGroups.map(({ data: { id, name } }) => ({ id, name }));
    const allowContentBlockerCreation = !!navigateAfterCreation;

    const { prompt, form, isBusy, defaultValues, onFinish, onFinishFailed, onValuesChange, contextValue } =
        useFormServiceHandler({
            isEdit: fetched,
            territorialLegalBasis,
            entityTemplateVersion: cookie?.data?.meta?.presetVersion,
            attributes: overwriteAttributes,
            selectedGroup: group.key,
            trackFieldsDifferFromDefaultValues: ["group"],
            setCookiesViaManager,
            isGcm,
            groups,
            template: templateModel?.use,
            allowContentBlockerCreation,
            shouldUncheckContentBlockerCheckbox: templateModel?.use?.shouldUncheckContentBlockerCheckbox,
            handleSave: async (values) => {
                try {
                    // Pass as base64-encoded string to avoid Cloudflare XSS issues
                    const codeToBase64 = (str: string) =>
                        useEncodedStringForScriptInputs ? `encodedScript:${base64EncodeUnicodeSafe(str)}` : str;
                    const {
                        name,
                        status,
                        providerContact,
                        purpose,
                        isEmbeddingOnlyExternalResources,
                        dataProcessingInCountries,
                        dataProcessingInCountriesSpecialTreatments,
                        technicalDefinitions,
                        group,
                        codeDynamics,
                        createContentBlocker,
                        createContentBlockerId,
                        uniqueName,
                        googleConsentModeConsentTypes,
                        codeOptIn,
                        codeOptOut,
                        codeOnPageLoad,
                        succeessorDeletionCheck,
                        ...meta
                    } = values;
                    const newMeta = {
                        ...meta,
                        providerContactPhone: providerContact.phone,
                        providerContactEmail: providerContact.email,
                        providerContactLink: providerContact.link,
                        codeOptIn: codeToBase64(codeOptIn),
                        codeOptOut: codeToBase64(codeOptOut),
                        codeOnPageLoad: codeToBase64(codeOnPageLoad),
                        isEmbeddingOnlyExternalResources,
                        dataProcessingInCountries: JSON.stringify(dataProcessingInCountries),
                        dataProcessingInCountriesSpecialTreatments: JSON.stringify(
                            dataProcessingInCountriesSpecialTreatments,
                        ),
                        codeDynamics: JSON.stringify(codeDynamics),
                        // Still hold the data of `technicalDefinitions` in database so it is not cleared when activating this option
                        technicalDefinitions: JSON.stringify(
                            isEmbeddingOnlyExternalResources
                                ? initialValues.technicalDefinitions
                                : technicalDefinitions,
                        ),
                        uniqueName: uniqueName || slugify(name),
                        googleConsentModeConsentTypes: JSON.stringify(googleConsentModeConsentTypes),
                        presetId: templateModel?.data.identifier,
                        presetVersion: templateModel?.data.version,
                    };

                    delete newMeta.templateCheck;

                    if (queried) {
                        cookie.setName(name);
                        cookie.setStatus(status);
                        cookie.setPurpose(purpose);
                        cookie.setMeta(newMeta);
                        cookie.setGroup(group);
                        await cookie.patch();
                    } else {
                        const useGroup = cookieStore.groups.entries.get(group);
                        const draft = new CookieModel(useGroup.cookies, {
                            title: {
                                raw: name,
                            },
                            content: {
                                raw: purpose,
                                protected: false,
                            },
                            status,
                            meta: newMeta,
                        });
                        await draft.persist();

                        // Delete successor-relevant services
                        if (succeessorDeletionCheck) {
                            const successorOfIds = templateModel.data.consumerData.successorOf.map(({ id }) => id);
                            const allCookieModels = cookieStore.groups.sortedGroups
                                .map(({ cookies }) => [...cookies.entries.values()])
                                .flat();

                            const deleteModels = successorOfIds.map(
                                (id) =>
                                    allCookieModels.find(({ key }) => key === id) ||
                                    new CookieModel(cookieStore.essentialGroup.cookies, { id }),
                            );

                            await Promise.allSettled(deleteModels.map((m) => m.delete()));
                        }

                        onCreated?.(draft);
                    }

                    const useCreateContentBlockerId = createContentBlockerId || templateModel?.data.identifier;

                    // Navigate back after creation
                    return () =>
                        navigateAfterCreation &&
                        (createContentBlocker
                            ? // Navigate to content blocker form and forward `navigateAfterCreation` if given
                              navigate(
                                  `/blocker/new?force=${useCreateContentBlockerId}&comingFromServiceCreation=1${
                                      typeof navigateAfterCreation === "string"
                                          ? `&navigateAfterCreation=${encodeURIComponent(navigateAfterCreation)}`
                                          : ""
                                  }`,
                              )
                            : // Navigate back to overview or custom link
                              typeof navigateAfterCreation === "string"
                              ? (window.location.href = navigateAfterCreation)
                              : navigate(`${link.slice(1)}/${group}`));
                } catch (e) {
                    throw (e as any).responseJSON.message;
                }
            },
        });

    const initialValues: FormServiceValueProps = fetched
        ? {
              name: cookie.data.title.raw,
              status: cookie.data.status as FormServiceValueProps["status"],
              group: group.key || undefined, // Can be `0`, so fallback to no selection (e.g. Cookie form modal in Content Blocker)
              purpose: cookie.data.content.raw,
              provider: cookie.data.meta.provider,
              isProviderCurrentWebsite: cookie.data.meta.isProviderCurrentWebsite,
              providerContact: {
                  phone: cookie.data.meta.providerContactPhone,
                  email: cookie.data.meta.providerContactEmail,
                  link: cookie.data.meta.providerContactLink,
              },
              providerPrivacyPolicyUrl: cookie.data.meta.providerPrivacyPolicyUrl,
              providerLegalNoticeUrl: cookie.data.meta.providerLegalNoticeUrl,
              uniqueName: cookie.data.meta.uniqueName || cookie.data.slug,
              isEmbeddingOnlyExternalResources: cookie.data.meta.isEmbeddingOnlyExternalResources,
              dataProcessingInCountries: JSON.parse(JSON.stringify(cookie.dataProcessingInCountries)) as string[],
              dataProcessingInCountriesSpecialTreatments: JSON.parse(
                  JSON.stringify(cookie.dataProcessingInCountriesSpecialTreatments),
              ) as EServiceTemplateDataProcessingInCountriesSpecialTreatment[],
              legalBasis: cookie.data.meta.legalBasis,
              technicalDefinitions: JSON.parse(
                  JSON.stringify(cookie.technicalDefinitions),
              ) as VisualServiceTechnicalDefinition[],
              codeDynamics: JSON.parse(JSON.stringify(cookie.codeDynamics)) as Record<string, string>,
              tagManagerOptInEventName: cookie.data.meta.tagManagerOptInEventName,
              tagManagerOptOutEventName: cookie.data.meta.tagManagerOptOutEventName,
              googleConsentModeConsentTypes: JSON.parse(
                  JSON.stringify(cookie.googleConsentModeConsentTypes),
              ) as EServiceTemplateGoogleConsentModeTypes[],
              executePriority: cookie.data.meta.executePriority,
              codeOptIn: cookie.data.meta.codeOptIn,
              executeCodeOptInWhenNoTagManagerConsentIsGiven:
                  cookie.data.meta.executeCodeOptInWhenNoTagManagerConsentIsGiven,
              codeOptOut: cookie.data.meta.codeOptOut,
              executeCodeOptOutWhenNoTagManagerConsentIsGiven:
                  cookie.data.meta.executeCodeOptOutWhenNoTagManagerConsentIsGiven,
              codeOnPageLoad: cookie.data.meta.codeOnPageLoad,
              deleteTechnicalDefinitionsAfterOptOut: cookie.data.meta.deleteTechnicalDefinitionsAfterOptOut,
              createContentBlocker: false,
              createContentBlockerId: undefined,
              templateCheck: undefined,
              succeessorDeletionCheck: undefined,
          }
        : defaultValues;

    // Initially load the cookie if not yet done
    useEffect(() => {
        if (queried && !fetched) {
            // Fetch the cookie within the correct group collection so it gets removed
            // from the original cookie group when it got moved to another cookie group.
            const groupToFetch =
                [...cookieStore.groups.entries.values()].filter(({ cookies }) => cookies.entries.get(id))[0] || group;
            groupToFetch.cookies.getSingle({
                params: {
                    id,
                    context: "edit",
                },
            });
        }
    }, [queried, fetched]);

    // Lazy load attributes of template model
    useEffect(() => {
        if (templateModel && !templateModel.use && !templateModel.busy) {
            templateModel.fetchUse();
        }
    }, [templateModel]);

    // Scroll to top when opening the form
    useEffect(() => {
        if (scrollToTop) {
            scrollTo(0);
        }
    }, []);

    const hasServiceByUniqueName = useCallback(
        async (slug: string) => {
            try {
                return (
                    (
                        (await request<
                            RequestRouteForwardCookieGet,
                            ParamsRouteForwardCookieGet,
                            ResponseRouteForwardCookieGet
                        >({
                            location: locationRestForwardCookieGet,
                            params: {
                                slug,
                            },
                        })) as ResponseRouteForwardCookieGet
                    ).filter((d) => d.ID !== cookie.key).length > 0
                );
            } catch (e) {
                return false;
            }
        },
        [cookie.key],
    );

    const notReady = (queried && !fetched) || (templateModel && !templateModel.use);
    const [FormServiceContextProvider, formServiceContextValue] = useFormServiceProvider(
        {
            ...contextValue,
            isTcf,
            isGcmShowRecommandationsWithoutConsent,
            isBannerLessConsent,
            isDataProcessingInUnsafeCountries,
            isConsentForwarding,
            hasServiceByUniqueName,
            createdTagManagers,
            iso3166OneAlpha2,
            predefinedDataProcessingInSafeCountriesLists,
            essentialGroupId: essentialGroup.key,
            skipIfActiveComponents: activePlugins,
            renderCodeMirror: () => <CodeMirror settings={(window as any).cm_settings} />,
            initiallyScrollToField: initiallyScrollToField as keyof FormSettingsValueProps,
        },
        {},
        { deps: [notReady], observe: ["initiallyScrollToField"] },
    );

    if (notReady) {
        return (
            <ConfigContent maxWidth="fixed">
                <Skeleton active paragraph={{ rows: 8 }} />
            </ConfigContent>
        );
    }

    return (
        <ConfigContent maxWidth="fixed">
            <FormServiceContextProvider value={formServiceContextValue}>
                <Spin spinning={isBusy || templateModel?.busy || false}>
                    {prompt}
                    <Form
                        name={`cookie-${group.key}-${id}`}
                        form={form}
                        {...FormServiceLayout}
                        initialValues={initialValues}
                        onFinish={onFinish}
                        onFinishFailed={onFinishFailed}
                        onValuesChange={onValuesChange}
                        scrollToFirstError={{ behavior: "smooth", block: "center" }}
                        labelWrap
                    >
                        <FormService />
                    </Form>
                </Spin>
            </FormServiceContextProvider>
        </ConfigContent>
    );
});

export { CookieEditForm };
