import {
  useState,
  useEffect,
  useCallback,
  useMemo,
  useRef,
  type Dispatch,
  type SetStateAction,
} from "react";
import { TauriService, type CustomEdition } from "../services/TauriService";
import { getCurrentWindow } from "@tauri-apps/api/window";
import type { Edition } from "../types/edition";

async function imageUrlToBase64(url: string): Promise<string> {
  const response = await fetch(url);
  const blob = await response.blob();
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onloadend = () => {
      const result = reader.result as string;
      const base64 = result.split(",")[1];
      resolve(base64);
    };
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}
const BASE_EDITIONS = [
  {
    id: "legacy_evolved",
    name: "neoLegacy",
    desc: "Backporting newer title updates and Minigames back to LCE",
    url: "https://git.neolegacy.dev/neoStudiosLCE/neoLegacy/releases/download/1.0.8b/neoLegacyWindows64.zip",
    titleImage: "/images/minecraft_title_neoLegacy.png",
    supportsSlimSkins: true,
    logo: "/images/neoLegacy.png",
    panorama: "legacy_evolved",
    officialDLC: "main:https://git.neolegacy.dev/neoStudiosLCE/DLCs", //neo: format is {branch}:{git_url}
  },
  {
    id: "revelations",
    name: "Legacy Revelations",
    desc: "QoL, performance, hardcore mode, & security features for LCE.",
    url: "https://git.revela.dev/itsRevela/LCE-Revelations/releases/download/Nightly/LCE-Revelations-Client-Win64.zip",
    titleImage: "/images/minecraft_title_revelations.png",
    supportsSlimSkins: false,
    panorama: "vanilla_tu24",
    logo: "/images/revelations.png",
  },
  {
    id: "360revived",
    name: "360 Revived",
    desc: "PC port of Xbox 360 Edition TU19",
    url: "https://github.com/BluTac10/360Revived/releases/download/nightly/LCEWindows64.zip",
    titleImage: "/images/minecraft_title_360revived.png",
    supportsSlimSkins: false,
    logo: "/images/360_revived.png",
    panorama: "360revived",
  },
  {
    id: "legacy_nether_fork",
    name: "Hellish Ends",
    desc: "QoL, Random additions, and Nether/End/Aether dimensions overhaul (Modded build!)",
    url: "https://github.com/deadvoxelx/ThatModdedRepo/releases/download/nightly/LCEWindows64.zip",
    titleImage: "/images/minecraft_title_hellishends.png",
    supportsSlimSkins: false,
    logo: "/images/hellishEndsIcon.png",
    panorama: "hellishends",
  },
  {
    id: "moon_edition",
    name: "Minecraft: Moon Edition",
    desc: "Galacticraft LCE port (Modded build!)",
    url: "https://INTENTIONALLY_INVALID_LINK_BECAUSE_FUCK_YOU_THATS_WHY", //neo: placeholder for now
    titleImage: "/images/minecraft_title_moon.png",
    supportsSlimSkins: false,
    logo: "/images/moonEdition.png",
    panorama: "vanilla_tu19",
  },
];

const PARTNERSHIP_SERVERS = [
  {
    name: "Kowhaifans Clubhouse",
    ip: "lce.kowhaifan.net",
    port: 25565,
  },
  {
    name: "BluerNetwork",
    ip: "bluer-network.ddns.net",
    port: 25565,
  },
];

interface GameManagerProps {
  profile: string;
  setProfile: (id: string) => void;
  customEditions: CustomEdition[];
  setCustomEditions: (editions: CustomEdition[]) => void;
  customizations: Record<string, { titleImage?: string; panorama?: string }>;
  setCustomizations: Dispatch<
    SetStateAction<Record<string, { titleImage?: string; panorama?: string }>>
  >;
  extraLaunchArgs?: string[];
}

