import "@/augmentations";
import { type Api, Jellyfin } from "@jellyfin/sdk";
import type { UserDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getUserApi } from "@jellyfin/sdk/lib/utils/api";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import axios, { AxiosError } from "axios";
import { useSegments } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { atom, useAtom } from "jotai";
import type React from "react";
import {
  createContext,
  type ReactNode,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";
import { useTranslation } from "react-i18next";
import { AppState, Platform } from "react-native";
import { getDeviceNameSync } from "react-native-device-info";
import uuid from "react-native-uuid";
import useRouter from "@/hooks/useAppRouter";
import { useInterval } from "@/hooks/useInterval";
import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
import { useSettings } from "@/utils/atoms/settings";
import { writeErrorLog, writeInfoLog } from "@/utils/log";
import { storage } from "@/utils/mmkv";
import {
  type AccountSecurityType,
  addAccountToServer,
  addServerToList,
  deleteAccountCredential,
  getAccountCredential,
  hashPIN,
  migrateToMultiAccount,
  saveAccountCredential,
  updateAccountToken,
} from "@/utils/secureCredentials";
import { store } from "@/utils/store";
import { clearTVDiscoverySafely } from "@/utils/tvDiscovery/sync";
import { APP_VERSION } from "@/utils/version";

interface Server {
  address: string;
}

const initialApi = (() => {
  try {
    const token = storage.getString("token") || null;
    const serverUrl = storage.getString("serverUrl") || null;
    if (serverUrl && token) {
      const id = getOrSetDeviceId();
      const deviceName = getDeviceNameSync();
      const jellyfinInstance = new Jellyfin({
        clientInfo: { name: "Streamyfin", version: APP_VERSION },
        deviceInfo: {
          name: deviceName,
          id,
        },
      });
      return jellyfinInstance.createApi(serverUrl, token);
    }
  } catch (e) {
    console.error("Failed to initialize API synchronously:", e);
  }
  return null;
})();

const initialUser = (() => {
  try {
    // Only return a stored user if we also have a token. Otherwise the
    // user atom would be populated while the api atom is null (e.g. after
    // a logout that left stale user JSON in storage), which causes
    // useProtectedRoute to keep us inside the (auth) group instead of
    // redirecting to /login.
    const token = storage.getString("token");
    if (!token) return null;
    const userStr = storage.getString("user");
    if (userStr) {
      return JSON.parse(userStr) as UserDto;
    }
  } catch (e) {
    console.error("Failed to parse initial user synchronously:", e);
  }
  return null;
})();

export const apiAtom = atom<Api | null>(initialApi);
export const userAtom = atom<UserDto | null>(initialUser);
export const wsAtom = atom<WebSocket | null>(null);
export const cacheVersionAtom = atom<number>(0);

interface LoginOptions {
  saveAccount?: boolean;
  securityType?: AccountSecurityType;
  pinCode?: string;
}

interface JellyfinContextValue {
  discoverServers: (url: string) => Promise<Server[]>;
  setServer: (server: Server) => Promise<void>;
  removeServer: () => void;
  login: (
    username: string,
    password: string,
    serverName?: string,
    options?: LoginOptions,
  ) => Promise<void>;
  logout: () => Promise<void>;
  initiateQuickConnect: () => Promise<string | undefined>;
  stopQuickConnectPolling: () => void;
  loginWithSavedCredential: (
    serverUrl: string,
    userId: string,
  ) => Promise<void>;
  loginWithPassword: (
    serverUrl: string,
    username: string,
    password: string,
  ) => Promise<void>;
  removeSavedCredential: (serverUrl: string, userId: string) => Promise<void>;
  switchServerUrl: (newUrl: string) => void;
}

const JellyfinContext = createContext<JellyfinContextValue | undefined>(
  undefined,
);

export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
  children,
}) => {
  const [jellyfin] = useState<Jellyfin | undefined>(() => {
    try {
      const id = getOrSetDeviceId();
      const deviceName = getDeviceNameSync();
      return new Jellyfin({
        clientInfo: { name: "Streamyfin", version: APP_VERSION },
        deviceInfo: {
          name: deviceName,
          id,
        },
      });
    } catch (e) {
      console.error("Failed to initialize Jellyfin synchronously in state:", e);
      return undefined;
    }
  });
  const [deviceId] = useState<string | undefined>(() => {
    try {
      return getOrSetDeviceId();
    } catch {
      return undefined;
    }
  });

  const { t } = useTranslation();

  const [api, setApi] = useAtom(apiAtom);
  const [user, setUser] = useAtom(userAtom);
  const [isPolling, setIsPolling] = useState<boolean>(false);
  const [secret, setSecret] = useState<string | null>(null);
  const { setPluginSettings, refreshStreamyfinPluginSettings } = useSettings();
  const { clearAllJellyseerData, setJellyseerrUser } = useJellyseerr();
  const queryClient = useQueryClient();

  const headers = useMemo(() => {
    if (!deviceId) return {};
    return {
      authorization: `MediaBrowser Client="Streamyfin", Device=${
        Platform.OS === "android" ? "Android" : "iOS"
      }, DeviceId="${deviceId}", Version="${APP_VERSION}"`,
    };
  }, [deviceId]);

  const initiateQuickConnect = useCallback(async () => {
    if (!api || !deviceId) return;
    try {
      const response = await api.axiosInstance.post(
        `${api.basePath}/QuickConnect/Initiate`,
        null,
        {
          headers,
        },
      );
      if (response?.status === 200) {
        setSecret(response?.data?.Secret);
        setIsPolling(true);
        return response.data?.Code;
      }
      throw new Error("Failed to initiate quick connect");
    } catch (error) {
      console.error(error);
      throw error;
    }
  }, [api, deviceId, headers]);

  const stopQuickConnectPolling = useCallback(() => {
    setIsPolling(false);
    setSecret(null);
  }, []);

  const pollQuickConnect = useCallback(async () => {
    if (!api || !secret || !jellyfin) return;

    try {
      const response = await api.axiosInstance.get(
        `${api.basePath}/QuickConnect/Connect?Secret=${secret}`,
      );

      if (response.status === 200) {
        if (response.data.Authenticated) {
          setIsPolling(false);

          const authResponse = await api.axiosInstance.post(
            `${api.basePath}/Users/AuthenticateWithQuickConnect`,
            {
              secret,
            },
            {
              headers,
            },
          );

          const { AccessToken, User } = authResponse.data;
          setUser(User);
          setApi(jellyfin.createApi(api.basePath, AccessToken));
          storage.set("token", AccessToken);
          storage.set("user", JSON.stringify(User));
          return true;
        }
      }
      return false;
    } catch (error) {
      if (error instanceof AxiosError) {
        if (error.response?.status === 400 || error.response?.status === 404) {
          setIsPolling(false);
          setSecret(null);
          if (error.response?.status === 400) {
            throw new Error("The code has expired. Please try again.");
          }
          return false;
        }
      }
      console.error("Error polling Quick Connect:", error);
      throw error;
    }
  }, [api, secret, headers, jellyfin]);

  useEffect(() => {
    (async () => {
      await refreshStreamyfinPluginSettings();
    })();
  }, []);

  useEffect(() => {
    store.set(apiAtom, api);
  }, [api]);

  useInterval(pollQuickConnect, isPolling ? 1000 : null);

  // Refresh plugin settings when app comes to foreground
  useEffect(() => {
    const subscription = AppState.addEventListener("change", (nextAppState) => {
      if (nextAppState === "active") {
        refreshStreamyfinPluginSettings();
      }
    });

    return () => subscription.remove();
  }, []);

  const discoverServers = async (url: string): Promise<Server[]> => {
    const servers =
      await jellyfin?.discovery.getRecommendedServerCandidates(url);
    return servers?.map((server) => ({ address: server.address })) || [];
  };

  const setServerMutation = useMutation({
    mutationFn: async (server: Server) => {
      clearTVDiscoverySafely();
      const apiInstance = jellyfin?.createApi(server.address);

      if (!apiInstance?.basePath) throw new Error("Failed to connect");

      setApi(apiInstance);
      storage.set("serverUrl", server.address);
    },
    onSuccess: async (_, server) => {
      // Add server to the list (will update existing or add new)
      addServerToList(server.address);
    },
    onError: (error) => {
      console.error("Failed to set server:", error);
    },
  });

  const removeServerMutation = useMutation({
    mutationFn: async () => {
      clearTVDiscoverySafely();
      storage.remove("serverUrl");
      setApi(null);
    },
    onError: (error) => {
      console.error("Failed to remove server:", error);
    },
  });

  const loginMutation = useMutation({
    mutationFn: async ({
      username,
      password,
      serverName,
      options,
    }: {
      username: string;
      password: string;
      serverName?: string;
      options?: LoginOptions;
    }) => {
      if (!api || !jellyfin) throw new Error("API not initialized");

      try {
        const auth = await api.authenticateUserByName(username, password);

        if (auth.data.AccessToken && auth.data.User) {
          setUser(auth.data.User);
          storage.set("user", JSON.stringify(auth.data.User));
          setApi(jellyfin.createApi(api?.basePath, auth.data?.AccessToken));
          storage.set("token", auth.data?.AccessToken);

          // Save credentials to secure storage if requested
          if (api.basePath && options?.saveAccount) {
            const securityType = options.securityType || "none";
            let pinHash: string | undefined;

            if (securityType === "pin" && options.pinCode) {
              pinHash = await hashPIN(options.pinCode);
            }

            await saveAccountCredential({
              serverUrl: api.basePath,
              serverName: serverName || "",
              token: auth.data.AccessToken,
              userId: auth.data.User.Id || "",
              username,
              savedAt: Date.now(),
              securityType,
              pinHash,
              primaryImageTag: auth.data.User.PrimaryImageTag ?? undefined,
            });
          }

          const recentPluginSettings = await refreshStreamyfinPluginSettings();
          if (recentPluginSettings?.jellyseerrServerUrl?.value) {
            const jellyseerrApi = new JellyseerrApi(
              recentPluginSettings.jellyseerrServerUrl.value,
            );
            await jellyseerrApi.test().then((result) => {
              if (result.isValid && result.requiresPass) {
                jellyseerrApi.login(username, password).then(setJellyseerrUser);
              }
            });
          }
        }
      } catch (error) {
        if (axios.isAxiosError(error)) {
          switch (error.response?.status) {
            case 401:
              throw new Error(t("login.invalid_username_or_password"));
            case 403:
              throw new Error(
                t("login.user_does_not_have_permission_to_log_in"),
              );
            case 408:
              throw new Error(
                t("login.server_is_taking_too_long_to_respond_try_again_later"),
              );
            case 429:
              throw new Error(
                t("login.server_received_too_many_requests_try_again_later"),
              );
            case 500:
              throw new Error(t("login.there_is_a_server_error"));
            default:
              throw new Error(
                t(
                  "login.an_unexpected_error_occurred_did_you_enter_the_correct_url",
                ),
              );
          }
        }
        throw error;
      }
    },
    onError: (error) => {
      console.error("Login failed:", error);
    },
  });

  const logoutMutation = useMutation({
    mutationFn: async () => {
      // Fire-and-forget: don't block logout on server cleanup
      api
        ?.delete(`/Streamyfin/device/${deviceId}`)
        .then((_r) => writeInfoLog("Deleted expo push token for device"))
        .catch((_e) =>
          writeErrorLog("Failed to delete expo push token for device"),
        );

      storage.remove("token");
      storage.remove("user");
      clearTVDiscoverySafely();
      setUser(null);
      setApi(null);
      setPluginSettings(undefined);
      await clearAllJellyseerData();

      // Clear React Query cache to prevent data from previous account lingering
      queryClient.clear();
      storage.remove("REACT_QUERY_OFFLINE_CACHE");

      // Note: We keep saved credentials for quick switching back
    },
    onError: (error) => {
      console.error("Logout failed:", error);
    },
  });

  const loginWithSavedCredentialMutation = useMutation({
    mutationFn: async ({
      serverUrl,
      userId,
    }: {
      serverUrl: string;
      userId: string;
    }) => {
      if (!jellyfin) throw new Error("Jellyfin not initialized");

      const credential = await getAccountCredential(serverUrl, userId);
      if (!credential) {
        throw new Error("No saved credential found");
      }

      // Create API instance with saved token
      const apiInstance = jellyfin.createApi(serverUrl, credential.token);
      if (!apiInstance) {
        throw new Error("Failed to create API instance");
      }

      // Validate token by fetching current user
      try {
        const response = await getUserApi(apiInstance).getCurrentUser();

        // Clear React Query cache to prevent data from previous account lingering
        queryClient.clear();
        storage.remove("REACT_QUERY_OFFLINE_CACHE");

        // Token is valid, update state
        setApi(apiInstance);
        setUser(response.data);
        storage.set("serverUrl", serverUrl);
        storage.set("token", credential.token);
        storage.set("user", JSON.stringify(response.data));

        // Update account info (in case user changed their avatar)
        if (response.data.PrimaryImageTag !== credential.primaryImageTag) {
          addAccountToServer(serverUrl, credential.serverName, {
            userId: credential.userId,
            username: credential.username,
            securityType: credential.securityType,
            savedAt: credential.savedAt,
            primaryImageTag: response.data.PrimaryImageTag ?? undefined,
          });
        }

        // Refresh plugin settings
        await refreshStreamyfinPluginSettings();
      } catch (error) {
        // Check for axios error
        if (axios.isAxiosError(error)) {
          // Token is invalid/expired - remove it
          if (
            error.response?.status === 401 ||
            error.response?.status === 403
          ) {
            await deleteAccountCredential(serverUrl, userId);
            throw new Error(t("server.session_expired"));
          }

          // Network error - server not reachable (no response means server didn't respond)
          if (!error.response) {
            throw new Error(t("home.server_unreachable"));
          }
        }

        // Check for network error by message pattern (fallback detection)
        if (
          error instanceof Error &&
          (error.message.toLowerCase().includes("network") ||
            error.message.toLowerCase().includes("econnrefused") ||
            error.message.toLowerCase().includes("timeout"))
        ) {
          throw new Error(t("home.server_unreachable"));
        }

        throw error;
      }
    },
    onError: (error) => {
      console.error("Quick login failed:", error);
    },
  });

  const loginWithPasswordMutation = useMutation({
    mutationFn: async ({
      serverUrl,
      username,
      password,
    }: {
      serverUrl: string;
      username: string;
      password: string;
    }) => {
      if (!jellyfin) throw new Error("Jellyfin not initialized");

      // Create API instance for the server
      const apiInstance = jellyfin.createApi(serverUrl);
      if (!apiInstance) {
        throw new Error("Failed to create API instance");
      }

      // Authenticate with password
      const auth = await apiInstance.authenticateUserByName(username, password);

      if (auth.data.AccessToken && auth.data.User) {
        // Clear React Query cache to prevent data from previous account lingering
        queryClient.clear();
        storage.remove("REACT_QUERY_OFFLINE_CACHE");

        setUser(auth.data.User);
        storage.set("user", JSON.stringify(auth.data.User));
        setApi(jellyfin.createApi(serverUrl, auth.data.AccessToken));
        storage.set("serverUrl", serverUrl);
        storage.set("token", auth.data.AccessToken);

        // Update the saved credential with new token and image tag
        await updateAccountToken(
          serverUrl,
          auth.data.User.Id || "",
          auth.data.AccessToken,
          auth.data.User.PrimaryImageTag ?? undefined,
        );

        // Refresh plugin settings
        await refreshStreamyfinPluginSettings();
      }
    },
    onError: (error) => {
      console.error("Password login failed:", error);
      throw error;
    },
  });

  const removeSavedCredentialMutation = useMutation({
    mutationFn: async ({
      serverUrl,
      userId,
    }: {
      serverUrl: string;
      userId: string;
    }) => {
      await deleteAccountCredential(serverUrl, userId);
    },
    onError: (error) => {
      console.error("Failed to remove saved credential:", error);
    },
  });

  const switchServerUrl = useCallback(
    (newUrl: string) => {
      if (!jellyfin || !api?.accessToken) return;

      clearTVDiscoverySafely();
      const newApi = jellyfin.createApi(newUrl, api.accessToken);
      setApi(newApi);
      // Note: We don't update storage.set("serverUrl") here
      // because we want to keep the original remote URL as the "primary" URL
    },
    [jellyfin, api?.accessToken],
  );

  const [loaded, setLoaded] = useState(false);
  const [initialLoaded, setInitialLoaded] = useState(false);

  useEffect(() => {
    if (initialLoaded) {
      setLoaded(true);
    }
  }, [initialLoaded]);

  useEffect(() => {
    const initializeJellyfin = async () => {
      if (!jellyfin) return;

      try {
        // Run migration to multi-account format (once)
        await migrateToMultiAccount();

        const token = getTokenFromStorage();
        const serverUrl = getServerUrlFromStorage();
        const storedUser = getUserFromStorage();

        if (serverUrl && token) {
          const apiInstance = jellyfin.createApi(serverUrl, token);
          setApi(apiInstance);

          if (storedUser?.Id) {
            setUser(storedUser);
          }

          // Dismiss splash screen with cached data immediately,
          // fetch fresh user data in the background
          setInitialLoaded(true);

          try {
            const response = await getUserApi(apiInstance).getCurrentUser();
            setUser(response.data);

            // Migrate current session to secure storage if not already saved
            if (storedUser?.Id && storedUser?.Name) {
              const existingCredential = await getAccountCredential(
                serverUrl,
                storedUser.Id,
              );
              if (!existingCredential) {
                await saveAccountCredential({
                  serverUrl,
                  serverName: "",
                  token,
                  userId: storedUser.Id,
                  username: storedUser.Name,
                  savedAt: Date.now(),
                  securityType: "none",
                  primaryImageTag: response.data.PrimaryImageTag ?? undefined,
                });
              } else if (
                response.data.PrimaryImageTag !==
                existingCredential.primaryImageTag
              ) {
                // Update image tag if it has changed
                addAccountToServer(serverUrl, existingCredential.serverName, {
                  userId: existingCredential.userId,
                  username: existingCredential.username,
                  securityType: existingCredential.securityType,
                  savedAt: existingCredential.savedAt,
                  primaryImageTag: response.data.PrimaryImageTag ?? undefined,
                });
              }
            }
          } catch (e) {
            // Background fetch failed — app already rendered with cached data
            console.warn("Background user fetch failed, using cached data:", e);
          }
        } else {
          setInitialLoaded(true);
        }
      } catch (e) {
        console.error(e);
        setInitialLoaded(true);
      }
    };

    initializeJellyfin();
  }, [jellyfin]);

  const contextValue: JellyfinContextValue = {
    discoverServers,
    setServer: (server) => setServerMutation.mutateAsync(server),
    removeServer: () => removeServerMutation.mutateAsync(),
    login: (username, password, serverName, options) =>
      loginMutation.mutateAsync({ username, password, serverName, options }),
    logout: () => logoutMutation.mutateAsync(),
    initiateQuickConnect,
    stopQuickConnectPolling,
    loginWithSavedCredential: (serverUrl, userId) =>
      loginWithSavedCredentialMutation.mutateAsync({ serverUrl, userId }),
    loginWithPassword: (serverUrl, username, password) =>
      loginWithPasswordMutation.mutateAsync({ serverUrl, username, password }),
    removeSavedCredential: (serverUrl, userId) =>
      removeSavedCredentialMutation.mutateAsync({ serverUrl, userId }),
    switchServerUrl,
  };

  useEffect(() => {
    if (loaded) {
      SplashScreen.hideAsync();
    }
  }, [loaded]);

  useProtectedRoute(user, loaded);

  return (
    <JellyfinContext.Provider value={contextValue}>
      {children}
    </JellyfinContext.Provider>
  );
};

