export type AppUpdateStatus = 'idle' | 'available' | 'updating' | 'ready';

export type AppUpdateSnapshot = {
  supported: boolean;
  status: AppUpdateStatus;
  progressPercent: number | null;
  currentTaskLabel: string | null;
};

type Listener = (snapshot: AppUpdateSnapshot) => void;
type ServiceWorkerUpdater = (reloadPage?: boolean) => Promise<void>;

const UPDATE_APPLY_TIMEOUT_MS = 15000;
const UPDATE_DISCOVERY_TIMEOUT_MS = 6000;
const REGISTRATION_LOOKUP_TIMEOUT_MS = 10000;
const DEV_UPDATE_POLL_INTERVAL_MS = 4000;
const DEV_UPDATE_ENDPOINT_PATH = '/__app-update';
const APP_UPDATE_CACHE_BUST_PARAM = '__appUpdatedAt';

let initialized = false;
let snapshot: AppUpdateSnapshot = {
  supported: false,
  status: 'idle',
  progressPercent: null,
  currentTaskLabel: null,
};
const listeners = new Set<Listener>();
let devUpdateToken: string | null = null;
let devUpdatePollTimerId: number | null = null;
let serviceWorkerUpdater: ServiceWorkerUpdater | null = null;

function isAppUpdateSupported() {
  return typeof window !== 'undefined' && typeof navigator !== 'undefined' && 'serviceWorker' in navigator;
}

function isAppUpdateDisabled() {
  return import.meta.env.VITE_DISABLE_APP_UPDATE === 'true';
}

function isDevelopmentUpdateMode() {
  return import.meta.env.DEV;
}

function emit() {
  for (const listener of listeners) {
    listener(snapshot);
  }
}

function buildAppUpdateReloadUrl() {
  const targetUrl = new URL(window.location.href);
  targetUrl.searchParams.set(APP_UPDATE_CACHE_BUST_PARAM, `${Date.now()}`);
  return targetUrl.toString();
}

function reloadAppWithCacheBuster() {
  window.location.replace(buildAppUpdateReloadUrl());
}

async function resetServiceWorkersAndReload() {
  if (!isAppUpdateSupported()) {
    reloadAppWithCacheBuster();
    return;
  }

  try {
    const registrations = await navigator.serviceWorker.getRegistrations();
    await Promise.all(
      registrations.map((registration) =>
        registration.unregister().catch(() => false),
      ),
    );
  } catch {
    // ignore and continue reload
  }

  if (typeof window !== 'undefined' && 'caches' in window) {
    try {
      const cacheKeys = await caches.keys();
      await Promise.all(cacheKeys.map((cacheKey) => caches.delete(cacheKey).catch(() => false)));
    } catch {
      // ignore and continue reload
    }
  }

  reloadAppWithCacheBuster();
}

export async function forceReloadApp() {
  await resetServiceWorkersAndReload();
}

async function unregisterServiceWorkers() {
  if (!isAppUpdateSupported()) {
    return;
  }

  try {
    const registrations = await navigator.serviceWorker.getRegistrations();
    await Promise.all(registrations.map((registration) => registration.unregister().catch(() => false)));
  } catch {
    // ignore cleanup failure
  }
}

async function fetchDevelopmentUpdateToken() {
  const response = await fetch(DEV_UPDATE_ENDPOINT_PATH, {
    cache: 'no-store',
    headers: {
      Accept: 'application/json',
    },
  });

  if (!response.ok) {
    throw new Error('개발 업데이트 상태를 확인하지 못했습니다.');
  }

  const payload = (await response.json()) as { token?: string };
  return typeof payload.token === 'string' && payload.token ? payload.token : null;
}

async function pollDevelopmentUpdateStatus() {
  try {
    const nextToken = await fetchDevelopmentUpdateToken();

    if (!nextToken) {
      return;
    }

    if (!devUpdateToken) {
      devUpdateToken = nextToken;
      snapshot = {
        supported: true,
        status: 'ready',
        progressPercent: null,
        currentTaskLabel: null,
      };
      emit();
      return;
    }

    if (nextToken !== devUpdateToken && snapshot.status !== 'available' && snapshot.status !== 'updating') {
      snapshot = {
        supported: true,
        status: 'available',
        progressPercent: null,
        currentTaskLabel: '개발 서버의 변경 사항이 준비되었습니다. 설정의 업데이트 버튼으로 반영할 수 있습니다.',
      };
      emit();
    }
  } catch {
    if (snapshot.status === 'idle') {
      snapshot = {
        supported: true,
        status: 'ready',
        progressPercent: null,
        currentTaskLabel: null,
      };
      emit();
    }
  }
}

