import "@/augmentations";
import { Platform } from "react-native";
import i18n from "@/i18n";
import { DownloadProvider } from "@/providers/DownloadProvider";
import { getOrSetDeviceId, getTokenFromStorage, JellyfinProvider, apiAtom } from "@/providers/JellyfinProvider";
import { JobQueueProvider } from "@/providers/JobQueueProvider";
import { PlaySettingsProvider } from "@/providers/PlaySettingsProvider";
import { WebSocketProvider } from "@/providers/WebSocketProvider";
import { Settings, useSettings } from "@/utils/atoms/settings";
import {
  BACKGROUND_FETCH_TASK,
  BACKGROUND_FETCH_TASK_SESSIONS,
  registerBackgroundFetchAsyncSessions,
} from "@/utils/background-tasks";
import {LogProvider, writeErrorLog, writeToLog} from "@/utils/log";
import { storage } from "@/utils/mmkv";
import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server";
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
const BackGroundDownloader = !Platform.isTV ? require("@kesha-antonov/react-native-background-downloader") : null;
import { DarkTheme, ThemeProvider } from "@react-navigation/native";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const BackgroundFetch = !Platform.isTV ? require("expo-background-fetch") : null;
import * as FileSystem from "expo-file-system";
const Notifications = !Platform.isTV ? require("expo-notifications") : null;
import { router, Stack, useSegments } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
const TaskManager = !Platform.isTV ? require("expo-task-manager") : null;
import { getLocales } from "expo-localization";
import { Provider as JotaiProvider } from "jotai";
import {useEffect, useRef, useState} from "react";
import { I18nextProvider } from "react-i18next";
import { Appearance, AppState } from "react-native";
import { SystemBars } from "react-native-edge-to-edge";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import "react-native-reanimated";
import { Toaster } from "sonner-native";
import { useAtom } from "jotai";
import { userAtom } from "@/providers/JellyfinProvider";
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
import { store } from "@/utils/store";
import {EventSubscription} from "expo-modules-core";
import {ExpoPushToken} from "expo-notifications/build/Tokens.types";
import {Notification, NotificationResponse} from "expo-notifications/build/Notifications.types";

if (!Platform.isTV) {
  Notifications.setNotificationHandler({
    handleNotification: async () => ({
      shouldShowAlert: true,
      shouldPlaySound: true,
      shouldSetBadge: false,
    }),
  });
}

// Keep the splash screen visible while we fetch resources
SplashScreen.preventAutoHideAsync();

// Set the animation options. This is optional.
SplashScreen.setOptions({
  duration: 500,
  fade: true,
});