export const useJellyfin = (): JellyfinContextValue => {
  const context = useContext(JellyfinContext);
  if (!context)
    throw new Error("useJellyfin must be used within a JellyfinProvider");
  return context;
};

function useProtectedRoute(user: UserDto | null, loaded = false) {
  const segments = useSegments();
  const router = useRouter();

  useEffect(() => {
    if (loaded === false) return;

    const inAuthGroup = segments.length > 1 && segments[0] === "(auth)";
    const isTopShelfLaunchRoute = segments[0] === "topshelf";

    if (!user?.Id && inAuthGroup) {
      router.replace("/login");
    } else if (user?.Id && !inAuthGroup && !isTopShelfLaunchRoute) {
      router.replace("/(auth)/(tabs)/(home)/");
    }
  }, [user, segments, loaded]);
}

export function getTokenFromStorage(): string | null {
  return storage.getString("token") || null;
}

export function getUserFromStorage(): UserDto | null {
  const userStr = storage.getString("user");
  if (userStr) {
    try {
      return JSON.parse(userStr) as UserDto;
    } catch (e) {
      console.error(e);
    }
  }
  return null;
}

export function getServerUrlFromStorage(): string | null {
  return storage.getString("serverUrl") || null;
}

export function getOrSetDeviceId(): string {
  let deviceId = storage.getString("deviceId");

  if (!deviceId) {
    deviceId = uuid.v4() as string;
    storage.set("deviceId", deviceId);
  }

  return deviceId;
}
