import { invoke } from "@tauri-apps/api/core";
import type { Event as TauriEvent } from "@tauri-apps/api/event";
import { listen } from "@tauri-apps/api/event";
import { useCallback, useEffect, useState } from "react";
import i18n from "@/i18n";
import { getBrowserDisplayName } from "@/lib/browser-utils";
import { isOnboardingActive } from "@/lib/onboarding-signal";
import {
  dismissToast,
  showDownloadToast,
  showErrorToast,
  showSuccessToast,
  showToast,
} from "@/lib/toast-utils";

interface GithubRelease {
  tag_name: string;
  assets: {
    name: string;
    browser_download_url: string;
    hash?: string;
  }[];
  published_at: string;
  is_nightly: boolean;
}

interface BrowserVersionInfo {
  version: string;
  date: string;
}

interface DownloadProgress {
  browser: string;
  version: string;
  downloaded_bytes: number;
  total_bytes?: number;
  percentage: number;
  speed_bytes_per_sec: number;
  eta_seconds?: number;
  stage: string;
}

interface BrowserVersionsResult {
  versions: string[];
  new_versions_count?: number;
  total_versions_count: number;
}

export function useBrowserDownload() {
  const [availableVersions, setAvailableVersions] = useState<GithubRelease[]>(
    [],
  );
  const [downloadedVersionsMap, setDownloadedVersionsMap] = useState<
    Record<string, string[]>
  >({});
  const [downloadingBrowsers, setDownloadingBrowsers] = useState<Set<string>>(
    new Set(),
  );
  const [downloadProgress, setDownloadProgress] =
    useState<DownloadProgress | null>(null);

  const formatTime = useCallback((seconds: number): string => {
    if (seconds < 60) {
      return `${Math.round(seconds)}s`;
    }
    if (seconds < 3600) {
      const minutes = Math.floor(seconds / 60);
      const remainingSeconds = Math.round(seconds % 60);
      return `${minutes}m ${remainingSeconds}s`;
    }
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    return `${hours}h ${minutes}m`;
  }, []);

  const formatBytes = useCallback((bytes: number): string => {
    if (bytes === 0) return "0 B";
    const k = 1024;
    const sizes = ["B", "KB", "MB", "GB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`;
  }, []);

  const loadVersions = useCallback(async (browserStr: string) => {
    const browserName = getBrowserDisplayName(browserStr);

    // Use a simple loading state instead of toast for version fetching
    console.log(`Fetching ${browserName} versions...`);

    try {
      const versionInfos = await invoke<BrowserVersionInfo[]>(
        "fetch_browser_versions_cached_first",
        { browserStr },
      );

      // Convert BrowserVersionInfo to GithubRelease format for compatibility
      const githubReleases: GithubRelease[] = versionInfos.map(
        (versionInfo) => ({
          tag_name: versionInfo.version,
          assets: [],
          published_at: versionInfo.date,
          is_nightly: false,
        }),
      );

      setAvailableVersions(githubReleases);
      return githubReleases;
    } catch (error) {
      console.error("Failed to load versions:", error);
      showErrorToast(
        i18n.t("browserDownload.toast.fetchVersionsFailed", {
          browser: browserName,
        }),
        {
          description:
            error instanceof Error
              ? error.message
              : i18n.t("common.errors.unknown"),
          duration: 4000,
        },
      );
      throw error;
    }
  }, []);

  const loadVersionsWithNewCount = useCallback(async (browserStr: string) => {
    const browserName = getBrowserDisplayName(browserStr);

    try {
      // Get versions with new count info and cached detailed info
      const result = await invoke<BrowserVersionsResult>(
        "fetch_browser_versions_with_count_cached_first",
        { browserStr },
      );

      // Get detailed version info for compatibility
      const versionInfos = await invoke<BrowserVersionInfo[]>(
        "fetch_browser_versions_cached_first",
        { browserStr },
      );

      // Convert BrowserVersionInfo to GithubRelease format for compatibility
      const githubReleases: GithubRelease[] = versionInfos.map(
        (versionInfo) => ({
          tag_name: versionInfo.version,
          assets: [],
          published_at: versionInfo.date,
          is_nightly: false,
        }),
      );

      setAvailableVersions(githubReleases);

      // Show notification about new versions if any were found
      if (result.new_versions_count && result.new_versions_count > 0) {
        showSuccessToast(
          i18n.t("browserDownload.toast.foundNewVersions", {
            count: result.new_versions_count,
            browser: browserName,
          }),
          {
            duration: 3000,
            description: i18n.t(
              "browserDownload.toast.totalAvailableVersions",
              { count: result.total_versions_count },
            ),
          },
        );
      }

      return githubReleases;
    } catch (error) {
      console.error("Failed to load versions:", error);
      showErrorToast(
        i18n.t("browserDownload.toast.fetchVersionsFailed", {
          browser: browserName,
        }),
        {
          description:
            error instanceof Error
              ? error.message
              : i18n.t("common.errors.unknown"),
          duration: 4000,
        },
      );
      throw error;
    }
  }, []);

  const loadDownloadedVersions = useCallback(async (browserStr: string) => {
    try {
      const versions = await invoke<string[]>(
        "get_downloaded_browser_versions",
        { browserStr },
      );
      setDownloadedVersionsMap((prev) => ({ ...prev, [browserStr]: versions }));
      return versions;
    } catch (error) {
      console.error("Failed to load downloaded versions:", error);
      throw error;
    }
  }, []);

  const downloadBrowser = useCallback(
    async (
      browserStr: string,
      version: string,
      suppressNotifications = false,
    ) => {
      const browserName = getBrowserDisplayName(browserStr);
      setDownloadingBrowsers((prev) => new Set(prev).add(browserStr));

      try {
        // Check browser compatibility before attempting download
        const isSupported = await invoke<boolean>(
          "is_browser_supported_on_platform",
          { browserStr },
        );
        if (!isSupported) {
          const supportedBrowsers = await invoke<string[]>(
            "get_supported_browsers",
          );
          throw new Error(
            `${browserName} is not supported on your platform. Supported browsers: ${supportedBrowsers
              .map(getBrowserDisplayName)
              .join(", ")}`,
          );
        }

        await invoke("download_browser", { browserStr, version });
        await loadDownloadedVersions(browserStr);
      } catch (error) {
        console.error("Failed to download browser:", error);

        if (!suppressNotifications) {
          // Dismiss any existing download toast and show error
          dismissToast(`download-${browserStr}-${version}`);

          let errorMessage = i18n.t("common.errors.unknown");
          if (error instanceof Error) {
            errorMessage = error.message;
          } else if (typeof error === "string") {
            errorMessage = error;
          } else if (error && typeof error === "object" && "message" in error) {
            errorMessage = String(error.message);
          }

          // Ensure the long-running download toast is dismissed, and show a finite error toast
          dismissToast(`download-${browserStr}-${version}`);
          showErrorToast(
            i18n.t("browserDownload.toast.downloadFailed", {
              browser: browserName,
              version,
            }),
            {
              description: errorMessage,
              duration: 8000,
            },
          );
        }
        throw error;
      } finally {
        setDownloadingBrowsers((prev) => {
          const next = new Set(prev);
          next.delete(browserStr);
          return next;
        });
      }
    },
    [loadDownloadedVersions],
  );

  const isVersionDownloaded = useCallback(
    (version: string) => {
      return Object.values(downloadedVersionsMap).some((versions) =>
        versions.includes(version),
      );
    },
    [downloadedVersionsMap],
  );

  // Check if a browser type is currently downloading
  const isBrowserDownloading = useCallback(
    (browserStr: string) => {
      return downloadingBrowsers.has(browserStr);
    },
    [downloadingBrowsers],
  );

  // Legacy isDownloading for backwards compatibility
  const isDownloading = downloadingBrowsers.size > 0;

  // Listen for download progress events (browsers) and GeoIP progress events
  useEffect(() => {
    let unlistenBrowser: (() => void) | null = null;
    let unlistenGeoip: (() => void) | null = null;

    const setupListeners = async () => {
      try {
        // Browser binaries download progress
        unlistenBrowser = await listen<DownloadProgress>(
          "download-progress",
          async (event: TauriEvent<DownloadProgress>) => {
            const progress = event.payload;
            setDownloadProgress(progress);

            if (
              progress.stage === "downloading" ||
              progress.stage === "extracting" ||
              progress.stage === "verifying"
            ) {
              setDownloadingBrowsers((prev) => {
                if (prev.has(progress.browser)) return prev;
                return new Set(prev).add(progress.browser);
              });
            }

            const browserName = getBrowserDisplayName(progress.browser);

            if (progress.stage === "downloading") {
              const speedMBps = (
                progress.speed_bytes_per_sec /
                (1024 * 1024)
              ).toFixed(1);
              const etaText = progress.eta_seconds
                ? formatTime(progress.eta_seconds)
                : i18n.t("browserDownload.toast.calculating");

              const toastId = `download-${browserName.toLowerCase()}-${progress.version}`;
              // During first-run onboarding the welcome dialog shows browser
              // setup progress itself, so suppress the global download toast.
              if (!isOnboardingActive()) {
                showDownloadToast(
                  browserName,
                  progress.version,
                  "downloading",
                  {
                    percentage: progress.percentage,
                    speed: speedMBps,
                    eta: etaText,
                  },
                  {
                    onCancel: () => {
                      invoke("cancel_download", {
                        browserStr: progress.browser,
                        version: progress.version,
                      }).catch((err) => {
                        console.error("Failed to cancel download:", err);
                      });
                      dismissToast(toastId);
                    },
                  },
                );
              }
            } else if (progress.stage === "extracting") {
              if (!isOnboardingActive()) {
                showDownloadToast(
                  browserName,
                  progress.version,
                  "extracting",
                  progress.percentage > 0
                    ? { percentage: progress.percentage }
                    : undefined,
                );
              }
            } else if (progress.stage === "verifying") {
              if (!isOnboardingActive()) {
                showDownloadToast(browserName, progress.version, "verifying");
              }
            } else if (progress.stage === "cancelled") {
              setDownloadingBrowsers((prev) => {
                const next = new Set(prev);
                next.delete(progress.browser);
                return next;
              });
              dismissToast(
                `download-${browserName.toLowerCase()}-${progress.version}`,
              );
              setDownloadProgress(null);
            } else if (progress.stage === "error") {
              setDownloadingBrowsers((prev) => {
                const next = new Set(prev);
                next.delete(progress.browser);
                return next;
              });
              dismissToast(
                `download-${browserName.toLowerCase()}-${progress.version}`,
              );
              setDownloadProgress(null);
              // During first-run onboarding the welcome dialog surfaces a
              // concrete setup error itself, so suppress the global toast.
              if (!isOnboardingActive()) {
                showErrorToast(
                  i18n.t("browserDownload.toast.extractionFailed", {
                    browser: browserName,
                    version: progress.version,
                  }),
                  {
                    description: i18n.t(
                      "browserDownload.toast.extractionFailedDescription",
                    ),
                  },
                );
              }
            } else if (progress.stage === "completed") {
              setDownloadingBrowsers((prev) => {
                const next = new Set(prev);
                next.delete(progress.browser);
                return next;
              });
              try {
                await loadDownloadedVersions(progress.browser);
              } catch {
                /* empty */
              }
              if (!isOnboardingActive()) {
                showDownloadToast(browserName, progress.version, "completed");
              }
              setDownloadProgress(null);
            }
          },
        );
      } catch (error) {
        console.error("Failed to setup download progress listener:", error);
      }

      try {
        // GeoIP database download progress
        unlistenGeoip = await listen<{
          stage: string;
          percentage: number;
          message: string;
          downloaded_bytes?: number;
          total_bytes?: number;
          speed_bytes_per_sec?: number;
          eta_seconds?: number;
        }>(
          "geoip-download-progress",
          (
            event: TauriEvent<{
              stage: string;
              percentage: number;
              message: string;
              downloaded_bytes?: number;
              total_bytes?: number;
              speed_bytes_per_sec?: number;
              eta_seconds?: number;
            }>,
          ) => {
            const { stage, percentage, speed_bytes_per_sec, eta_seconds } =
              event.payload;
            if (stage === "downloading") {
              const speedMBps = speed_bytes_per_sec
                ? (speed_bytes_per_sec / (1024 * 1024)).toFixed(1)
                : undefined;
              const etaText = eta_seconds ? formatTime(eta_seconds) : undefined;
              showToast({
                id: "geoip-download",
                type: "download",
                title: i18n.t("browserDownload.toast.geoipDownloading"),
                stage: "downloading",
                progress: {
                  percentage,
                  ...(speedMBps ? { speed: speedMBps } : {}),
                  ...(etaText ? { eta: etaText } : {}),
                },
              });
            } else if (stage === "completed") {
              showToast({
                id: "geoip-download",
                type: "download",
                title: i18n.t("browserDownload.toast.geoipDownloaded"),
                stage: "completed",
              });
            }
          },
        );
      } catch (error) {
        console.error("Failed to setup GeoIP progress listener:", error);
      }
    };

    void setupListeners();

    return () => {
      if (unlistenBrowser) {
        try {
          unlistenBrowser();
        } catch (error) {
          console.error("Failed to cleanup browser download listener:", error);
        }
      }
      if (unlistenGeoip) {
        try {
          unlistenGeoip();
        } catch (error) {
          console.error("Failed to cleanup GeoIP progress listener:", error);
        }
      }
    };
  }, [formatTime, loadDownloadedVersions]);

  return {
    availableVersions,
    downloadedVersionsMap,
    isDownloading,
    isBrowserDownloading,
    downloadingBrowsers,
    downloadProgress,
    loadVersions,
    loadVersionsWithNewCount,
    loadDownloadedVersions,
    downloadBrowser,
    isVersionDownloaded,
    formatBytes,
    formatTime,
  };
}