function useNotificationObserver() {
  if (Platform.isTV) return;

  useEffect(() => {
    let isMounted = true;

    function redirect(notification: typeof Notifications.Notification) {
      const url = notification.request.content.data?.url;
      if (url) {
        router.push(url);
      }
    }

    Notifications.getLastNotificationResponseAsync().then((response: { notification: any }) => {
      if (!isMounted || !response?.notification) {
        return;
      }
      redirect(response?.notification);
    });

    const subscription = Notifications.addNotificationResponseReceivedListener((response: { notification: any }) => {
      redirect(response.notification);
    });

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

if (!Platform.isTV) {
  TaskManager.defineTask(BACKGROUND_FETCH_TASK_SESSIONS, async () => {
    console.log("TaskManager ~ sessions trigger");

    const api = store.get(apiAtom);
    if (api === null || api === undefined) return;

    const response = await getSessionApi(api).getSessions({
      activeWithinSeconds: 360,
    });

    const result = response.data.filter((s) => s.NowPlayingItem);
    Notifications.setBadgeCountAsync(result.length);

    return BackgroundFetch.BackgroundFetchResult.NewData;
  });

  TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
    console.log("TaskManager ~ trigger");

    const now = Date.now();

    const settingsData = storage.getString("settings");

    if (!settingsData) return BackgroundFetch.BackgroundFetchResult.NoData;

    const settings: Partial<Settings> = JSON.parse(settingsData);
    const url = settings?.optimizedVersionsServerUrl;

    if (!settings?.autoDownload || !url) return BackgroundFetch.BackgroundFetchResult.NoData;

    const token = getTokenFromStorage();
    const deviceId = getOrSetDeviceId();
    const baseDirectory = FileSystem.documentDirectory;

    if (!token || !deviceId || !baseDirectory) return BackgroundFetch.BackgroundFetchResult.NoData;

    const jobs = await getAllJobsByDeviceId({
      deviceId,
      authHeader: token,
      url,
    });

    console.log("TaskManager ~ Active jobs: ", jobs.length);

    for (let job of jobs) {
      if (job.status === "completed") {
        const downloadUrl = url + "download/" + job.id;
        const tasks = await BackGroundDownloader.checkForExistingDownloads();

        if (tasks.find((task: { id: string }) => task.id === job.id)) {
          console.log("TaskManager ~ Download already in progress: ", job.id);
          continue;
        }

        BackGroundDownloader.download({
          id: job.id,
          url: downloadUrl,
          destination: `${baseDirectory}${job.item.Id}.mp4`,
          headers: {
            Authorization: token,
          },
        })
          .begin(() => {
            console.log("TaskManager ~ Download started: ", job.id);
          })
          .done(() => {
            console.log("TaskManager ~ Download completed: ", job.id);
            saveDownloadedItemInfo(job.item);
            BackGroundDownloader.completeHandler(job.id);
            cancelJobById({
              authHeader: token,
              id: job.id,
              url: url,
            });
            Notifications.scheduleNotificationAsync({
              content: {
                title: job.item.Name,
                body: "Download completed",
                data: {
                  url: `/downloads`,
                },
              },
              trigger: null,
            });
          })
          .error((error: any) => {
            console.log("TaskManager ~ Download error: ", job.id, error);
            BackGroundDownloader.completeHandler(job.id);
            Notifications.scheduleNotificationAsync({
              content: {
                title: job.item.Name,
                body: "Download failed",
                data: {
                  url: `/downloads`,
                },
              },
              trigger: null,
            });
          });
      }
    }

    console.log(`Auto download started: ${new Date(now).toISOString()}`);

    // Be sure to return the successful result type!
    return BackgroundFetch.BackgroundFetchResult.NewData;
  });
}

const checkAndRequestPermissions = async () => {
  try {
    const hasAskedBefore = storage.getString("hasAskedForNotificationPermission");

    if (hasAskedBefore !== "true") {
      const { status } = await Notifications.requestPermissionsAsync();

      if (status === "granted") {
        writeToLog("INFO", "Notification permissions granted.");
        console.log("Notification permissions granted.");
      } else {
        writeToLog("ERROR", "Notification permissions denied.");
        console.log("Notification permissions denied.");
      }

      storage.set("hasAskedForNotificationPermission", "true");
    } else {
      console.log("Already asked for notification permissions before.");
    }
  } catch (error) {
    writeToLog("ERROR", "Error checking/requesting notification permissions:", error);
    console.error("Error checking/requesting notification permissions:", error);
  }
};

export default function RootLayout() {
  Appearance.setColorScheme("dark");

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <JotaiProvider>
        <ActionSheetProvider>
          <I18nextProvider i18n={i18n}>
            <Layout />
          </I18nextProvider>
        </ActionSheetProvider>
      </JotaiProvider>
    </GestureHandlerRootView>
  );
}

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 0,
      refetchOnMount: true,
      refetchOnReconnect: true,
      refetchOnWindowFocus: true,
      retryOnMount: true,
    },
  },
});