function compareVersions(v1: string, v2: string) {
  const parts1 = v1.replace(/^v/, "").split(/[.-]/);
  const parts2 = v2.replace(/^v/, "").split(/[.-]/);
  for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
    const p1 = parts1[i] || "0";
    const p2 = parts2[i] || "0";
    const n1 = parseInt(p1);
    const n2 = parseInt(p2);
    if (!isNaN(n1) && !isNaN(n2)) {
      if (n1 !== n2) return n1 - n2;
    } else {
      if (p1 !== p2) return p1 > p2 ? 1 : -1;
    }
  }
  return 0;
}

export function useGameManager({
  profile,
  setProfile,
  customEditions,
  setCustomEditions,
  customizations,
  setCustomizations,
  extraLaunchArgs,
}: GameManagerProps) {
  const [installs, setInstalls] = useState<string[]>([]);
  const [isGameRunning, setIsGameRunning] = useState(false);
  const [downloadProgress, setDownloadProgress] = useState<Record<string, number>>({});
  const [downloadingIds, setDownloadingIds] = useState<string[]>([]);
  const [isRunnerDownloading, setIsRunnerDownloading] = useState(false);
  const [runnerDownloadProgress, setRunnerDownloadProgress] = useState<
    number | null
  >(null);
  const [error, setError] = useState<string | null>(null);
  const [gameUpdateMessage, setGameUpdateMessage] = useState<string | null>(
    null,
  );
  const [steamSuccessMessage, setSteamSuccessMessage] = useState<string | null>(
    null,
  );
  const [dynamicUrls, setDynamicUrls] = useState<Record<string, string>>({});
  const [branches, setBranches] = useState<Record<string, string[]>>({});
  const [selectedBranches, setSelectedBranches] = useState<
    Record<string, string>
  >({});
  const branchesFetched = useRef<Set<string>>(new Set());
  const initialBranchesSet = useRef(false);
  useEffect(() => {
    if (initialBranchesSet.current || !profile) return;
    BASE_EDITIONS.forEach((e) => {
      if (profile.startsWith(e.id + "_")) {
        const branch = profile.replace(e.id + "_", "");
        setSelectedBranches((prev) => ({ ...prev, [e.id]: branch }));
      } else if (profile === e.id) {
        setSelectedBranches((prev) => ({ ...prev, [e.id]: "Stable" }));
      }
    });
    initialBranchesSet.current = true;
  }, [profile]);

  const fetchBranchesForEdition = useCallback(
    async (editionId: string, url: string) => {
      if (branchesFetched.current.has(editionId)) return;
      if (!url.includes("/releases/download/")) return;
      try {
        const urlObj = new URL(url);
        const pathParts = urlObj.pathname
          .split("/releases/download/")[0]
          .split("/")
          .filter(Boolean);
        const owner = pathParts[0];
        const repo = pathParts[1];
        const isGitHub = urlObj.host === "github.com";
        const apiBase = isGitHub
          ? `https://api.github.com/repos/${owner}/${repo}`
          : `${urlObj.origin}/api/v1/repos/${owner}/${repo}`;

        const res = await TauriService.httpProxyRequest(
          "GET",
          `${apiBase}/releases`,
          null,
          {},
        );
        if (res.status >= 200 && res.status < 300) {
          const data = JSON.parse(res.body);
          let tags: string[] = data
            .map((r: { tag_name: string }) => r.tag_name)
            .filter((t: string) => !t.toLowerCase().includes("server"));

          const vTags = tags
            .filter((t) => t.startsWith("v"))
            .sort(compareVersions);
          const bestVTag = vTags[vTags.length - 1];
          if (!tags.includes("Stable")) tags.unshift("Stable");
          if (bestVTag) {
            setDynamicUrls((prev) => ({
              ...prev,
              [`${editionId}_Stable`]: bestVTag,
            }));
          }

          setBranches((prev) => ({ ...prev, [editionId]: tags }));
          branchesFetched.current.add(editionId);
          if (data.length > 0) {
            const filename = url.split("/").pop();
            const asset = data[0].assets?.find(
              (a: { name: string }) => a.name === filename,
            );
            if (asset) {
              setDynamicUrls((prev) => ({
                ...prev,
                [editionId]: asset.browser_download_url,
              }));
            }
          }
        }
      } catch (e) {
        console.error(`Failed to fetch branches for ${editionId}:`, e);
      }
    },
    [],
  );

  useEffect(() => {
    BASE_EDITIONS.forEach((e) => fetchBranchesForEdition(e.id, e.url));
  }, [fetchBranchesForEdition]);

  const cycleBranch = useCallback(
    (editionId: string) => {
      const available = branches[editionId] || ["Stable"];
      if (available.length <= 1) return;
      setSelectedBranches((prev) => {
        const current = prev[editionId] || available[0];
        let currentIndex = available.indexOf(current);
        let nextIndex = currentIndex;
        let nextBranch = current;
        do {
          nextIndex = (nextIndex + 1) % available.length;
          nextBranch = available[nextIndex];
        } while (nextBranch.startsWith("v") && nextIndex !== currentIndex);
        const oldInstanceId =
          current === "Stable" ? editionId : `${editionId}_${current}`;
        const newInstanceId =
          nextBranch === "Stable" ? editionId : `${editionId}_${nextBranch}`;
        if (profile === oldInstanceId) {
          setProfile(newInstanceId);
        }
        return { ...prev, [editionId]: nextBranch };
      });
    },
    [branches, profile, setProfile],
  );

  const editions = useMemo((): Edition[] => {
    return [
      ...BASE_EDITIONS.map((e) => {
        const availableBranches = branches[e.id] || ["Stable"];
        const selectedBranch = selectedBranches[e.id] || availableBranches[0];
        let url = dynamicUrls[e.id] || e.url;
        const defaultBranchFromUrl = e.url.includes("/releases/download/")
          ? e.url.split("/releases/download/")[1].split("/")[0]
          : "nightly";
        const branchToUse =
          selectedBranch === "Stable"
            ? dynamicUrls[`${e.id}_Stable`] || defaultBranchFromUrl
            : selectedBranch;
        if (e.url.includes("/releases/download/")) {
          const baseUrl = e.url.split("/releases/download/")[0];
          const filename = e.url.split("/").pop();
          url = `${baseUrl}/releases/download/${branchToUse}/${filename}`;
        }

        const edition = {
          ...e,
          url,
          branches: availableBranches,
          selectedBranch,
          instanceId:
            selectedBranch === "Stable" ? e.id : `${e.id}_${selectedBranch}`,
        };
        const custom = customizations[e.id];
        if (custom?.titleImage) edition.titleImage = custom.titleImage;
        if (custom?.panorama) edition.panorama = custom.panorama;
        return edition;
      }),
      ...customEditions.map((e) => {
        const edition: Edition = { ...e, instanceId: e.id };
        const custom = customizations[e.id];
        if (custom?.titleImage) edition.titleImage = custom.titleImage;
        if (custom?.panorama) edition.panorama = custom.panorama;
        return edition;
      }),
    ];
  }, [customEditions, dynamicUrls, branches, selectedBranches, customizations]);

  const checkInstalls = useCallback(async () => {
    const results = await Promise.all(
      editions.map(async (e) => {
        const isInstalled = await TauriService.checkGameInstalled(e.instanceId);
        return isInstalled ? e.instanceId : null;
      }),
    );
    setInstalls(results.filter((id): id is string => id !== null));
  }, [editions]);

  const [updatesAvailable, setUpdatesAvailable] = useState<
    Record<string, boolean>
  >({});
  const checkForGameUpdates = useCallback(async () => {
    const checks = await Promise.all(
      editions.map(async (edition) => {
        if (!installs.includes(edition.instanceId))
          return [edition.instanceId, false] as const;
        try {
          const isUpdate = await TauriService.checkGameUpdate(
            edition.instanceId,
            edition.url,
          );
          return [edition.instanceId, isUpdate] as const;
        } catch (e) {
          console.error(e);
          return [edition.instanceId, false] as const;
        }
      }),
    );

    const newUpdates: Record<string, boolean> = {};
    for (const [id, hasUpdate] of checks) {
      newUpdates[id as string] = hasUpdate as boolean;
    }
    setUpdatesAvailable(newUpdates);

    const updatedGames = editions.filter((e) => newUpdates[e.id]);
    if (updatedGames.length > 0) {
      if (updatedGames.length === 1) {
        setGameUpdateMessage(
          `An update is available for ${updatedGames[0].name}!`,
        );
      } else {
        setGameUpdateMessage(
          `Updates are available for ${updatedGames.length} versions!`,
        );
      }
    } else {
      setGameUpdateMessage(null);
    }
  }, [editions, installs]);

  useEffect(() => {
    checkForGameUpdates();
  }, [profile, installs, checkForGameUpdates]);

  useEffect(() => {
    checkInstalls();
    const unlistenDownload = TauriService.onDownloadProgress((data) =>
      setDownloadProgress((prev) => ({ ...prev, [data.instanceId]: data.percent })),
    );
    const unlistenRunner = TauriService.onRunnerDownloadProgress((p) =>
      setRunnerDownloadProgress(p),
    );
    const unlistenError = TauriService.onBackendError((msg) => {
      setError(msg);
    });
    const unlistenRetry = TauriService.onDownloadRetry((attempt) => {
      setError(`Download failed, retrying (${attempt}/3)...`);
    });
    return () => {
      unlistenDownload.then((u) => u());
      unlistenRunner.then((u) => u());
      unlistenError.then((u) => u());
      unlistenRetry.then((u) => u());
    };
  }, [customEditions, checkInstalls]);

  const downloadRunner = useCallback(
    async (name: string, url: string) => {
      if (isRunnerDownloading) return;
      setIsRunnerDownloading(true);
      setRunnerDownloadProgress(0);
      setError(null);
      try {
        await TauriService.downloadRunner(name, url);
        setRunnerDownloadProgress(null);
      } catch (e: unknown) {
        console.error(e);
        setError(
          e instanceof Error
            ? e.message
            : typeof e === "string"
              ? e
              : "Failed to download runner",
        );
      } finally {
        setIsRunnerDownloading(false);
      }
    },
    [isRunnerDownloading],
  );

  const toggleInstall = useCallback(
    async (id: string) => {
      if (downloadingIds.includes(id)) return;
      const edition = editions.find((e) => e.instanceId === id);
      if (!edition) return;
      setError(null);
      try {
        setDownloadingIds((prev) => [...prev, id]);
        setDownloadProgress((prev) => ({ ...prev, [id]: 0 }));
        await TauriService.downloadAndInstall(edition.url, id);
        await TauriService.syncDlc(id);
        await checkInstalls();
        setProfile(id);
        setDownloadProgress((prev) => {
          const next = { ...prev };
          delete next[id];
          return next;
        });
        setDownloadingIds((prev) => prev.filter((did) => did !== id));
      } catch (e: unknown) {
        console.error(e);
        setError(
          e instanceof Error
            ? e.message
            : typeof e === "string"
              ? e
              : "Failed to install version",
        );
        setDownloadProgress((prev) => {
          const next = { ...prev };
          delete next[id];
          return next;
        });
        setDownloadingIds((prev) => prev.filter((did) => did !== id));
      }
    },
    [downloadingIds, editions, checkInstalls, setProfile],
  );

  const handleUninstall = useCallback(
    async (id: string) => {
      await TauriService.deleteInstance(id);
      await checkInstalls();
    },
    [checkInstalls],
  );

  const handleCancelDownload = useCallback(async (id: string) => {
    try {
      await TauriService.cancelDownload(id);
      await TauriService.deleteInstance(id);
      setDownloadProgress((prev) => {
        const next = { ...prev };
        delete next[id];
        return next;
      });
      setDownloadingIds((prev) => prev.filter((did) => did !== id));
      await checkInstalls();
    } catch (e) {
      console.error(e);
    }
  }, [checkInstalls]);

  const handleLaunch = useCallback(async () => {
    if (isGameRunning) return;
    setError(null);
    setIsGameRunning(true);
    try {
      getCurrentWindow().minimize();
      await TauriService.launchGame(
        profile,
        PARTNERSHIP_SERVERS,
        extraLaunchArgs,
      );
    } catch (e: unknown) {
      console.error(e);
      setError(
        e instanceof Error
          ? e.message
          : typeof e === "string"
            ? e
            : "Failed to launch game",
      );
    } finally {
      setIsGameRunning(false);
    }
  }, [isGameRunning, profile, extraLaunchArgs]);

  const stopGame = useCallback(async () => {
    try {
      await TauriService.stopGame(profile);
      setIsGameRunning(false);
    } catch (e) {
      console.error(e);
    }
  }, [profile]);

  const addCustomEdition = useCallback(
    (edition: {
      name: string;
      desc: string;
      url: string;
      path?: string;
      category?: string[];
      logo?: string;
      id?: string;
    }) => {
      const id = edition.id || `custom_${Date.now()}`;
      const newEdition = {
        ...edition,
        id,
        titleImage: "/images/MenuTitle.png",
      };
      setCustomEditions([...customEditions, newEdition]);
      return id;
    },
    [customEditions, setCustomEditions],
  );

  const deleteCustomEdition = useCallback(
    (id: string) => {
      setCustomEditions(customEditions.filter((e) => e.id !== id));
      TauriService.deleteInstance(id).catch(console.error);
    },
    [customEditions, setCustomEditions],
  );

  const updateCustomEdition = useCallback(
    (
      id: string,
      updated: { name: string; desc: string; url: string; path?: string },
    ) => {
      setCustomEditions(
        customEditions.map((e) => (e.id === id ? { ...e, ...updated } : e)),
      );
    },
    [customEditions, setCustomEditions],
  );

  const updateCustomization = useCallback(
    (
      instanceId: string,
      updates: { titleImage?: string; panorama?: string },
    ) => {
      setCustomizations((prev) => ({
        ...prev,
        [instanceId]: { ...prev[instanceId], ...updates },
      }));
    },
    [],
  );

  const saveCustomPath = useCallback(
    async (instanceId: string, path: string) => {
      const config = await TauriService.loadConfig();
      config.customPaths = {
        ...config.customPaths,
        [instanceId]: path,
      };
      await TauriService.saveConfig(config);
    },
    [],
  );

  const addToSteam = useCallback(
    async (
      id: string,
      name: string,
      titleImage: string,
      panoramaImage: string,
    ) => {
      try {
        const titleBase64 = await imageUrlToBase64(titleImage);
        const panoramaBase64 = await imageUrlToBase64(panoramaImage);
        await TauriService.addToSteam(id, name, titleBase64, panoramaBase64);
        setSteamSuccessMessage(
          `Added ${name} to Steam! (Restart Steam to see it)`,
        );
      } catch (e: unknown) {
        console.error(e);
        setError(
          e instanceof Error
            ? e.message
            : typeof e === "string"
              ? e
              : "Failed to add to Steam",
        );
      }
    },
    [setError, setSteamSuccessMessage],
  );

  return {
    installs,
    isGameRunning,
    downloadProgress,
    downloadingIds,
    isRunnerDownloading,
    runnerDownloadProgress,
    error,
    setError,
    editions,
    toggleInstall,
    handleUninstall,
    handleCancelDownload,
    handleLaunch,
    stopGame,
    addCustomEdition,
    deleteCustomEdition,
    updateCustomEdition,
    downloadRunner,
    checkInstalls,
    gameUpdateMessage,
    setGameUpdateMessage,
    steamSuccessMessage,
    setSteamSuccessMessage,
    updatesAvailable,
    addToSteam,
    cycleBranch,
    customizations,
    updateCustomization,
    saveCustomPath,
  };
}
