import axios from 'axios'
import { Capacitor } from '@capacitor/core'

const TOKEN_KEY = 'jiaowu_token'
const REFRESH_TOKEN_KEY = 'jiaowu_refresh_token'
const ACCESS_EXPIRES_KEY = 'jiaowu_access_expires_at'
const SESSION_EXPIRES_KEY = 'jiaowu_session_expires_at'
const USER_KEY = 'jiaowu_user'
const LAST_ACTIVITY_KEY = 'jiaowu_last_activity_at'
const WEB_IDLE_MILLISECONDS = 30 * 60 * 1000
const REFRESH_AHEAD_MILLISECONDS = 60 * 1000
const WEB_SLIDING_TOUCH_MILLISECONDS = 60 * 1000

export interface AuthSessionPayload {
  token: string
  accessTokenExpiresAt: string
  refreshToken: string
  sessionExpiresAt: string
  user: unknown
}

export const isNativeApp = () => Capacitor.isNativePlatform()

export function saveAuthSession(payload: AuthSessionPayload, recordActivity = true) {
  localStorage.setItem(TOKEN_KEY, payload.token)
  localStorage.setItem(REFRESH_TOKEN_KEY, payload.refreshToken)
  localStorage.setItem(ACCESS_EXPIRES_KEY, payload.accessTokenExpiresAt)
  localStorage.setItem(SESSION_EXPIRES_KEY, payload.sessionExpiresAt)
  localStorage.setItem(USER_KEY, JSON.stringify(payload.user))
  if (recordActivity) markActivity()
  window.dispatchEvent(new Event('mingxu-auth-changed'))
}

export function clearAuthSession(notifyExpired = false) {
  localStorage.removeItem(TOKEN_KEY)
  localStorage.removeItem(REFRESH_TOKEN_KEY)
  localStorage.removeItem(ACCESS_EXPIRES_KEY)
  localStorage.removeItem(SESSION_EXPIRES_KEY)
  localStorage.removeItem(USER_KEY)
  localStorage.removeItem(LAST_ACTIVITY_KEY)
  window.dispatchEvent(new Event('mingxu-auth-changed'))
  if (notifyExpired) window.dispatchEvent(new Event('mingxu-session-expired'))
}

export function markActivity() {
  localStorage.setItem(LAST_ACTIVITY_KEY, String(Date.now()))
}

export function hasExceededWebIdleTimeout(now = Date.now()) {
  if (isNativeApp()) return false
  const lastActivity = Number(localStorage.getItem(LAST_ACTIVITY_KEY) ?? 0)
  return lastActivity > 0 && now - lastActivity >= WEB_IDLE_MILLISECONDS
}

export function hasLocallyExpired(now = Date.now()) {
  const sessionExpiresAt = Date.parse(localStorage.getItem(SESSION_EXPIRES_KEY) ?? '')
  return hasExceededWebIdleTimeout(now) ||
    (Number.isFinite(sessionExpiresAt) && sessionExpiresAt <= now)
}

let refreshPromise: Promise<string | null> | null = null

export function refreshAuthSession(): Promise<string | null> {
  if (refreshPromise) return refreshPromise
  const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY)
  if (!refreshToken || hasLocallyExpired()) {
    clearAuthSession(true)
    return Promise.resolve(null)
  }

  refreshPromise = axios.post<AuthSessionPayload>(
    `${import.meta.env.VITE_API_BASE_URL ?? '/api'}/auth/refresh`,
    { refreshToken },
    { timeout: 15000 },
  ).then(({ data }) => {
    saveAuthSession(data, false)
    return data.token
  }).catch((error: unknown) => {
    const currentRefreshToken = localStorage.getItem(REFRESH_TOKEN_KEY)
    if (currentRefreshToken && currentRefreshToken !== refreshToken) {
      return localStorage.getItem(TOKEN_KEY)
    }
    if (axios.isAxiosError(error) &&
      error.response &&
      [400, 401, 403].includes(error.response.status)) {
      clearAuthSession(true)
      return null
    }
    throw error
  }).finally(() => {
    refreshPromise = null
  })
  return refreshPromise
}

export async function refreshIfNeeded(isCurrentRequestActivity = false) {
  const token = localStorage.getItem(TOKEN_KEY)
  if (!token) return null
  if (hasLocallyExpired()) {
    clearAuthSession(true)
    return null
  }
  const expiresAt = Date.parse(localStorage.getItem(ACCESS_EXPIRES_KEY) ?? '')
  const sessionExpiresAt = Date.parse(localStorage.getItem(SESSION_EXPIRES_KEY) ?? '')
  const lastActivity = Number(localStorage.getItem(LAST_ACTIVITY_KEY) ?? 0)
  const now = Date.now()
  const hasRecentWebActivity = !isNativeApp() &&
    (isCurrentRequestActivity || now - lastActivity <= WEB_SLIDING_TOUCH_MILLISECONDS)
  const webSessionNeedsSlidingTouch = hasRecentWebActivity &&
    Number.isFinite(sessionExpiresAt) &&
    sessionExpiresAt - now <= WEB_IDLE_MILLISECONDS - WEB_SLIDING_TOUCH_MILLISECONDS
  if (!Number.isFinite(expiresAt) ||
    expiresAt - now <= REFRESH_AHEAD_MILLISECONDS ||
    webSessionNeedsSlidingTouch) {
    return refreshAuthSession()
  }
  return token
}

export function initializeAuthSession() {
  if (!localStorage.getItem(TOKEN_KEY)) return
  if (hasLocallyExpired()) {
    clearAuthSession(true)
    return
  }

  let lastActivityWrite = 0
  const recordActivity = () => {
    const now = Date.now()
    if (now - lastActivityWrite < 5000) return
    lastActivityWrite = now
    markActivity()
  }
  const activityEvents: Array<keyof WindowEventMap> = [
    'pointerdown',
    'keydown',
    'touchstart',
    'scroll',
  ]
  activityEvents.forEach(event =>
    window.addEventListener(event, recordActivity, { passive: true }))

  window.setInterval(() => {
    if (!localStorage.getItem(TOKEN_KEY)) return
    if (hasLocallyExpired()) {
      clearAuthSession(true)
      return
    }
    if (document.visibilityState === 'visible') {
      void refreshIfNeeded().catch(() => undefined)
    }
  }, 30000)

  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'visible') {
      void refreshIfNeeded(true).catch(() => undefined)
    }
  })
  window.addEventListener('online', () =>
    void refreshIfNeeded(true).catch(() => undefined))
}

export const authStorageKeys = {
  token: TOKEN_KEY,
  refreshToken: REFRESH_TOKEN_KEY,
  user: USER_KEY,
}