function Layout() {
  const [settings] = useSettings();
  const [user] = useAtom(userAtom);
  const [api] = useAtom(apiAtom);
  const appState = useRef(AppState.currentState);
  const segments = useSegments();

  useEffect(() => {
    i18n.changeLanguage(settings?.preferedLanguage ?? getLocales()[0].languageCode ?? "en");
  }, [settings?.preferedLanguage, i18n]);

  if (!Platform.isTV) {
    useNotificationObserver();

    const [expoPushToken, setExpoPushToken] = useState<ExpoPushToken>();
    const notificationListener = useRef<EventSubscription>();
    const responseListener = useRef<EventSubscription>();

    useEffect(() => {
      if (expoPushToken && api && user) {
        api?.post("/Streamyfin/device", {
          token: expoPushToken.data,
          deviceId: getOrSetDeviceId(),
          userId: user.Id
        }).then(_ => console.log("Posted expo push token"))
          .catch(_ => writeErrorLog("Failed to push expo push token to plugin"))
      }
      else console.log("No token available")
    }, [api, expoPushToken, user]);

    async function registerNotifications() {
      if (Platform.OS === 'android') {
        console.log("Setting android notification channel 'default'")
        await Notifications?.setNotificationChannelAsync('default', {
          name: 'default'
        });
      }

      await checkAndRequestPermissions();

      if (!Platform.isTV && user && user.Policy?.IsAdministrator) {
        await registerBackgroundFetchAsyncSessions();
      }

      Notifications?.getExpoPushTokenAsync()
        .then((token: ExpoPushToken) => token && setExpoPushToken(token))
        .catch((reason: any) => console.log("Failed to get token", reason));
    }

    useEffect(() => {
      registerNotifications()

      notificationListener.current = Notifications?.addNotificationReceivedListener((notification: Notification) => {
        console.log("Notification received while app running", notification);
      });

      responseListener.current = Notifications?.addNotificationResponseReceivedListener((response: NotificationResponse) => {
        console.log("Notification interacted with", response);
      });

      return () => {
        notificationListener.current &&
        Notifications?.removeNotificationSubscription(notificationListener.current);
        responseListener.current &&
        Notifications?.removeNotificationSubscription(responseListener.current);
      }
    }, []);

    useEffect(() => {
      if (Platform.isTV) return;
      if (segments.includes("direct-player" as never)) {
        return;
      }

      // If the user has auto rotate enabled, unlock the orientation
      if (settings.followDeviceOrientation === true) {
        ScreenOrientation.unlockAsync();
      } else {
        // If the user has auto rotate disabled, lock the orientation to portrait
        ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP);
      }
    }, [settings.followDeviceOrientation, segments]);

    useEffect(() => {
      const subscription = AppState.addEventListener("change", (nextAppState) => {
        if (appState.current.match(/inactive|background/) && nextAppState === "active") {
          BackGroundDownloader.checkForExistingDownloads();
        }
      });

      BackGroundDownloader.checkForExistingDownloads();

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

  return (
    <QueryClientProvider client={queryClient}>
      <JobQueueProvider>
        <JellyfinProvider>
          <PlaySettingsProvider>
            <LogProvider>
              <WebSocketProvider>
                <DownloadProvider>
                  <BottomSheetModalProvider>
                    <SystemBars style="light" hidden={false} />
                    <ThemeProvider value={DarkTheme}>
                      <Stack initialRouteName="(auth)/(tabs)">
                        <Stack.Screen
                          name="(auth)/(tabs)"
                          options={{
                            headerShown: false,
                            title: "",
                            header: () => null,
                          }}
                        />
                        <Stack.Screen
                          name="(auth)/player"
                          options={{
                            headerShown: false,
                            title: "",
                            header: () => null,
                          }}
                        />
                        <Stack.Screen
                          name="login"
                          options={{
                            headerShown: true,
                            title: "",
                            headerTransparent: true,
                          }}
                        />
                        <Stack.Screen name="+not-found" />
                      </Stack>
                      <Toaster
                        duration={4000}
                        toastOptions={{
                          style: {
                            backgroundColor: "#262626",
                            borderColor: "#363639",
                            borderWidth: 1,
                          },
                          titleStyle: {
                            color: "white",
                          },
                        }}
                        closeButton
                      />
                    </ThemeProvider>
                  </BottomSheetModalProvider>
                </DownloadProvider>
              </WebSocketProvider>
            </LogProvider>
          </PlaySettingsProvider>
        </JellyfinProvider>
      </JobQueueProvider>
    </QueryClientProvider>
  );
}

function saveDownloadedItemInfo(item: BaseItemDto) {
  try {
    const downloadedItems = storage.getString("downloadedItems");
    let items: BaseItemDto[] = downloadedItems ? JSON.parse(downloadedItems) : [];

    const existingItemIndex = items.findIndex((i) => i.Id === item.Id);
    if (existingItemIndex !== -1) {
      items[existingItemIndex] = item;
    } else {
      items.push(item);
    }

    storage.set("downloadedItems", JSON.stringify(items));
  } catch (error) {
    writeToLog("ERROR", "Failed to save downloaded item information:", error);
    console.error("Failed to save downloaded item information:", error);
  }
}
