/**
 * Authentication composable for OAuth-based login flow.
 *
 * Provides a higher-level API on top of the auth store, with:
 * - Loading and error state management
 * - OAuth flow helpers (initiate, callback)
 * - Logout with navigation
 * - App initialization with token validation
 *
 * Use this composable in components instead of accessing the auth store directly
 * for operations that involve async operations or navigation.
 */
import { ref, computed, readonly } from 'vue'
import { useRouter } from 'vue-router'

import { useAuthStore } from '@/stores/auth'
import type { User } from '@/stores/auth'
import { apiClient } from '@/api/client'
import { config } from '@/config'

/** OAuth provider types supported by the backend */
export type OAuthProvider = 'google' | 'discord'

/** User profile response from the backend API */
interface UserProfileResponse {
  id: string
  display_name: string
  avatar_url: string | null
  has_starter_deck: boolean
}

/** Parsed tokens from OAuth callback URL */
interface ParsedTokens {
  accessToken: string
  refreshToken: string
  expiresIn: number
}

/** Result of handleCallback operation */
export interface CallbackResult {
  success: boolean
  user?: User
  error?: string
  needsStarter?: boolean
}

/**
 * Parse tokens from URL hash fragment.
 *
 * The backend redirects with tokens in the fragment (after #) for security,
 * since fragments are not sent to servers in HTTP requests.
 */
function parseTokensFromHash(): ParsedTokens | null {
  const hash = window.location.hash.substring(1)
  if (!hash) return null

  const params = new URLSearchParams(hash)
  const accessToken = params.get('access_token')
  const refreshToken = params.get('refresh_token')
  const expiresIn = params.get('expires_in')

  if (!accessToken || !refreshToken || !expiresIn) {
    return null
  }

  return {
    accessToken,
    refreshToken,
    expiresIn: parseInt(expiresIn, 10),
  }
}

/**
 * Parse error from URL query params.
 *
 * When OAuth fails, the backend redirects with error info in query params.
 */
function parseErrorFromQuery(): { error: string; message: string } | null {
  const params = new URLSearchParams(window.location.search)
  const error = params.get('error')
  const message = params.get('message')

  if (error) {
    return {
      error,
      message: message || 'Authentication failed. Please try again.',
    }
  }

  return null
}

/**
 * Transform API user profile to auth store User type.
 */
function transformUserProfile(response: UserProfileResponse): User {
  return {
    id: response.id,
    displayName: response.display_name,
    avatarUrl: response.avatar_url,
    hasStarterDeck: response.has_starter_deck,
  }
}

/**
 * Authentication composable.
 *
 * Wraps the auth store with loading/error state management and provides
 * higher-level methods for OAuth flows.
 *
 * @example
 * ```vue
 * <script setup lang="ts">
 * import { useAuth } from '@/composables/useAuth'
 *
 * const { isAuthenticated, isLoading, initiateOAuth, logout } = useAuth()
 *
 * function handleGoogleLogin() {
 *   initiateOAuth('google')
 * }
 * </script>
 * ```
 */
