import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { getRequestUrl, getApiOrigin } from "@/features/api/utils";
import { getBlobDownloadRetrieveUrl } from "@/features/api/gen/blob/blob";
import { UnquoteMessage } from '@/features/utils/unquote-message';
import { useTranslation } from "react-i18next";
import { tokens } from '@/styles/cunningham-tokens'
import { useTheme } from "@/features/providers/theme";
import { useConfig } from "@/features/providers/config";
import { useMailboxContext } from "@/features/providers/mailbox";
import { Icon } from "@gouvfr-lasuite/ui-kit";
import { Banner } from "@/features/ui/components/banner";
import { getMailboxesImageProxyListUrl } from "@/features/api/gen/mailboxes/mailboxes";
import { EXTERNAL_IMAGES_CONSENT_KEY } from "@/features/config/constants";
import { renderBodyParts } from "./renderers";
import { linkifyHtml } from "./renderers/linkify";
import { isHostTrusted } from "./is-host-trusted";
import { useLinkConfirmation } from "./use-link-confirmation";
import { ThreadMessageBodyProps } from "./types";

const CSP = [
    // Allow images from our domain, data URIs, and API endpoints
    `img-src 'self' data: ${getApiOrigin()}`,
    // Disable everything else by default
    "default-src 'none'",
    // No scripts at all
    "script-src 'none'",
    // No styles from external sources
    "style-src 'unsafe-inline'",
    // No fonts
    "font-src 'none'",
    // No connections
    "connect-src 'none'",
    // No media
    "media-src 'none'",
    // No objects/embeds
    "object-src 'none'",
    // No prefetch
    "prefetch-src 'none'",
    // No frames
    "child-src 'none'",
    "frame-src 'none'",
    // No workers
    "worker-src 'none'",
    // No frame ancestors
    "frame-ancestors 'none'",
].join('; ');

/**
 * Detect link masking: the link text pretends to be a URL but points to a
 * different host than the real target, a common phishing technique
 * (e.g. <a href="http://malicious.com">http://safe.fr</a>).
 */
