import {
  type FormEvent,
  type MouseEvent,
  useCallback,
  useEffect,
  useRef,
  useState,
} from 'react';
import './GenerateRoadmap.css';
import { useToast } from '../../hooks/use-toast';
import { generateAIRoadmapFromText } from '../../../editor/utils/roadmap-generator';
import { renderFlowJSON } from '../../../editor/renderer/renderer';
import { replaceChildren } from '../../lib/dom';
import { readAIRoadmapStream } from '../../helper/read-stream';
import {
  getOpenAIKey,
  isLoggedIn,
  removeAuthToken,
  setAIReferralCode,
  visitAIRoadmap,
} from '../../lib/jwt';
import { RoadmapSearch } from './RoadmapSearch.tsx';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { Ban, Cog, Download, PenSquare, Save, Wand } from 'lucide-react';
import { ShareRoadmapButton } from '../ShareRoadmapButton.tsx';
import { httpGet, httpPost } from '../../lib/http.ts';
import { pageProgressMessage } from '../../stores/page.ts';
import {
  deleteUrlParam,
  getUrlParams,
  setUrlParams,
} from '../../lib/browser.ts';
import { downloadGeneratedRoadmapImage } from '../../helper/download-image.ts';
import { showLoginPopup } from '../../lib/popup.ts';
import { cn } from '../../lib/classname.ts';
import { RoadmapTopicDetail } from './RoadmapTopicDetail.tsx';
import { AIRoadmapAlert } from './AIRoadmapAlert.tsx';
import { OpenAISettings } from './OpenAISettings.tsx';
import { IS_KEY_ONLY_ROADMAP_GENERATION } from '../../lib/ai.ts';
import { AITermSuggestionInput } from './AITermSuggestionInput.tsx';
import { useParams } from '../../hooks/use-params.ts';
import { IncreaseRoadmapLimit } from './IncreaseRoadmapLimit.tsx';
import { AuthenticationForm } from '../AuthenticationFlow/AuthenticationForm.tsx';

export type GetAIRoadmapLimitResponse = {
  used: number;
  limit: number;
  topicUsed: number;
  topicLimit: number;
};

const ROADMAP_ID_REGEX = new RegExp('@ROADMAPID:(\\w+)@');
const ROADMAP_SLUG_REGEX = new RegExp(/@ROADMAPSLUG:([\w-]+)@/);

export type RoadmapNodeDetails = {
  nodeId: string;
  nodeType: string;
  targetGroup?: SVGElement;
  nodeTitle?: string;
  parentTitle?: string;
};

export function getNodeDetails(
  svgElement: SVGElement,
): RoadmapNodeDetails | null {
  const targetGroup = (svgElement?.closest('g') as SVGElement) || {};

  const nodeId = targetGroup?.dataset?.nodeId;
  const nodeType = targetGroup?.dataset?.type;
  const nodeTitle = targetGroup?.dataset?.title;
  const parentTitle = targetGroup?.dataset?.parentTitle;
  if (!nodeId || !nodeType) return null;

  return { nodeId, nodeType, targetGroup, nodeTitle, parentTitle };
}

export const allowedClickableNodeTypes = [
  'topic',
  'subtopic',
  'button',
  'link-item',
];

type GetAIRoadmapResponse = {
  id: string;
  term: string;
  title: string;
  data: string;
};

type GenerateRoadmapProps = {
  roadmapId?: string;
  slug?: string;
};