export function useAuth() {
  const router = useRouter()
  const authStore = useAuthStore()

  // Local state for this composable instance
  const isInitializing = ref(false)
  const isInitialized = ref(false)
  const isProcessingCallback = ref(false)
  const isLoggingOut = ref(false)
  const localError = ref<string | null>(null)

  // Computed properties from store
  const isAuthenticated = computed(() => authStore.isAuthenticated)
  const user = computed(() => authStore.user)
  const isLoading = computed(
    () => authStore.isLoading || isInitializing.value || isProcessingCallback.value || isLoggingOut.value
  )
  const error = computed(() => localError.value || authStore.error)

  /**
   * Initiate OAuth login flow.
   *
   * Redirects the user to the backend OAuth endpoint, which then redirects
   * to the OAuth provider (Google/Discord). After authentication, the user
   * is redirected back to /auth/callback with tokens.
   *
   * @param provider - OAuth provider to use ('google' or 'discord')
   */
  function initiateOAuth(provider: OAuthProvider): void {
    localError.value = null
    const url = authStore.getOAuthUrl(provider)
    window.location.href = url
  }

  /**
   * Handle OAuth callback.
   *
   * Called from AuthCallbackPage after being redirected from OAuth provider.
   * Extracts tokens from URL, fetches user profile, and determines redirect.
   *
   * @returns Result indicating success/failure and redirect info
   */
  async function handleCallback(): Promise<CallbackResult> {
    isProcessingCallback.value = true
    localError.value = null

    try {
      // Check for OAuth errors first
      const errorInfo = parseErrorFromQuery()
      if (errorInfo) {
        return {
          success: false,
          error: errorInfo.message,
        }
      }

      // Parse tokens from URL fragment
      const tokens = parseTokensFromHash()
      if (!tokens) {
        return {
          success: false,
          error: 'Invalid authentication response. Missing tokens.',
        }
      }

      // Store tokens in auth store
      authStore.setTokens({
        accessToken: tokens.accessToken,
        refreshToken: tokens.refreshToken,
        expiresAt: Date.now() + tokens.expiresIn * 1000,
      })

      // Clear tokens from URL for security
      window.history.replaceState(null, '', window.location.pathname)

      // Fetch user profile
      const profileResponse = await apiClient.get<UserProfileResponse>('/api/users/me')
      const user = transformUserProfile(profileResponse)
      authStore.setUser(user)

      return {
        success: true,
        user,
        needsStarter: !user.hasStarterDeck,
      }
    } catch (e) {
      const errorMessage = e instanceof Error ? e.message : 'Failed to complete authentication.'
      localError.value = errorMessage
      return {
        success: false,
        error: errorMessage,
      }
    } finally {
      isProcessingCallback.value = false
    }
  }

  /**
   * Log out the current user.
   *
   * Revokes the refresh token on the server, clears local state,
   * and redirects to the login page.
   *
   * @param redirectToLogin - Whether to redirect to login page (default: true)
   */
  async function logout(redirectToLogin = true): Promise<void> {
    isLoggingOut.value = true
    localError.value = null

    try {
      await authStore.logout(true)

      if (redirectToLogin) {
        router.push({ name: 'Login' })
      }
    } finally {
      isLoggingOut.value = false
    }
  }

  /**
   * Log out from all devices.
   *
   * Revokes all refresh tokens for the user, effectively logging them out
   * from all sessions. This requires an additional API call.
   *
   * @param redirectToLogin - Whether to redirect to login page (default: true)
   */
  async function logoutAll(redirectToLogin = true): Promise<void> {
    isLoggingOut.value = true
    localError.value = null

    try {
      // Call the logout-all endpoint to revoke all tokens
      const token = await authStore.getValidToken()
      if (token) {
        await fetch(`${config.apiBaseUrl}/api/auth/logout-all`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
          },
        })
      }

      // Clear local state without making another server call
      await authStore.logout(false)

      if (redirectToLogin) {
        router.push({ name: 'Login' })
      }
    } catch {
      // Even if server call fails, clear local state
      await authStore.logout(false)

      if (redirectToLogin) {
        router.push({ name: 'Login' })
      }
    } finally {
      isLoggingOut.value = false
    }
  }

  /**
   * Initialize authentication state.
   *
   * Should be called once on app startup. Validates existing tokens
   * and fetches user profile if authenticated.
   *
   * @returns Whether initialization succeeded with a valid session
   */
  async function initialize(): Promise<boolean> {
    if (isInitialized.value) {
      return authStore.isAuthenticated
    }

    isInitializing.value = true
    localError.value = null

    try {
      // First, let the store validate/refresh tokens
      await authStore.init()

      // If we have tokens but no user data, fetch the profile
      if (authStore.isAuthenticated && !authStore.user) {
        try {
          const profileResponse = await apiClient.get<UserProfileResponse>('/api/users/me')
          const user = transformUserProfile(profileResponse)
          authStore.setUser(user)
        } catch {
          // Failed to fetch profile - tokens may be invalid
          await authStore.logout(false)
          return false
        }
      }

      return authStore.isAuthenticated
    } finally {
      isInitializing.value = false
      isInitialized.value = true
    }
  }

  /**
   * Fetch the current user's profile from the API.
   *
   * Updates the user in the auth store with fresh data.
   *
   * @returns The updated user profile
   * @throws Error if not authenticated or fetch fails
   */
  async function fetchProfile(): Promise<User> {
    if (!authStore.isAuthenticated) {
      throw new Error('Not authenticated')
    }

    const profileResponse = await apiClient.get<UserProfileResponse>('/api/users/me')
    const user = transformUserProfile(profileResponse)
    authStore.setUser(user)
    return user
  }

  /**
   * Clear any error state.
   */
  function clearError(): void {
    localError.value = null
  }

  return {
    // State (readonly to prevent external mutation)
    isAuthenticated,
    isInitialized: readonly(isInitialized),
    isLoading,
    error,
    user,

    // Actions
    initiateOAuth,
    handleCallback,
    logout,
    logoutAll,
    initialize,
    fetchProfile,
    clearError,
  }
}

export type UseAuth = ReturnType<typeof useAuth>