const isLinkMasked = (href: string, text: string): boolean => {
    const trimmedText = text.trim();
    if (!/^(https?:\/\/|www\.)/i.test(trimmedText)) {
        return false;
    }
    try {
        const textUrl = new URL(/^https?:\/\//i.test(trimmedText) ? trimmedText : `https://${trimmedText}`);
        const hrefUrl = new URL(href);
        return textUrl.host.replace(/^www\./, "") !== hrefUrl.host.replace(/^www\./, "");
    } catch {
        // The text pretends to be a URL but one side cannot be parsed, stay cautious.
        return true;
    }
};

const ThreadMessageBody = ({ bodyParts, attachments = [], isHidden = false, messageId, onLoad }: ThreadMessageBodyProps) => {
    const { t } = useTranslation();
    const iframeRef = useRef<HTMLIFrameElement>(null);
    const { cunninghamTheme, variant } = useTheme();
    const { selectedMailbox, selectedThread } = useMailboxContext();
    const confirmLinkOpening = useLinkConfirmation();
    // Links of spam messages are inert. A ref keeps the iframe click handler
    // in sync without having to re-attach listeners when the flag changes.
    const areLinksDisabled = Boolean(selectedThread?.is_spam);
    const areLinksDisabledRef = useRef(areLinksDisabled);
    areLinksDisabledRef.current = areLinksDisabled;
    const { IMAGE_PROXY_ENABLED: canDisplayExternalImages, MESSAGE_TRUSTED_LINK_DOMAINS: trustedLinkDomains } = useConfig();
    const [displayExternalImages, setDisplayExternalImages] = useState(() => {
        const consentMessageIds = sessionStorage.getItem(EXTERNAL_IMAGES_CONSENT_KEY);
        if (consentMessageIds) {
            return consentMessageIds.split('|').includes(messageId);
        }
        return false;
    });
    const hasExternalImagesRef = useRef(false);
    const showExternalImages = () => {
        const oldConsentMessageIds = new Set(sessionStorage.getItem(EXTERNAL_IMAGES_CONSENT_KEY)?.split('|') ?? []);
        oldConsentMessageIds.add(messageId);
        sessionStorage.setItem(EXTERNAL_IMAGES_CONSENT_KEY, Array.from(oldConsentMessageIds).join('|'));
        setDisplayExternalImages(true);
    };

    // Build CID to blob URL mapping for inline image resolution
    const cidToBlobUrlMap = useMemo(() => {
        const map = new Map<string, string>();
        attachments.forEach(attachment => {
            if (attachment.cid) {
                const blobUrl = getRequestUrl(getBlobDownloadRetrieveUrl(attachment.blobId));
                map.set(attachment.cid, blobUrl);
            }
        });
        return map;
    }, [attachments]);

    // Options for external image handling, passed to renderers
    const externalImageOptions = useMemo(() => ({
        canDisplayExternalImages,
        displayExternalImages,
        selectedMailboxId: selectedMailbox?.id,
        onExternalImageDetected: () => { hasExternalImagesRef.current = true; },
        getProxiedUrl: (url: string) => selectedMailbox
            ? getRequestUrl(getMailboxesImageProxyListUrl(selectedMailbox.id, { url }))
            : url,
    }), [canDisplayExternalImages, displayExternalImages, selectedMailbox]);

    const sanitizedHtmlBody = useMemo(() => {
        const renderedContent = renderBodyParts(bodyParts, cidToBlobUrlMap, externalImageOptions);

        if (!renderedContent) {
            return "";
        }

        // Check content types to determine processing mode
        const hasHtmlPart = bodyParts.some(part => part.type === "text/html");
        const hasOnlyPlainText = bodyParts.every(part => part.type === "text/plain");

        let htmlContent = renderedContent;

        if (hasHtmlPart) {
            // Process HTML content with UnquoteMessage for quote detection
            const unquoteMessage = new UnquoteMessage(renderedContent, '', {
                mode: 'wrap',
                ignoreFirstForward: true,
                depth: 0,
            });
            htmlContent = unquoteMessage.getHtml().content;
        } else if (hasOnlyPlainText) {
            // Pure plain text - process original content through UnquoteMessage for quote detection
            const rawTextContent = bodyParts.map(part => part.content).join("\n");
            const unquoteMessage = new UnquoteMessage('', rawTextContent, {
                mode: 'wrap',
                ignoreFirstForward: true,
                depth: 0,
            });
            const unquotedText = unquoteMessage.getText().content;

            htmlContent = renderToStaticMarkup(<p className="text-plain-content">{unquotedText}</p>);
        }
        // Otherwise, mixed content (plain text + images, etc.) - use rendered content directly

        // Turn bare URLs (text not already wrapped in a link) into anchors
        return linkifyHtml(htmlContent);
    }, [bodyParts, cidToBlobUrlMap, externalImageOptions]);

    const wrappedHtml = useMemo(() => {
        return `
            <html>
            <head>
                <meta http-equiv="Content-Security-Policy" content="${CSP}">
                <base target="_blank">
                <style>
                html, body {
                    margin: 0;
                    padding: 0;
                }
                body {
                    font-family: ${tokens.themes[cunninghamTheme].globals.font.families.base};
                    padding: ${tokens.themes[cunninghamTheme].globals.spacings.base};
                    padding-top: 0;
                    font-size: ${tokens.themes[cunninghamTheme].globals.font.sizes.sm};
                    color: ${tokens.themes[cunninghamTheme].contextuals.content.semantic.neutral.primary};
                }
                .text-plain-content {
                    white-space: pre-wrap;
                    word-wrap: break-word;
                }
                .body-part + .body-part {
                    margin-top: ${tokens.themes[cunninghamTheme].globals.spacings.base};
                }
                body > *:first-child {
                    padding-top: 0 !important;
                }
                body > *:last-child {
                    margin-bottom: ${tokens.themes[cunninghamTheme].globals.spacings.base};
                }
                table, div {
                    max-width: 100%;
                }
                p, ul, ol, li, blockquote, pre, code, h1, h2, h3, h4, h5, h6 {
                    margin: 0;
                }
                p, ul {
                    line-height: 1.3;
                }
                a {
                    color: ${tokens.themes[cunninghamTheme].contextuals.background.semantic.info.primary};
                    text-decoration: none;
                }
                a:hover { text-decoration: underline; }
                ${areLinksDisabled ? `
                /* Spam: links are inert and rendered as plain text */
                a, a:hover {
                    pointer-events: none;
                    color: inherit !important;
                    text-decoration: none !important;
                    cursor: default;
                }` : ''}

                blockquote {
                    padding: 0 ${tokens.themes[cunninghamTheme].globals.spacings.base} !important;
                    margin: ${tokens.themes[cunninghamTheme].globals.spacings.base} 0 !important;
                    border-left-style: solid;
                    border-color: ${tokens.themes[cunninghamTheme].globals.colors["gray-500"]} !important;
                }

                blockquote blockquote {
                    border-color: ${tokens.themes[cunninghamTheme].globals.colors["gray-400"]} !important;
                }

                blockquote blockquote blockquote {
                    border-color: ${tokens.themes[cunninghamTheme].globals.colors["gray-300"]} !important;
                }

                blockquote blockquote blockquote blockquote {
                    border-color: ${tokens.themes[cunninghamTheme].globals.colors["gray-200"]} !important;
                }

                blockquote blockquote blockquote blockquote blockquote {
                    border-color: ${tokens.themes[cunninghamTheme].globals.colors["gray-100"]} !important;
                }

                body[data-theme-variant="dark"] {
                    blockquote {
                        border-color: ${tokens.themes[cunninghamTheme].globals.colors["gray-450"]} !important;
                    }

                    blockquote blockquote {
                        border-color: ${tokens.themes[cunninghamTheme].globals.colors["gray-550"]} !important;
                    }

                    blockquote blockquote blockquote {
                        border-color: ${tokens.themes[cunninghamTheme].globals.colors["gray-650"]} !important;
                    }

                    blockquote blockquote blockquote blockquote {
                        border-color: ${tokens.themes[cunninghamTheme].globals.colors["gray-750"]} !important;
                    }


                    blockquote blockquote blockquote blockquote blockquote {
                        border-color: ${tokens.themes[cunninghamTheme].globals.colors["gray-850"]} !important;
                    }
                }

                img {
                    max-width: 100%;
                }

                pre {
                    background-color: ${tokens.themes[cunninghamTheme].contextuals.background.semantic.overlay.primary};
                    border-radius: 4px;
                    padding: ${tokens.themes[cunninghamTheme].globals.spacings.base};
                    overflow: auto;
                }
                code {
                    font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
                    font-size: 85%;
                }

                details.email-quoted-content > summary.email-quoted-summary {
                    background-color: ${tokens.themes[cunninghamTheme].contextuals.background.semantic.brand.tertiary};
                    cursor: pointer;
                    user-select: none;
                    padding: ${tokens.themes[cunninghamTheme].globals.spacings['3xs']} ${tokens.themes[cunninghamTheme].globals.spacings.xs};
                    border-radius: 4px;
                    display: grid;
                    place-items: center;
                    color: ${tokens.themes[cunninghamTheme].contextuals.content.semantic.brand.primary};
                    vertical-align: middle;
                    list-style: none;
                    outline: none;
                    width: fit-content;
                    position: relative;
                    margin-top: ${tokens.themes[cunninghamTheme].globals.spacings.base};
                    font-family: ${tokens.themes[cunninghamTheme].globals.font.families.base};
                    transition: background-color ${tokens.themes.default.globals.transitions.duration} ${tokens.themes.default.globals.transitions['ease-in-out']};
                }

                details.email-quoted-content:not([open]) > summary.email-quoted-summary::before {
                    content: attr(data-content);
                    position: absolute;
                    left: 110%;
                    top: 50%;
                    width: 100%;
                    height: 100%;
                    background-color: ${tokens.themes[cunninghamTheme].contextuals.background.semantic.neutral.tertiary};
                    border: 1px solid ${tokens.themes[cunninghamTheme].contextuals.border.semantic.neutral.tertiary};
                    box-shadow: 0 1px 5.4px 0 rgba(0, 0, 0, 0.15);
                    width: max-content;
                    transform: translateY(-50%);
                    color: ${tokens.themes[cunninghamTheme].contextuals.content.semantic.neutral.tertiary};
                    padding: ${tokens.themes[cunninghamTheme].globals.spacings['3xs']} ${tokens.themes[cunninghamTheme].globals.spacings.xs};
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    border-radius: 0.5rem;
                    font-size: ${tokens.themes[cunninghamTheme].globals.font.sizes.s};
                    visibility: hidden;
                    opacity: 0;
                    transition-property: visibility, opacity;
                    transition-duration: 150ms;
                    transition-timing-function: ${tokens.themes.default.globals.transitions['ease-in']};
                }

                details.email-quoted-content > summary.email-quoted-summary > span {
                    font-size: ${tokens.themes[cunninghamTheme].globals.font.sizes.lg};
                    font-weight: 700;
                    transform: translateY(-5px);
                    line-height: 1ex;
                }

                details.email-quoted-content > summary.email-quoted-summary:hover {
                    background-color: ${tokens.themes[cunninghamTheme].contextuals.background.semantic.brand["tertiary-hover"]};
                }
                details.email-quoted-content > summary.email-quoted-summary:hover::before {
                    visibility: visible;
                    opacity: 1;
                    transition-timing-function: ${tokens.themes.default.globals.transitions['ease-in-out']};
                    transition-delay: 1000ms;
                }
                details.email-quoted-content > summary.email-quoted-summary::-webkit-details-marker {
                    display: none;
                }
                </style>
            </head>
            <body data-theme-variant="${variant}">
                ${sanitizedHtmlBody}
            </body>
            </html>
      `;
    }, [sanitizedHtmlBody, cunninghamTheme, variant, areLinksDisabled]);

    const resizeIframe = useCallback(() => {
        if (iframeRef.current?.contentWindow?.document.documentElement) {
            const height = iframeRef.current.contentWindow.document.documentElement.getBoundingClientRect().height;
            iframeRef.current.style.height = `${height}px`;
        }
    }, [iframeRef]);

    // Delegated handler for link activations inside the iframe (mouse click,
    // keyboard activation and middle click through the auxclick event).
    const handleLinkActivation = useCallback((event: MouseEvent) => {
        const target = event.target as Element | null;
        const link = target?.closest?.('a[href]') as HTMLAnchorElement | null;
        if (!link) {
            return;
        }

        // In-page anchors are safe and stay within the iframe
        if (link.getAttribute('href')?.startsWith('#')) {
            return;
        }

        if (areLinksDisabledRef.current) {
            event.preventDefault();
            return;
        }

        // Let other protocols (mailto:, tel:, ...) follow their default behavior
        if (!/^https?:/i.test(link.href)) {
            return;
        }

        event.preventDefault();
        // Middle clicks are only blocked, the confirmation flow targets primary clicks
        if (event.type === 'auxclick') {
            return;
        }

        const url = link.href;
        const isMasked = isLinkMasked(url, link.textContent ?? '');
        // Trusted domains skip the confirmation, unless the link is masked: a
        // masked link is exactly the phishing case the confirmation exists for.
        if (!isMasked && isHostTrusted(new URL(url).hostname, trustedLinkDomains)) {
            window.open(url, '_blank', 'noopener,noreferrer');
            return;
        }
        void confirmLinkOpening(url, isMasked).then((confirmed) => {
            if (confirmed) {
                window.open(url, '_blank', 'noopener,noreferrer');
            }
        });
    }, [confirmLinkOpening, trustedLinkDomains]);

    const handleIframeLoad = useCallback(() => {
        resizeIframe();
        if (iframeRef.current?.contentWindow?.document) {
            const doc = iframeRef.current.contentWindow.document;

            // When details element is toggled, resize the iframe to fit the content
            doc.querySelectorAll('details.email-quoted-content').forEach(node => {
                node.addEventListener('toggle', resizeIframe);
            });

            // Intercept link activations to reveal the real target before opening it
            doc.addEventListener('click', handleLinkActivation);
            doc.addEventListener('auxclick', handleLinkActivation);
        }
        onLoad?.();
    }, [onLoad, resizeIframe, handleLinkActivation]);

    useEffect(() => {
        const handleMessage = (event: MessageEvent) => {
            if (event.data === 'iframe-loaded') {
                // Send a message to the iframe to add event listeners
                iframeRef.current?.contentWindow?.postMessage('add-toggle-listeners', '*');
            } else if (event.data === 'resize') {
                resizeIframe();
            }
        };

        window.addEventListener('message', handleMessage);
        window.addEventListener('resize', resizeIframe);

        return () => {
            window.removeEventListener('message', handleMessage);
            window.removeEventListener('resize', resizeIframe);
        };
    }, [resizeIframe]);

    useEffect(() => {
        if (!isHidden) {
            resizeIframe();
        }
    }, [isHidden, resizeIframe, showExternalImages]);

    return (
        <>
            {!isHidden && canDisplayExternalImages && hasExternalImagesRef.current && !displayExternalImages &&
                <Banner
                    type="neutral"
                    icon={<Icon name="security" />}
                    compact
                    actions={[
                        {
                            label: t("Display those images"),
                            onClick: showExternalImages,
                        }
                    ]}
                >
                    {t("For your security and privacy, external images are not displayed.")}
                </Banner>
            }
            <iframe
                title={t("Message content")}
                style={{
                    maxHeight: isHidden ? '0px' : undefined,
                    visibility: isHidden ? 'hidden' : 'visible',
                    margin: isHidden ? '0' : undefined,
                }}
                ref={iframeRef}
                className="thread-message__body"
                srcDoc={wrappedHtml}
                sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
                onLoad={handleIframeLoad}
            />
        </>
    )
}

export default ThreadMessageBody;
