import { App as CapacitorApp } from "@capacitor/app";
import { Capacitor } from "@capacitor/core";
import { Device } from "@capacitor/device";
import { Preferences } from "@capacitor/preferences";
import { PushNotifications, type Token } from "@capacitor/push-notifications";

type PushSubscriptionsResponse = {
  items: Array<{
    id: string;
    active: boolean;
    createdAt: string;
    updatedAt: string;
    endpoint?: string;
    platform?: string;
  }>;
  hasActiveSubscription: boolean;
};

type BeforeInstallPromptEvent = Event & {
  prompt: () => Promise<void>;
  userChoice: Promise<{ outcome: "accepted" | "dismissed"; platform: string }>;
};

type NotificationPermissionState = "default" | "denied" | "granted" | "prompt" | "unsupported";

const ANON_PUSH_CLIENT_ID_KEY = "anonymous-push-client-id";
const PENDING_PUSH_ROUTE_KEY = "pending-push-route";

export function usePush() {
  const { user, token } = useAuth();
  const config = useRuntimeConfig();
  const installPromptEvent = useState<BeforeInstallPromptEvent | null>("pwa-install-prompt-event", () => null);
  const anonymousClientId = useState<string | null>("anonymous-push-client-id", () => null);
  const nativePushToken = useState<string | null>("native-push-token", () => null);
  const nativePushDeviceId = useState<string | null>("native-push-device-id", () => null);
  const nativePushInitialized = useState<boolean>("native-push-initialized", () => false);
  const webPushClickInitialized = useState<boolean>("web-push-click-initialized", () => false);
  const pendingPushRoute = useState<string | null>("pending-push-route", () => null);

  const isNativeApp = () => process.client && Capacitor.isNativePlatform();

  const getNativePlatform = () => {
    if (!isNativeApp()) {
      return null;
    }

    const platform = Capacitor.getPlatform();
    if (platform === "android" || platform === "ios") {
      return platform;
    }

    return null;
  };

  const readStoredAnonymousClientId = async () => {
    if (!process.client) {
      return null;
    }

    if (isNativeApp()) {
      const result = await Preferences.get({ key: ANON_PUSH_CLIENT_ID_KEY });
      return result.value;
    }

    return localStorage.getItem(ANON_PUSH_CLIENT_ID_KEY);
  };

  const persistAnonymousClientId = async (value: string) => {
    if (!process.client) {
      return;
    }

    if (isNativeApp()) {
      await Preferences.set({ key: ANON_PUSH_CLIENT_ID_KEY, value });
      return;
    }

    localStorage.setItem(ANON_PUSH_CLIENT_ID_KEY, value);
  };

  const clearAnonymousClientId = async () => {
    if (!process.client) {
      return;
    }

    anonymousClientId.value = null;

    if (isNativeApp()) {
      await Preferences.remove({ key: ANON_PUSH_CLIENT_ID_KEY });
      return;
    }

    localStorage.removeItem(ANON_PUSH_CLIENT_ID_KEY);
  };

  const readStoredPendingPushRoute = async () => {
    if (!process.client) {
      return null;
    }

    if (isNativeApp()) {
      const result = await Preferences.get({ key: PENDING_PUSH_ROUTE_KEY });
      return result.value;
    }

    return localStorage.getItem(PENDING_PUSH_ROUTE_KEY);
  };

  const persistPendingPushRoute = async (value: string | null) => {
    if (!process.client) {
      return;
    }

    pendingPushRoute.value = value;

    if (isNativeApp()) {
      if (value) {
        await Preferences.set({ key: PENDING_PUSH_ROUTE_KEY, value });
      } else {
        await Preferences.remove({ key: PENDING_PUSH_ROUTE_KEY });
      }
      return;
    }

    if (value) {
      localStorage.setItem(PENDING_PUSH_ROUTE_KEY, value);
    } else {
      localStorage.removeItem(PENDING_PUSH_ROUTE_KEY);
    }
  };

  const normalizeTargetRoute = (targetUrl: unknown) => {
    if (typeof targetUrl !== "string" || targetUrl.length === 0) {
      return null;
    }

    if (targetUrl.startsWith("/")) {
      return targetUrl;
    }

    try {
      const parsed = new URL(targetUrl);
      return `${parsed.pathname}${parsed.search}${parsed.hash}` || "/";
    } catch {
      return null;
    }
  };

  const openPushTarget = async (targetUrl: unknown) => {
    const route = normalizeTargetRoute(targetUrl);
    if (!route) {
      return;
    }

    await persistPendingPushRoute(route);

    if (user.value) {
      await navigateTo(route);
      await persistPendingPushRoute(null);
      return;
    }

    await navigateTo(`/login?redirect=${encodeURIComponent(route)}`);
  };

  const consumePendingPushRoute = async () => {
    if (!process.client) {
      return null;
    }

    if (!pendingPushRoute.value) {
      pendingPushRoute.value = await readStoredPendingPushRoute();
    }

    const route = pendingPushRoute.value;
    if (!route || !user.value) {
      return null;
    }

    await navigateTo(route);
    await persistPendingPushRoute(null);
    return route;
  };

  const ensureAnonymousClientId = async () => {
    if (!process.client) {
      return "server-client";
    }

    if (!anonymousClientId.value) {
      anonymousClientId.value = await readStoredAnonymousClientId();
    }

    if (!anonymousClientId.value) {
      anonymousClientId.value = crypto.randomUUID().replace(/[^a-zA-Z0-9_-]/g, "");
      await persistAnonymousClientId(anonymousClientId.value);
    }

    return anonymousClientId.value;
  };

  const registerServiceWorker = async () => {
    if (!process.client || isNativeApp() || !("serviceWorker" in navigator)) {
      return null;
    }

    return navigator.serviceWorker.register("/sw.js");
  };

  const syncServiceWorkerContext = async () => {
    if (!process.client || isNativeApp() || !("serviceWorker" in navigator)) {
      return;
    }

    const registration = await registerServiceWorker();
    const readyRegistration = registration ?? (await navigator.serviceWorker.ready);
    const worker = readyRegistration.active ?? navigator.serviceWorker.controller;
    if (!worker) {
      return;
    }

    worker.postMessage({
      type: "push-context-sync",
      payload: {
        apiBase: config.public.apiBase,
        isAuthenticated: Boolean(user.value),
        anonymousClientId: await ensureAnonymousClientId()
      }
    });
  };

  const urlBase64ToUint8Array = (base64String: string) => {
    const normalizedInput = base64String.trim();
    if (!normalizedInput || normalizedInput.startsWith("replace_")) {
      throw new Error("Push-уведомления еще не настроены на сервере");
    }

    const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
    const base64 = (normalizedInput + padding).replace(/-/g, "+").replace(/_/g, "/");

    try {
      const rawData = window.atob(base64);
      return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
    } catch {
      throw new Error("На сервере задан некорректный VAPID public key");
    }
  };

  const isMobileDevice = () => {
    if (!process.client) {
      return false;
    }

    if (isNativeApp()) {
      return true;
    }

    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  };

  const isIosDevice = () => {
    if (!process.client) {
      return false;
    }

    if (isNativeApp()) {
      return Capacitor.getPlatform() === "ios";
    }

    return /iPhone|iPad|iPod/i.test(navigator.userAgent);
  };

  const isStandaloneMode = () => {
    if (!process.client) {
      return false;
    }

    if (isNativeApp()) {
      return true;
    }

    const standaloneMedia = window.matchMedia?.("(display-mode: standalone)")?.matches ?? false;
    const standaloneNavigator =
      "standalone" in navigator ? Boolean((navigator as Navigator & { standalone?: boolean }).standalone) : false;
    return standaloneMedia || standaloneNavigator;
  };

  const setInstallPromptEvent = (event: BeforeInstallPromptEvent | null) => {
    installPromptEvent.value = event;
  };

  const canTriggerInstallPrompt = () => !isNativeApp() && Boolean(installPromptEvent.value);

  const triggerInstallPrompt = async () => {
    if (!installPromptEvent.value) {
      return false;
    }

    await installPromptEvent.value.prompt();
    const choice = await installPromptEvent.value.userChoice;
    installPromptEvent.value = null;
    return choice.outcome === "accepted";
  };

  const getBrowserPermissionState = (): NotificationPermissionState => {
    if (!process.client || !("Notification" in window)) {
      return "unsupported";
    }

    return Notification.permission;
  };

  const getNativePermissionState = async (): Promise<NotificationPermissionState> => {
    if (!isNativeApp()) {
      return "unsupported";
    }

    const permissions = await PushNotifications.checkPermissions();
    if (permissions.receive === "prompt") {
      return "default";
    }

    return permissions.receive;
  };

  const getPermissionState = () => {
    if (isNativeApp()) {
      return getNativePermissionState();
    }

    return Promise.resolve(getBrowserPermissionState());
  };

  const requestBrowserPermission = async (): Promise<NotificationPermissionState> => {
    if (!process.client || !("Notification" in window)) {
      return "unsupported";
    }

    return Notification.requestPermission();
  };

  const requestNativePermission = async (): Promise<NotificationPermissionState> => {
    if (!isNativeApp()) {
      return "unsupported";
    }

    const permissions = await PushNotifications.requestPermissions();
    if (permissions.receive === "prompt") {
      return "default";
    }

    return permissions.receive;
  };

  const requestPermission = () => {
    if (isNativeApp()) {
      return requestNativePermission();
    }

    return requestBrowserPermission();
  };

  const syncWebSubscription = async (subscription: PushSubscription) => {
    const serializedSubscription = subscription.toJSON();
    if (!serializedSubscription.endpoint || !serializedSubscription.keys?.p256dh || !serializedSubscription.keys?.auth) {
      throw new Error("Браузер вернул неполную push-подписку");
    }

    if (user.value) {
      await useApi("/me/push-subscriptions", {
        method: "POST",
        body: serializedSubscription
      });
      return;
    }

    await useApi("/public/push-subscriptions", {
      method: "POST",
      body: {
        clientId: await ensureAnonymousClientId(),
        ...serializedSubscription
      }
    });
  };

  const syncNativeSubscription = async (tokenValue = nativePushToken.value) => {
    const platform = getNativePlatform();
    if (!platform || !tokenValue) {
      return;
    }

    if (!nativePushDeviceId.value) {
      const device = await Device.getId();
      nativePushDeviceId.value = device.identifier;
    }

    const body = {
      token: tokenValue,
      platform,
      deviceId: nativePushDeviceId.value ?? undefined
    };

    if (user.value) {
      await useApi("/me/native-push-subscriptions", {
        method: "POST",
        body
      });
      return;
    }

    await useApi("/public/native-push-subscriptions", {
      method: "POST",
      body: {
        clientId: await ensureAnonymousClientId(),
        ...body
      }
    });
  };

  const initializeNativePush = async () => {
    if (!isNativeApp() || nativePushInitialized.value) {
      return;
    }

    nativePushInitialized.value = true;

    await PushNotifications.createChannel({
      id: "signals",
      name: "Signals",
      description: "Уведомления о новых сигналах",
      importance: 5,
      visibility: 1,
      sound: "default"
    }).catch(() => undefined);

    await PushNotifications.addListener("registration", async (registrationToken: Token) => {
      nativePushToken.value = registrationToken.value;
      await syncNativeSubscription(registrationToken.value);
    });

    await PushNotifications.addListener("registrationError", (error) => {
      console.error("Native push registration failed", error);
    });

    await PushNotifications.addListener("pushNotificationActionPerformed", (notification) => {
      const targetUrl =
        notification.notification.data?.url ||
        notification.notification.data?.link ||
        notification.notification.data?.path;

      void openPushTarget(targetUrl);
    });

    await CapacitorApp.addListener("appUrlOpen", ({ url }) => {
      if (!url) {
        return;
      }

      try {
        const parsed = new URL(url);
        const route = `${parsed.pathname}${parsed.search}${parsed.hash}`;
        void openPushTarget(route || "/");
      } catch {
        // Ignore invalid deep links.
      }
    });
  };

  const initializeWebPushRouting = () => {
    if (!process.client || isNativeApp() || webPushClickInitialized.value || !("serviceWorker" in navigator)) {
      return;
    }

    webPushClickInitialized.value = true;

    navigator.serviceWorker.addEventListener("message", (event) => {
      if (event.data?.type !== "push-notification-click") {
        return;
      }

      void openPushTarget(event.data.url);
    });
  };

  const getCurrentBrowserSubscription = async () => {
    const registration = await registerServiceWorker();
    if (!registration || !("PushManager" in window)) {
      return null;
    }

    return registration.pushManager.getSubscription();
  };

  const claimAnonymousPushSubscriptions = async () => {
    if (!process.client || !user.value) {
      return;
    }

    const existingClientId = anonymousClientId.value ?? (await readStoredAnonymousClientId());
    if (!existingClientId) {
      await syncServiceWorkerContext();
      return;
    }

    anonymousClientId.value = existingClientId;

    if (isNativeApp()) {
      if (nativePushToken.value) {
        await syncNativeSubscription(nativePushToken.value);
      }

      const anonymousSubscriptions = await useApi<PushSubscriptionsResponse>(
        `/public/native-push-subscriptions/${existingClientId}`
      ).catch(() => null);

      for (const subscription of anonymousSubscriptions?.items ?? []) {
        await useApi(`/public/native-push-subscriptions/${existingClientId}/${subscription.id}`, {
          method: "DELETE"
        }).catch(() => undefined);
      }
    } else {
      const currentSubscription = await getCurrentBrowserSubscription();
      if (currentSubscription) {
        await syncWebSubscription(currentSubscription);
      }

      const anonymousSubscriptions = await useApi<PushSubscriptionsResponse>(
        `/public/push-subscriptions/${existingClientId}`
      ).catch(() => null);

      for (const subscription of anonymousSubscriptions?.items ?? []) {
        await useApi(`/public/push-subscriptions/${existingClientId}/${subscription.id}`, {
          method: "DELETE"
        }).catch(() => undefined);
      }
    }

    await clearAnonymousClientId();
    await syncServiceWorkerContext();
  };

  const getCurrentSubscription = async () => {
    if (isNativeApp()) {
      return nativePushToken.value;
    }

    return getCurrentBrowserSubscription();
  };

  const deactivateCurrentPushSubscription = async () => {
    if (!process.client || !user.value) {
      return;
    }

    if (isNativeApp()) {
      if (!nativePushToken.value) {
        return;
      }

      await useApi("/me/native-push-subscriptions/deactivate", {
        method: "POST",
        body: {
          token: nativePushToken.value
        }
      });

      return;
    }

    const currentSubscription = await getCurrentBrowserSubscription();
    if (!currentSubscription) {
      return;
    }

    const serializedSubscription = currentSubscription.toJSON();
    if (!serializedSubscription.endpoint) {
      return;
    }

    await useApi("/me/push-subscriptions/deactivate", {
      method: "POST",
      body: {
        endpoint: serializedSubscription.endpoint
      }
    });

    await currentSubscription.unsubscribe().catch(() => undefined);
  };

  const subscribeToBrowserPush = async () => {
    const registration = await registerServiceWorker();
    if (!registration || !("PushManager" in window)) {
      throw new Error("Push не поддерживается в этом браузере");
    }

    const vapid = await useApi<{ publicKey: string }>("/vapid-public-key");
    if (!vapid.publicKey || vapid.publicKey.startsWith("replace_")) {
      throw new Error("Push-уведомления еще не настроены на сервере");
    }

    const subscription = await registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(vapid.publicKey)
    });

    await syncWebSubscription(subscription);
    return subscription;
  };

  const subscribeToNativePush = async () => {
    await initializeNativePush();

    let permission = await getNativePermissionState();
    if (permission === "default") {
      permission = await requestNativePermission();
    }

    if (permission !== "granted") {
      throw new Error("Разрешение на уведомления не выдано");
    }

    const device = await Device.getId();
    nativePushDeviceId.value = device.identifier;

    return new Promise<string>((resolve, reject) => {
      let finished = false;

      const finalize = (callback: () => void) => {
        if (finished) {
          return;
        }

        finished = true;
        callback();
      };

      PushNotifications.addListener("registration", async (registrationToken: Token) => {
        finalize(() => {
          nativePushToken.value = registrationToken.value;
          void syncNativeSubscription(registrationToken.value);
          resolve(registrationToken.value);
        });
      }).catch(reject);

      PushNotifications.addListener("registrationError", (error) => {
        finalize(() => {
          reject(new Error(error.error || "Не удалось зарегистрировать устройство для push"));
        });
      }).catch(reject);

      PushNotifications.register().catch((error) => {
        finalize(() => {
          reject(error instanceof Error ? error : new Error("Не удалось зарегистрировать устройство для push"));
        });
      });
    });
  };

  const subscribeToPush = () => {
    if (isNativeApp()) {
      return subscribeToNativePush();
    }

    return subscribeToBrowserPush();
  };

  const ensurePushSubscription = async () => {
    const permission = await getPermissionState();
    if (permission === "unsupported") {
      throw new Error("Push не поддерживается на этом устройстве");
    }

    let finalPermission = permission;
    if (finalPermission === "default") {
      finalPermission = await requestPermission();
    }

    if (finalPermission !== "granted") {
      throw new Error("Разрешение на уведомления не выдано");
    }

    if (isNativeApp()) {
      if (nativePushToken.value) {
        await syncNativeSubscription(nativePushToken.value);
        return nativePushToken.value;
      }

      return subscribeToNativePush();
    }

    const currentSubscription = await getCurrentBrowserSubscription();
    if (currentSubscription) {
      await syncWebSubscription(currentSubscription);
      return currentSubscription;
    }

    return subscribeToBrowserPush();
  };

  const getPushStatus = async () => {
    const permission = await getPermissionState();
    const isMobile = isMobileDevice();
    const isIos = isIosDevice();
    const isStandalone = isStandaloneMode();
    const installRequired = !isNativeApp() && isMobile && !isStandalone;

    if (permission === "unsupported") {
      return {
        supported: false,
        permission,
        isMobile,
        isIos,
        isStandalone,
        installRequired,
        canInstall: canTriggerInstallPrompt(),
        hasBrowserSubscription: false,
        hasServerSubscription: false
      };
    }

    let hasServerSubscription = false;

    try {
      if (isNativeApp()) {
        if (user.value) {
          const serverSubscriptions = await useApi<PushSubscriptionsResponse>("/me/native-push-subscriptions");
          hasServerSubscription = serverSubscriptions.hasActiveSubscription;
        } else {
          const clientId = await ensureAnonymousClientId();
          const serverSubscriptions = await useApi<PushSubscriptionsResponse>(`/public/native-push-subscriptions/${clientId}`);
          hasServerSubscription = serverSubscriptions.hasActiveSubscription;
        }
      } else if (user.value) {
        const serverSubscriptions = await useApi<PushSubscriptionsResponse>("/me/push-subscriptions");
        hasServerSubscription = serverSubscriptions.hasActiveSubscription;
      } else {
        const clientId = await ensureAnonymousClientId();
        const serverSubscriptions = await useApi<PushSubscriptionsResponse>(`/public/push-subscriptions/${clientId}`);
        hasServerSubscription = serverSubscriptions.hasActiveSubscription;
      }
    } catch {
      hasServerSubscription = false;
    }

    return {
      supported: true,
      permission,
      isMobile,
      isIos,
      isStandalone,
      installRequired,
      canInstall: canTriggerInstallPrompt(),
      hasBrowserSubscription: isNativeApp()
        ? Boolean(nativePushToken.value)
        : Boolean(await getCurrentBrowserSubscription()),
      hasServerSubscription
    };
  };

  return {
    registerServiceWorker,
    syncServiceWorkerContext,
    syncNativeSubscription,
    initializeNativePush,
    initializeWebPushRouting,
    isNativeApp,
    isMobileDevice,
    isIosDevice,
    isStandaloneMode,
    getPermissionState,
    requestPermission,
    getCurrentSubscription,
    deactivateCurrentPushSubscription,
    getPushStatus,
    subscribeToPush,
    ensurePushSubscription,
    claimAnonymousPushSubscriptions,
    consumePendingPushRoute,
    setInstallPromptEvent,
    canTriggerInstallPrompt,
    triggerInstallPrompt,
    ensureAnonymousClientId
  };
}
