/**
 * Authentication Utilities
 *
 * Shared authentication functionality for CounterMeasure applications.
 */

export interface TokenPayload {
  sub: string
  exp: number
  iat: number
  roles?: string[]
  groups?: string[]
}

export interface AuthResponse {
  access_token: string
  refresh_token?: string
  token_type: string
  expires_in: number
}

export interface AuthTokenStorage {
  getAccessToken: () => string | null
  getRefreshToken: () => string | null
  setTokens: (accessToken: string, refreshToken?: string) => void
  clearTokens: () => void
}

export interface AuthTokenStorageConfig {
  accessTokenKey?: string
  refreshTokenKey?: string
}

export interface InitialAuthTokens {
  accessToken?: string | null
  refreshToken?: string | null
}

export interface UserInfo {
  id: string
  username: string
  email: string
  display_name?: string
  roles: string[]
  groups: string[]
  is_active: boolean
  is_verified: boolean
}

export interface MfaSetupPayload {
  secret: string
  qr_code: string
  recovery_codes?: string[]
}

export interface MfaChallenge {
  challenge_id: string
  challenge_type: 'totp' | 'webauthn' | 'recovery'
}

export interface WebConfig {
  app_name?: string
  app_links?: AppLink[]
}

export interface AppLink {
  name: string
  url: string
  icon?: string
}

export interface AuthConfig {
  /** Legacy localStorage key for access tokens. Ignored when tokenStorage is provided. */
  accessTokenKey?: string
  /** Legacy localStorage key for refresh tokens. Ignored when tokenStorage is provided. */
  refreshTokenKey?: string
  apiBasePath?: string
  /**
   * Token storage strategy. Prefer cookie-backed auth or createMemoryTokenStorage().
   * The default localStorage strategy is retained for compatibility and development.
   */
  tokenStorage?: AuthTokenStorage
}

const DEFAULT_CONFIG: Required<Omit<AuthConfig, 'tokenStorage'>> = {
  accessTokenKey: 'auth_access_token',
  refreshTokenKey: 'auth_refresh_token',
  apiBasePath: '/api/v1/auth',
}

/**
 * Legacy token storage backed by localStorage. Prefer memory-only storage or
 * server-managed HttpOnly cookies for production auth flows.
 */
export function createLocalStorageTokenStorage(
  storageConfig: AuthTokenStorageConfig = {}
): AuthTokenStorage {
  const accessTokenKey = storageConfig.accessTokenKey ?? DEFAULT_CONFIG.accessTokenKey
  const refreshTokenKey = storageConfig.refreshTokenKey ?? DEFAULT_CONFIG.refreshTokenKey

  return {
    getAccessToken: () => localStorage.getItem(accessTokenKey),
    getRefreshToken: () => localStorage.getItem(refreshTokenKey),
    setTokens: (accessToken: string, refreshToken?: string) => {
      localStorage.setItem(accessTokenKey, accessToken)
      if (refreshToken) {
        localStorage.setItem(refreshTokenKey, refreshToken)
      }
    },
    clearTokens: () => {
      localStorage.removeItem(accessTokenKey)
      localStorage.removeItem(refreshTokenKey)
    },
  }
}

/** Create an in-memory token store that does not persist tokens across reloads. */
export function createMemoryTokenStorage(initialTokens: InitialAuthTokens = {}): AuthTokenStorage {
  let accessToken = initialTokens.accessToken ?? null
  let refreshToken = initialTokens.refreshToken ?? null

  return {
    getAccessToken: () => accessToken,
    getRefreshToken: () => refreshToken,
    setTokens: (newAccessToken: string, newRefreshToken?: string) => {
      accessToken = newAccessToken
      if (newRefreshToken) {
        refreshToken = newRefreshToken
      }
    },
    clearTokens: () => {
      accessToken = null
      refreshToken = null
    },
  }
}

/**
 * Create an auth manager instance with the given configuration.
 */
