/**
 * useAuth — React hook wrapping the vanilla auth manager.
 *
 * Wraps `createAuthManager` / `getDefaultAuthManager` and exposes a reactive
 * snapshot of authentication state.  The vanilla auth manager has no
 * subscription API, so we derive state from it on mount and expose a
 * `refresh()` function that re-reads the snapshot and re-renders.
 *
 * Token expiry causes automatic refresh: we schedule a setTimeout to call
 * `manager.refreshAccessToken()` shortly before the token expires, then
 * trigger a re-render.
 */
import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
import { createAuthManager, getDefaultAuthManager } from '../../auth/index'
import type { AuthConfig, UserInfo, TokenPayload } from '../../auth/index'

export type { AuthConfig, UserInfo, TokenPayload }

type AuthManager = ReturnType<typeof createAuthManager>

export interface UseAuthResult {
  /** The underlying auth manager instance. */
  manager: AuthManager
  /** True when a valid, non-expired access token is present. */
  isAuthenticated: boolean
  /** Decoded user info extracted from the JWT payload, or null. */
  user: UserInfo | null
  /** Re-read auth state from the manager (useful after external mutations). */
  refresh: () => void
  /**
   * Perform a token refresh using the stored refresh token.
   * Resolves to the new access token, or null on failure.
   */
  refreshToken: () => Promise<string | null>
  /** Clear tokens and call the server logout endpoint. */
  logout: () => Promise<void>
}

function payloadToUserInfo(payload: TokenPayload | null): UserInfo | null {
  if (!payload) return null
  return {
    id: payload.sub,
    username: payload.sub,
    email: '',
    roles: payload.roles ?? [],
    groups: payload.groups ?? [],
    is_active: true,
    is_verified: true,
  }
}

/**
 * @param config - Optional AuthConfig.  When omitted the module-level default
 *   manager is used (shared with other consumers in the same app).  Pass an
 *   explicit config to get an isolated manager instance.
 */
export function useAuth(config?: AuthConfig): UseAuthResult {
  const manager = useMemo<AuthManager>(
    () => (config !== undefined ? createAuthManager(config) : getDefaultAuthManager()),
    [config] // config reference controls re-creation
  )

  const [isAuthenticated, setIsAuthenticated] = useState<boolean>(() => manager.isAuthenticated())
  const [user, setUser] = useState<UserInfo | null>(() => {
    const token = manager.getAccessToken()
    return token ? payloadToUserInfo(manager.parseToken(token)) : null
  })

  const refreshTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)

  const readSnapshot = useCallback(() => {
    const auth = manager.isAuthenticated()
    setIsAuthenticated(auth)
    const token = manager.getAccessToken()
    setUser(token ? payloadToUserInfo(manager.parseToken(token)) : null)
    return auth
  }, [manager])

  const scheduleTokenRefresh = useCallback(() => {
    if (refreshTimerRef.current !== null) {
      clearTimeout(refreshTimerRef.current)
      refreshTimerRef.current = null
    }

    const token = manager.getAccessToken()
    if (!token) return

    const payload = manager.parseToken(token)
    if (!payload) return

    const nowSec = Math.floor(Date.now() / 1000)
    const expiresIn = payload.exp - nowSec
    if (expiresIn <= 0) return

    // Refresh 30 s before expiry (or immediately when very short-lived).
    const delayMs = Math.max((expiresIn - 30) * 1000, 0)

    refreshTimerRef.current = setTimeout(() => {
      void manager.refreshAccessToken().then(() => {
        readSnapshot()
        scheduleTokenRefresh()
      })
    }, delayMs)
  }, [manager, readSnapshot])

  useEffect(() => {
    readSnapshot()
    scheduleTokenRefresh()

    return () => {
      if (refreshTimerRef.current !== null) {
        clearTimeout(refreshTimerRef.current)
        refreshTimerRef.current = null
      }
    }
  }, [readSnapshot, scheduleTokenRefresh])

  const refresh = useCallback(() => {
    readSnapshot()
  }, [readSnapshot])

  const refreshToken = useCallback(async (): Promise<string | null> => {
    const newToken = await manager.refreshAccessToken()
    readSnapshot()
    scheduleTokenRefresh()
    return newToken
  }, [manager, readSnapshot, scheduleTokenRefresh])

  const logout = useCallback(async (): Promise<void> => {
    await manager.logout()
    readSnapshot()
  }, [manager, readSnapshot])

  return { manager, isAuthenticated, user, refresh, refreshToken, logout }
}