function startDevelopmentUpdatePolling() {
  void pollDevelopmentUpdateStatus();

  if (devUpdatePollTimerId !== null) {
    window.clearInterval(devUpdatePollTimerId);
  }

  devUpdatePollTimerId = window.setInterval(() => {
    void pollDevelopmentUpdateStatus();
  }, DEV_UPDATE_POLL_INTERVAL_MS);
}

async function getAppServiceWorkerRegistration() {
  if (!isAppUpdateSupported()) {
    return null;
  }

  const serviceWorkerUrl = import.meta.env.DEV ? '/dev-sw.js?dev-sw' : '/sw.js';
  let registeredRegistration: ServiceWorkerRegistration | null = null;

  try {
    registeredRegistration = await navigator.serviceWorker.register(
      serviceWorkerUrl,
      import.meta.env.DEV ? { type: 'module' } : undefined,
    );
  } catch {
    // 기존 등록이 있거나 설치 중 충돌이 있어도 아래 조회 흐름으로 이어갑니다.
  }

  if (registeredRegistration) {
    return registeredRegistration;
  }

  const startedAt = Date.now();

  while (Date.now() - startedAt < REGISTRATION_LOOKUP_TIMEOUT_MS) {
    const directRegistration = await navigator.serviceWorker.getRegistration();

    if (directRegistration) {
      return directRegistration;
    }

    try {
      const readyRegistration = await Promise.race([
        navigator.serviceWorker.ready,
        new Promise<null>((resolve) => {
          window.setTimeout(() => resolve(null), 750);
        }),
      ]);

      if (readyRegistration) {
        return readyRegistration;
      }
    } catch {
      // keep polling until timeout
    }

    await new Promise((resolve) => {
      window.setTimeout(resolve, 250);
    });
  }

  const registrations = await navigator.serviceWorker.getRegistrations();
  return registrations[0] ?? null;
}

function waitForControllerActivation(timeoutMs: number) {
  return new Promise<boolean>((resolve) => {
    let completed = false;

    const cleanup = () => {
      if (completed) {
        return;
      }

      completed = true;
      navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange);
      window.clearTimeout(timeoutId);
    };

    const onControllerChange = () => {
      cleanup();
      resolve(true);
    };

    const timeoutId = window.setTimeout(() => {
      cleanup();
      resolve(false);
    }, timeoutMs);

    navigator.serviceWorker.addEventListener('controllerchange', onControllerChange);
  });
}

function waitForWaitingServiceWorker(registration: ServiceWorkerRegistration, timeoutMs: number) {
  return new Promise<ServiceWorker | null>((resolve) => {
    if (registration.waiting) {
      resolve(registration.waiting);
      return;
    }

    let completed = false;
    let currentInstallingWorker: ServiceWorker | null = registration.installing ?? null;

    const cleanup = () => {
      if (completed) {
        return;
      }

      completed = true;
      window.clearTimeout(timeoutId);
      registration.removeEventListener('updatefound', onUpdateFound);
      currentInstallingWorker?.removeEventListener('statechange', onStateChange);
    };

    const resolveWith = (worker: ServiceWorker | null) => {
      cleanup();
      resolve(worker);
    };

    const onStateChange = () => {
      const waitingWorker = registration.waiting;

      if (waitingWorker) {
        resolveWith(waitingWorker);
      }
    };

    const onUpdateFound = () => {
      currentInstallingWorker?.removeEventListener('statechange', onStateChange);
      currentInstallingWorker = registration.installing ?? null;
      currentInstallingWorker?.addEventListener('statechange', onStateChange);
    };

    const timeoutId = window.setTimeout(() => {
      resolveWith(registration.waiting ?? null);
    }, timeoutMs);

    registration.addEventListener('updatefound', onUpdateFound);

    if (currentInstallingWorker) {
      currentInstallingWorker.addEventListener('statechange', onStateChange);
    }
  });
}