export function createAuthManager(userConfig: AuthConfig = {}) {
  const config = { ...DEFAULT_CONFIG, ...userConfig }
  const tokenStorage =
    userConfig.tokenStorage ??
    createLocalStorageTokenStorage({
      accessTokenKey: config.accessTokenKey,
      refreshTokenKey: config.refreshTokenKey,
    })

  function getAccessToken(): string | null {
    return tokenStorage.getAccessToken()
  }

  function getRefreshToken(): string | null {
    return tokenStorage.getRefreshToken()
  }

  function setTokens(accessToken: string, refreshToken?: string): void {
    tokenStorage.setTokens(accessToken, refreshToken)
  }

  function clearTokens(): void {
    tokenStorage.clearTokens()
  }

  function parseToken(token: string): TokenPayload | null {
    try {
      const parts = token.split('.')
      if (parts.length !== 3) {
        return null
      }
      const encodedPayload = parts[1]
      if (!encodedPayload) {
        return null
      }
      const payload = JSON.parse(atob(encodedPayload)) as TokenPayload
      return payload
    } catch {
      return null
    }
  }

  function isTokenExpired(token: string): boolean {
    const payload = parseToken(token)
    if (!payload) {
      return true
    }
    const now = Math.floor(Date.now() / 1000)
    return payload.exp < now
  }

  function isAuthenticated(): boolean {
    const token = getAccessToken()
    if (!token) {
      return false
    }
    return !isTokenExpired(token)
  }

  async function refreshAccessToken(): Promise<string | null> {
    const refreshToken = getRefreshToken()
    if (!refreshToken) {
      return null
    }

    try {
      const response = await fetch(`${config.apiBasePath}/refresh`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ refresh_token: refreshToken }),
      })

      if (!response.ok) {
        clearTokens()
        return null
      }

      const data = (await response.json()) as AuthResponse
      setTokens(data.access_token, data.refresh_token)
      return data.access_token
    } catch {
      clearTokens()
      return null
    }
  }

  async function logout(): Promise<void> {
    const token = getAccessToken()
    if (token) {
      try {
        await fetch(`${config.apiBasePath}/logout`, {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${token}`,
          },
        })
      } catch {
        // Ignore errors during logout
      }
    }
    clearTokens()
  }

  async function fetchWithAuth(url: string, options: RequestInit = {}): Promise<Response> {
    let token = getAccessToken()

    if (token && isTokenExpired(token)) {
      token = await refreshAccessToken()
    }

    const headers = new Headers(options.headers)
    if (token) {
      headers.set('Authorization', `Bearer ${token}`)
    }

    return fetch(url, { ...options, headers })
  }

  function extractTokenFromResponse(response: unknown): AuthResponse | null {
    if (typeof response === 'object' && response !== null && 'access_token' in response) {
      return response as AuthResponse
    }
    return null
  }

  function extractMfaChallengeFromResponse(response: unknown): MfaChallenge | null {
    if (typeof response === 'object' && response !== null && 'challenge_id' in response) {
      return response as MfaChallenge
    }
    return null
  }

  return {
    getAccessToken,
    getRefreshToken,
    setTokens,
    clearTokens,
    parseToken,
    isTokenExpired,
    isAuthenticated,
    refreshAccessToken,
    logout,
    fetchWithAuth,
    extractTokenFromResponse,
    extractMfaChallengeFromResponse,
    config,
    tokenStorage,
  }
}

// Default instance for simple usage
let defaultManager: ReturnType<typeof createAuthManager> | null = null

export function getDefaultAuthManager(config?: AuthConfig) {
  defaultManager ??= createAuthManager(config)
  return defaultManager
}

// Re-export convenience functions using default manager
export const getAccessToken = () => getDefaultAuthManager().getAccessToken()
export const getRefreshToken = () => getDefaultAuthManager().getRefreshToken()
export const setTokens = (accessToken: string, refreshToken?: string) => {
  getDefaultAuthManager().setTokens(accessToken, refreshToken)
}
export const clearTokens = () => {
  getDefaultAuthManager().clearTokens()
}
export const parseToken = (token: string) => getDefaultAuthManager().parseToken(token)
export const isTokenExpired = (token: string) => getDefaultAuthManager().isTokenExpired(token)
export const isAuthenticated = () => getDefaultAuthManager().isAuthenticated()
export const refreshAccessToken = () => getDefaultAuthManager().refreshAccessToken()
export const logout = () => getDefaultAuthManager().logout()
export const fetchWithAuth = (url: string, options?: RequestInit) =>
  getDefaultAuthManager().fetchWithAuth(url, options)