export function GenerateRoadmap(props: GenerateRoadmapProps) {
  const { roadmapId: defaultRoadmapId, slug: defaultRoadmapSlug } = props;

  const roadmapContainerRef = useRef<HTMLDivElement>(null);

  const { rc: referralCode } = getUrlParams() as {
    rc?: string;
  };
  const toast = useToast();

  const [roadmapId, setRoadmapId] = useState<string | undefined>(
    defaultRoadmapId,
  );
  const [roadmapSlug, setRoadmapSlug] = useState<string | undefined>(
    defaultRoadmapSlug,
  );
  const [hasSubmitted, setHasSubmitted] = useState<boolean>(Boolean(roadmapId));
  const [isLoading, setIsLoading] = useState(false);
  const [isLoadingResults, setIsLoadingResults] = useState(false);
  const [roadmapTerm, setRoadmapTerm] = useState('');
  const [currentRoadmap, setCurrentRoadmap] =
    useState<GetAIRoadmapResponse | null>(null);
  const [generatedRoadmapContent, setGeneratedRoadmapContent] = useState('');
  const [selectedNode, setSelectedNode] = useState<RoadmapNodeDetails | null>(
    null,
  );

  const [roadmapLimit, setRoadmapLimit] = useState(0);
  const [roadmapLimitUsed, setRoadmapLimitUsed] = useState(0);
  const [roadmapTopicLimit, setRoadmapTopicLimit] = useState(0);
  const [roadmapTopicLimitUsed, setRoadmapTopicLimitUsed] = useState(0);
  const [isConfiguring, setIsConfiguring] = useState(false);

  const [openAPIKey, setOpenAPIKey] = useState<string | undefined>(
    getOpenAIKey(),
  );
  const isKeyOnly = IS_KEY_ONLY_ROADMAP_GENERATION;
  const isAuthenticatedUser = isLoggedIn();

  const renderRoadmap = async (roadmap: string) => {
    const { nodes, edges } = generateAIRoadmapFromText(roadmap);
    const svg = await renderFlowJSON({ nodes, edges });
    if (roadmapContainerRef?.current) {
      replaceChildren(roadmapContainerRef?.current, svg);
    }
  };

  const loadTermRoadmap = async (term: string) => {
    setIsLoading(true);
    setHasSubmitted(true);

    deleteUrlParam('id');
    setCurrentRoadmap(null);

    const origin = window.location.origin;
    window.history.pushState(null, '', `${origin}/ai`);
    const response = await fetch(
      `${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-roadmap`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        credentials: 'include',
        body: JSON.stringify({ term }),
      },
    );

    if (!response.ok) {
      const data = await response.json();

      toast.error(data?.message || 'Something went wrong');
      setIsLoading(false);

      // Logout user if token is invalid
      if (data.status === 401) {
        removeAuthToken();
        window.location.reload();
      }
    }

    const reader = response.body?.getReader();

    if (!reader) {
      setIsLoading(false);
      toast.error('Something went wrong');
      return;
    }

    await readAIRoadmapStream(reader, {
      onStream: async (result) => {
        if (result.includes('@ROADMAPID') || result.includes('@ROADMAPSLUG')) {
          // @ROADMAPID: is a special token that we use to identify the roadmap
          // @ROADMAPID:1234@ is the format, we will remove the token and the id
          // and replace it with a empty string
          const roadmapId = result.match(ROADMAP_ID_REGEX)?.[1] || '';
          const roadmapSlug = result.match(ROADMAP_SLUG_REGEX)?.[1] || '';

          window.history.pushState(null, '', `${origin}/ai/${roadmapSlug}`);
          result = result
            .replace(ROADMAP_ID_REGEX, '')
            .replace(ROADMAP_SLUG_REGEX, '');

          setRoadmapId(roadmapId);
          setRoadmapSlug(roadmapSlug);

          const roadmapTitle =
            result.trim().split('\n')[0]?.replace('#', '')?.trim() || term;
          setRoadmapTerm(roadmapTitle);
          setCurrentRoadmap({
            id: roadmapId,
            term: roadmapTerm,
            title: roadmapTitle,
            data: result,
          });
        }

        await renderRoadmap(result);
      },
      onStreamEnd: async (result) => {
        result = result
          .replace(ROADMAP_ID_REGEX, '')
          .replace(ROADMAP_SLUG_REGEX, '');

        setGeneratedRoadmapContent(result);
        loadAIRoadmapLimit().finally(() => {});
      },
    });

    setIsLoading(false);
  };

  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!roadmapTerm || isLoadingResults) {
      return;
    }

    if (roadmapTerm === currentRoadmap?.term) {
      return;
    }

    loadTermRoadmap(roadmapTerm).finally(() => null);
  };

  const saveAIRoadmap = async () => {
    if (!isLoggedIn()) {
      showLoginPopup();
      return;
    }

    pageProgressMessage.set('Redirecting to Editor');

    const { nodes, edges } = generateAIRoadmapFromText(generatedRoadmapContent);

    const { response, error } = await httpPost<{
      roadmapId: string;
    }>(
      `${import.meta.env.PUBLIC_API_URL}/v1-save-ai-roadmap/${currentRoadmap?.id}`,
      {
        title: roadmapTerm,
        nodes: nodes.map((node) => ({
          ...node,

          // To reset the width and height of the node
          // so that it can be calculated based on the content in the editor
          width: undefined,
          height: undefined,
          style: {
            ...node.style,
            width: undefined,
            height: undefined,
          },
        })),
        edges,
      },
    );

    if (error || !response) {
      toast.error(error?.message || 'Something went wrong');
      pageProgressMessage.set('');
      setIsLoading(false);
      return;
    }

    setIsLoading(false);
    pageProgressMessage.set('');
    return response.roadmapId;
  };

  const downloadGeneratedRoadmapContent = async () => {
    if (!isLoggedIn()) {
      showLoginPopup();
      return;
    }

    pageProgressMessage.set('Downloading Roadmap');

    const node = document.getElementById('roadmap-container');
    if (!node) {
      toast.error('Something went wrong');
      return;
    }

    try {
      await downloadGeneratedRoadmapImage(roadmapTerm, node);
      pageProgressMessage.set('');
    } catch (error) {
      console.error(error);
      toast.error('Something went wrong');
    }
  };

  const loadAIRoadmapLimit = async () => {
    const { response, error } = await httpGet<GetAIRoadmapLimitResponse>(
      `${import.meta.env.PUBLIC_API_URL}/v1-get-ai-roadmap-limit`,
    );

    if (error || !response) {
      toast.error(error?.message || 'Something went wrong');
      return;
    }

    const { limit, used, topicLimit, topicUsed } = response;
    setRoadmapLimit(limit);
    setRoadmapLimitUsed(used);
    setRoadmapTopicLimit(topicLimit);
    setRoadmapTopicLimitUsed(topicUsed);
  };

  const loadAIRoadmap = async (roadmapId: string) => {
    pageProgressMessage.set('Loading Roadmap');

    const { response, error } = await httpGet<{
      term: string;
      title: string;
      data: string;
    }>(`${import.meta.env.PUBLIC_API_URL}/v1-get-ai-roadmap/${roadmapId}`);

    if (error || !response) {
      toast.error(error?.message || 'Something went wrong');
      setIsLoading(false);
      return;
    }

    const { term, title, data } = response;
    await renderRoadmap(data);

    setCurrentRoadmap({
      id: roadmapId,
      title: title,
      term: term,
      data,
    });

    setRoadmapTerm(term);
    setGeneratedRoadmapContent(data);
    visitAIRoadmap(roadmapId);
  };

  const handleNodeClick = useCallback(
    (e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>) => {
      if (isLoading) {
        return;
      }

      const target = e.target as SVGElement;
      const { nodeId, nodeType, targetGroup, nodeTitle, parentTitle } =
        getNodeDetails(target) || {};
      if (
        !nodeId ||
        !nodeType ||
        !allowedClickableNodeTypes.includes(nodeType) ||
        !nodeTitle
      )
        return;

      if (nodeType === 'button' || nodeType === 'link-item') {
        const link = targetGroup?.dataset?.link || '';
        const isExternalLink = link.startsWith('http');
        if (isExternalLink) {
          window.open(link, '_blank');
        } else {
          window.location.href = link;
        }
        return;
      }

      setSelectedNode({
        nodeId,
        nodeType,
        nodeTitle,
        ...(nodeType === 'subtopic' && { parentTitle }),
      });
    },
    [isLoading],
  );

  useEffect(() => {
    loadAIRoadmapLimit().finally(() => {});
  }, []);

  useEffect(() => {
    if (!referralCode || isLoggedIn()) {
      deleteUrlParam('rc');
      return;
    }

    setAIReferralCode(referralCode);
    deleteUrlParam('rc');
    showLoginPopup();
  }, []);

  useEffect(() => {
    if (!roadmapId || roadmapId === currentRoadmap?.id) {
      return;
    }

    loadAIRoadmap(roadmapId).finally(() => {
      pageProgressMessage.set('');
    });
  }, [roadmapId, currentRoadmap]);

  if (!hasSubmitted) {
    return (
      <RoadmapSearch
        roadmapTerm={roadmapTerm}
        setRoadmapTerm={setRoadmapTerm}
        handleSubmit={handleSubmit}
        limit={roadmapLimit}
        limitUsed={roadmapLimitUsed}
        loadAIRoadmapLimit={loadAIRoadmapLimit}
        isKeyOnly={isKeyOnly}
        onLoadTerm={(term) => {
          setRoadmapTerm(term);
          loadTermRoadmap(term).finally(() => {});
        }}
      />
    );
  }

  const pageUrl = `https://roadmap.sh/ai/${roadmapSlug}`;
  const canGenerateMore = roadmapLimitUsed < roadmapLimit;

  return (
    <>
      {isConfiguring && (
        <IncreaseRoadmapLimit
          onClose={() => {
            setOpenAPIKey(getOpenAIKey());
            setIsConfiguring(false);
            loadAIRoadmapLimit().finally(() => null);
          }}
        />
      )}

      {selectedNode && currentRoadmap && !isLoading && (
        <RoadmapTopicDetail
          nodeId={selectedNode.nodeId}
          nodeType={selectedNode.nodeType}
          nodeTitle={selectedNode.nodeTitle}
          parentTitle={selectedNode.parentTitle}
          onConfigureOpenAI={() => {
            setSelectedNode(null);
            setIsConfiguring(true);
          }}
          onClose={() => {
            setSelectedNode(null);
            loadAIRoadmapLimit().finally(() => {});
          }}
          roadmapId={currentRoadmap?.id || ''}
          topicLimit={roadmapTopicLimit}
          topicLimitUsed={roadmapTopicLimitUsed}
          onTopicContentGenerateComplete={async () => {
            await loadAIRoadmapLimit();
          }}
        />
      )}

      <section className="flex flex-grow flex-col bg-gray-100">
        <div className="flex items-center justify-center border-b bg-white py-3 sm:py-6">
          {isLoading && (
            <span className="flex items-center gap-2 rounded-full bg-black px-3 py-1 text-white">
              <Spinner isDualRing={false} innerFill={'white'} />
              Generating roadmap ..
            </span>
          )}
          {!isLoading && (
            <div className="container flex flex-grow flex-col items-start">
              <AIRoadmapAlert />
              {isKeyOnly && isAuthenticatedUser && (
                <div className="flex flex-row gap-4">
                  {!openAPIKey && (
                    <p className={'text-left text-red-500'}>
                      We have hit the limit for AI roadmap generation. Please
                      try again tomorrow or{' '}
                      <button
                        onClick={() => setIsConfiguring(true)}
                        className="font-semibold text-purple-600 underline underline-offset-2"
                      >
                        add your own OpenAI API key
                      </button>
                    </p>
                  )}
                  {openAPIKey && (
                    <p className={'text-left text-gray-500'}>
                      You have added your own OpenAI API key.{' '}
                      <button
                        onClick={() => setIsConfiguring(true)}
                        className="font-semibold text-purple-600 underline underline-offset-2"
                      >
                        Configure it here if you want.
                      </button>
                    </p>
                  )}
                </div>
              )}
              {!isKeyOnly && isAuthenticatedUser && (
                <div className="mt-2 flex w-full flex-col items-start justify-between gap-2 text-sm sm:flex-row sm:items-center sm:gap-0">
                  <span>
                    <span
                      className={cn(
                        'mr-0.5 inline-block rounded-xl border px-1.5 text-center text-sm tabular-nums text-gray-800',
                        {
                          'animate-pulse border-zinc-300 bg-zinc-300 text-zinc-300':
                            !roadmapLimit,
                        },
                      )}
                    >
                      {roadmapLimitUsed} of {roadmapLimit}
                    </span>{' '}
                    roadmaps generated today.
                  </span>
                  {!openAPIKey && (
                    <button
                      onClick={() => setIsConfiguring(true)}
                      className="rounded-xl border border-current px-2 py-0.5 text-left text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white"
                    >
                      Need to generate more?{' '}
                      <span className="font-semibold">Click here.</span>
                    </button>
                  )}

                  {openAPIKey && (
                    <button
                      onClick={() => setIsConfiguring(true)}
                      className="flex flex-row items-center gap-1 rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white"
                    >
                      <Cog size={15} />
                      Configure OpenAI key
                    </button>
                  )}
                </div>
              )}
              {!isAuthenticatedUser && (
                <button
                  className="mt-2 rounded-xl border border-current px-2.5 py-0.5 text-left text-sm font-medium text-blue-500 transition-colors hover:bg-blue-500 hover:text-white sm:text-center"
                  onClick={showLoginPopup}
                >
                  Login to generate your own roadmaps
                </button>
              )}
              <form
                onSubmit={handleSubmit}
                className="my-3 flex w-full flex-col gap-2 sm:flex-row sm:items-center sm:justify-center"
              >
                <AITermSuggestionInput
                  value={roadmapTerm}
                  onValueChange={(value) => setRoadmapTerm(value)}
                  placeholder="e.g. Try searching for Ansible or DevOps"
                  wrapperClassName="grow"
                  onSelect={(id, title) => {
                    loadTermRoadmap(title).finally(() => null);
                  }}
                />
                <button
                  type={'submit'}
                  className={cn(
                    'flex min-w-[127px] flex-shrink-0 items-center justify-center gap-2 rounded-md bg-black px-4 py-2 text-white',
                    'disabled:cursor-not-allowed disabled:opacity-50',
                  )}
                  onClick={(e) => {
                    if (!isAuthenticatedUser) {
                      e.preventDefault();
                      showLoginPopup();
                    }
                  }}
                  disabled={
                    isLoadingResults ||
                    (isAuthenticatedUser &&
                      (!roadmapLimit ||
                        !roadmapTerm ||
                        roadmapLimitUsed >= roadmapLimit ||
                        roadmapTerm === currentRoadmap?.term ||
                        (isKeyOnly && !openAPIKey)))
                  }
                >
                  {isLoadingResults && (
                    <>
                      <span>Please wait..</span>
                    </>
                  )}
                  {!isLoadingResults && (
                    <>
                      {!isAuthenticatedUser && (
                        <>
                          <Wand size={20} />
                          Generate
                        </>
                      )}

                      {isAuthenticatedUser && (
                        <>
                          {roadmapLimit > 0 && canGenerateMore && (
                            <>
                              <Wand size={20} />
                              Generate
                            </>
                          )}

                          {roadmapLimit === 0 && <span>Please wait..</span>}

                          {roadmapLimit > 0 && !canGenerateMore && (
                            <span className="flex items-center">
                              <Ban size={15} className="mr-2" />
                              Limit reached
                            </span>
                          )}
                        </>
                      )}
                    </>
                  )}
                </button>
              </form>
              <div className="flex w-full items-center justify-between gap-2">
                <div className="flex items-center justify-between gap-2">
                  <button
                    className="inline-flex items-center justify-center gap-2 rounded-md bg-yellow-400 py-1.5 pl-2.5 pr-3 text-xs font-medium transition-opacity duration-300 hover:bg-yellow-500 sm:text-sm"
                    onClick={downloadGeneratedRoadmapContent}
                  >
                    <Download size={15} />
                    Download
                  </button>
                  {roadmapId && (
                    <ShareRoadmapButton
                      description={`Check out ${roadmapTerm} roadmap I generated on roadmap.sh`}
                      pageUrl={pageUrl}
                    />
                  )}
                </div>

                <div className="flex items-center justify-between gap-2">
                  <button
                    className="inline-flex items-center justify-center gap-2 rounded-md bg-gray-200 py-1.5 pl-2.5 pr-3 text-xs font-medium text-black transition-colors duration-300 hover:bg-gray-300 sm:text-sm"
                    onClick={async () => {
                      const roadmapId = await saveAIRoadmap();
                      if (roadmapId) {
                        window.location.href = `/r?id=${roadmapId}`;
                      }
                    }}
                    disabled={isLoading}
                  >
                    <Save size={15} />
                    <span className="hidden sm:inline">
                      Save and Start Learning
                    </span>
                    <span className="inline sm:hidden">Start Learning</span>
                  </button>

                  <button
                    className="hidden items-center justify-center gap-2 rounded-md bg-gray-200 py-1.5 pl-2.5 pr-3 text-xs font-medium text-black transition-colors duration-300 hover:bg-gray-300 sm:inline-flex sm:text-sm"
                    onClick={async () => {
                      const roadmapId = await saveAIRoadmap();
                      if (roadmapId) {
                        window.open(
                          `${import.meta.env.PUBLIC_EDITOR_APP_URL}/${roadmapId}`,
                          '_blank',
                        );
                      }
                    }}
                    disabled={isLoading}
                  >
                    <PenSquare size={15} />
                    Edit in Editor
                  </button>
                </div>
              </div>
            </div>
          )}
        </div>
        <div
          className={cn({
            'relative mb-20 max-h-[800px] min-h-[800px] overflow-hidden sm:max-h-[1000px]  md:min-h-[1000px] lg:max-h-[1200px] lg:min-h-[1200px]':
              !isAuthenticatedUser,
          })}
        >
          <div
            ref={roadmapContainerRef}
            id="roadmap-container"
            onClick={handleNodeClick}
            className="relative min-h-[400px] px-4 py-5 [&>svg]:mx-auto [&>svg]:max-w-[1300px]"
          />
          {!isAuthenticatedUser && (
            <div className="absolute bottom-0 left-0 right-0">
              <div className="h-80 w-full bg-gradient-to-t from-gray-100 to-transparent" />
              <div className="bg-gray-100">
                <div className="mx-auto max-w-[600px] flex-col items-center justify-center bg-gray-100 px-5 pt-px">
                  <div className="mt-8 text-center">
                    <h2 className="mb-0.5 text-xl font-medium sm:mb-3 sm:text-2xl">
                      Sign up to View the full roadmap
                    </h2>
                    <p className="mb-6 text-balance text-sm text-gray-600 sm:text-base">
                      You must be logged in to view the complete roadmap
                    </p>
                  </div>
                  <div className="mx-auto max-w-[350px]">
                    <AuthenticationForm type="signup" />

                    <div className="mt-6 text-center text-sm text-slate-600">
                      Already have an account?{' '}
                      <a
                        href="/login"
                        className="font-medium text-blue-700 hover:text-blue-600"
                      >
                        Login
                      </a>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          )}
        </div>
      </section>
    </>
  );
}