export function initializeAppUpdate() {
  if (initialized || typeof window === 'undefined') {
    return;
  }

  initialized = true;
  if (isAppUpdateDisabled()) {
    snapshot = {
      supported: false,
      status: 'idle',
      progressPercent: null,
      currentTaskLabel: null,
    };
    void unregisterServiceWorkers();
    emit();
    return;
  }

  const supported = isDevelopmentUpdateMode() ? true : isAppUpdateSupported();
  snapshot = {
    supported,
    status: supported ? 'ready' : 'idle',
    progressPercent: null,
    currentTaskLabel: null,
  };

  if (isDevelopmentUpdateMode()) {
    startDevelopmentUpdatePolling();
    emit();
    return;
  }

  if (!snapshot.supported) {
    emit();
    return;
  }

  emit();

  void import('virtual:pwa-register')
    .then(({ registerSW }) => {
      serviceWorkerUpdater = registerSW({
        immediate: true,
        onNeedRefresh() {
          snapshot = {
            supported: true,
            status: 'available',
            progressPercent: null,
            currentTaskLabel: null,
          };
          emit();
        },
        onRegistered() {
          snapshot = {
            supported: true,
            status: 'ready',
            progressPercent: null,
            currentTaskLabel: null,
          };
          emit();
        },
        onRegisterError() {
          snapshot = {
            supported: isAppUpdateSupported(),
            status: isAppUpdateSupported() ? 'ready' : 'idle',
            progressPercent: null,
            currentTaskLabel: null,
          };
          emit();
        },
      });
    })
    .catch(() => {
      snapshot = {
        supported: isAppUpdateSupported(),
        status: isAppUpdateSupported() ? 'ready' : 'idle',
        progressPercent: null,
        currentTaskLabel: null,
      };
      emit();
    });
}

export function subscribeAppUpdate(listener: Listener) {
  listeners.add(listener);
  listener(snapshot);

  return () => {
    listeners.delete(listener);
  };
}

export function getAppUpdateSnapshot() {
  return snapshot;
}

export async function applyAppUpdate() {
  if (isDevelopmentUpdateMode()) {
    if (snapshot.status !== 'available') {
      snapshot = {
        supported: true,
        status: 'ready',
        progressPercent: null,
        currentTaskLabel: null,
      };
      emit();
      return false;
    }

    snapshot = {
      supported: true,
      status: 'updating',
      progressPercent: 100,
      currentTaskLabel: '개발 서버 변경 사항을 적용하기 위해 화면을 새로고침합니다.',
    };
    emit();
    reloadAppWithCacheBuster();
    return true;
  }

  if (!isAppUpdateSupported()) {
    return false;
  }

  snapshot = {
    supported: true,
    status: 'updating',
    progressPercent: 20,
    currentTaskLabel: '새 버전 적용을 준비하고 있습니다.',
  };
  emit();

  try {
    if (serviceWorkerUpdater && snapshot.status === 'available') {
      try {
        await serviceWorkerUpdater(true);
        return true;
      } catch {
        // Fall back to manual service worker activation below when the helper path fails.
      }
    }

    const registration = await getAppServiceWorkerRegistration();

    if (!registration) {
      snapshot = {
        supported: true,
        status: 'ready',
        progressPercent: null,
        currentTaskLabel: null,
      };
      emit();
      return false;
    }

    let waitingWorker = registration.waiting;

    if (!waitingWorker) {
      snapshot = {
        supported: true,
        status: 'updating',
        progressPercent: 45,
        currentTaskLabel: '새 버전 서비스 워커를 확인하고 있습니다.',
      };
      emit();

      await registration.update();
      waitingWorker = await waitForWaitingServiceWorker(registration, UPDATE_DISCOVERY_TIMEOUT_MS);
    }

    if (!waitingWorker) {
      snapshot = {
        supported: true,
        status: 'ready',
        progressPercent: null,
        currentTaskLabel: null,
      };
      emit();
      return false;
    }

    snapshot = {
      supported: true,
      status: 'updating',
      progressPercent: 80,
      currentTaskLabel: '서비스 워커를 새 버전으로 교체하고 있습니다.',
    };
    emit();

    const activationPromise = waitForControllerActivation(UPDATE_APPLY_TIMEOUT_MS);
    waitingWorker.postMessage({ type: 'SKIP_WAITING' });
    const activated = await activationPromise;

    if (!activated) {
      snapshot = {
        supported: true,
        status: 'updating',
        progressPercent: 95,
        currentTaskLabel: '서비스 워커 재적용이 지연되어 캐시를 정리한 뒤 다시 불러옵니다.',
      };
      emit();
      await resetServiceWorkersAndReload();
      return true;
    }

    reloadAppWithCacheBuster();
    return true;
  } catch (error) {
    snapshot = {
      supported: true,
      status: 'available',
      progressPercent: null,
      currentTaskLabel: null,
    };
    emit();
    throw error;
  }
}
