import { authApi } from '@/lib/api/auth';
import { notifyAccessTokenRefreshed } from '@/lib/auth/accessTokenEvents';

const STORAGE_KEY = 'dyolink.accessTokenExpiresAt';
/** Refresh this long before the access JWT expires. */
const REFRESH_BUFFER_MS = 2 * 60 * 1000;
/** Safety cap — never call /auth/refresh more than once per minute per tab. */
const MIN_REFRESH_GAP_MS = 60 * 1000;

let timerId: ReturnType<typeof setTimeout> | null = null;
let refreshInFlight: Promise<string | undefined> | null = null;
let lastRefreshAt = 0;

export function setAccessTokenExpiresAt(iso: string): void {
  if (typeof window === 'undefined') {
    return;
  }
  sessionStorage.setItem(STORAGE_KEY, iso);
}

export function clearAccessTokenExpiresAt(): void {
  if (typeof window === 'undefined') {
    return;
  }
  sessionStorage.removeItem(STORAGE_KEY);
}

export function rememberAccessTokenExpiresAt(
  iso: string | undefined | null,
): void {
  if (iso) {
    setAccessTokenExpiresAt(iso);
  }
}

function getAccessTokenExpiresAtMs(): number | null {
  if (typeof window === 'undefined') {
    return null;
  }

  const raw = sessionStorage.getItem(STORAGE_KEY);
  if (!raw) {
    return null;
  }

  const ms = Date.parse(raw);
  return Number.isFinite(ms) ? ms : null;
}

function clearTimer(): void {
  if (timerId !== null) {
    clearTimeout(timerId);
    timerId = null;
  }
}

function computeDelayMs(expiresAtMs: number): number {
  const refreshAt = expiresAtMs - REFRESH_BUFFER_MS;
  const delay = refreshAt - Date.now();
  return Math.max(delay, MIN_REFRESH_GAP_MS);
}

function shouldRefreshNow(expiresAtMs: number): boolean {
  return expiresAtMs - REFRESH_BUFFER_MS <= Date.now();
}

async function refreshAccessTokenWithOrg(): Promise<string | undefined> {
  if (refreshInFlight) {
    return refreshInFlight;
  }

  if (Date.now() - lastRefreshAt < MIN_REFRESH_GAP_MS) {
    const expiresAtMs = getAccessTokenExpiresAtMs();
    return expiresAtMs
      ? new Date(expiresAtMs).toISOString()
      : undefined;
  }

  refreshInFlight = (async () => {
    try {
      const result = await authApi.refreshSessionFromCookies();
      const expiresAt = result.data?.accessTokenExpiresAt;
      rememberAccessTokenExpiresAt(expiresAt);

      const orgId = localStorage.getItem('currentOrganizationId');
      if (orgId) {
        try {
          await authApi.selectOrganization(orgId);
        } catch {
          /* refresh may already preserve organizationId on the JWT */
        }
      }

      lastRefreshAt = Date.now();
      notifyAccessTokenRefreshed();
      return expiresAt;
    } finally {
      refreshInFlight = null;
    }
  })();

  return refreshInFlight;
}

function scheduleNextRefresh(reschedule: () => void): void {
  clearTimer();

  if (typeof document !== 'undefined' && document.visibilityState !== 'visible') {
    return;
  }

  const expiresAtMs = getAccessTokenExpiresAtMs();
  if (!expiresAtMs) {
    return;
  }

  timerId = setTimeout(() => {
    void refreshAccessTokenWithOrg()
      .catch(() => {
        /* reactive refresh / next visibility check will recover */
      })
      .finally(reschedule);
  }, computeDelayMs(expiresAtMs));
}

/** Keeps the access cookie fresh while the tab is visible. Returns a cleanup function. */
export function startProactiveSessionRefresh(): () => void {
  if (typeof window === 'undefined') {
    return () => undefined;
  }

  const reschedule = () => scheduleNextRefresh(reschedule);

  const onVisibilityChange = () => {
    if (document.visibilityState === 'visible') {
      const expiresAtMs = getAccessTokenExpiresAtMs();
      if (expiresAtMs && shouldRefreshNow(expiresAtMs)) {
        void refreshAccessTokenWithOrg()
          .catch(() => undefined)
          .finally(reschedule);
      } else {
        reschedule();
      }
      return;
    }

    clearTimer();
  };

  document.addEventListener('visibilitychange', onVisibilityChange);
  reschedule();

  return () => {
    document.removeEventListener('visibilitychange', onVisibilityChange);
    clearTimer();
  };
}
