import { useMemo, useState } from 'react'
import type { PropsWithChildren } from 'react'
import { clearAuthSession, getAuthSession, isAccessTokenStale, setAuthSession } from '../lib/auth-storage'
import type { AuthSession } from '../lib/auth-storage'
import { loginCenter, logoutCenter, meGuildsCenter, refreshCenter, updateMeNameCenter } from '../lib/center-auth-client'
import { AuthContext } from './auth-context'
import type { AuthContextValue } from './auth-context'

export function AuthProvider({ children }: PropsWithChildren) {
  const [session, setSession] = useState<AuthSession | null>(getAuthSession())

  const value = useMemo<AuthContextValue>(
    () => ({
      session,
      isAuthed: !!session,
      login: async (centerApiBase: string, email: string, password: string) => {
        const next = await loginCenter(centerApiBase, { email, password })
        setAuthSession(next)
        setSession(next)
      },
      // Adopt a session obtained out-of-band (OIDC ticket exchange).
      adoptSession: (next: AuthSession) => {
        setAuthSession(next)
        setSession(next)
      },
      logout: async () => {
        if (session?.refreshToken) {
          try {
            await logoutCenter(session.centerApiBase, session.refreshToken)
          } catch {
            // noop
          }
        }
        clearAuthSession()
        setSession(null)
      },
      ensureFreshToken: async () => {
        if (!session) return null
        if (!isAccessTokenStale(session.accessToken)) return session.accessToken

        const refreshed = await refreshCenter(session.centerApiBase, session.refreshToken)
        const next: AuthSession = {
          ...session,
          accessToken: refreshed.accessToken,
          refreshToken: refreshed.refreshToken,
          tokenType: refreshed.tokenType,
          expiresIn: refreshed.expiresIn,
        }
        setAuthSession(next)
        setSession(next)
        return next.accessToken
      },
      refreshGuilds: async () => {
        if (!session) return
        let accessToken = session.accessToken
        let refreshToken = session.refreshToken
        let tokenType = session.tokenType
        let expiresIn = session.expiresIn
        if (isAccessTokenStale(session.accessToken)) {
          const refreshed = await refreshCenter(session.centerApiBase, session.refreshToken)
          accessToken = refreshed.accessToken
          refreshToken = refreshed.refreshToken
          tokenType = refreshed.tokenType
          expiresIn = refreshed.expiresIn
        }

        const guildData = await meGuildsCenter(session.centerApiBase, accessToken)
        const next: AuthSession = {
          ...session,
          accessToken,
          refreshToken,
          tokenType,
          expiresIn,
          guilds: guildData.guilds,
          guildAccessTokens: guildData.guildAccessTokens,
        }
        setAuthSession(next)
        setSession(next)
      },
      updateName: async (name: string) => {
        if (!session) return
        const accessToken = (await (async () => {
          if (!isAccessTokenStale(session.accessToken)) return session.accessToken
          const refreshed = await refreshCenter(session.centerApiBase, session.refreshToken)
          return refreshed.accessToken
        })())
        const updated = await updateMeNameCenter(session.centerApiBase, accessToken, name)
        const next: AuthSession = {
          ...session,
          accessToken,
          user: { ...session.user, name: updated.name },
        }
        setAuthSession(next)
        setSession(next)
      },
    }),
    [session],
  )

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
