/**
 * useAuthStore — Pinia auth store for DocuVault.
 *
 * Security invariants (CLAUDE.md):
 *   - accessToken lives ONLY in ref() memory — never written to browser storage
 *   - Refresh token is an httpOnly cookie managed by the backend
 *   - On 401: api/client.js calls authStore.refresh() (uses httpOnly cookie)
 *
 * State:
 *   accessToken  ref(null) — JWT access token, memory only
 *   user         ref(null) — { id, handle, email, role, totp_enabled }
 *   loading      ref(false)
 *   error        ref(null)
 */
import { defineStore } from 'pinia'
import { ref } from 'vue'
import * as api from '../api/client.js'

export const useAuthStore = defineStore('auth', () => {
  // State — accessToken in memory only (CLAUDE.md rule: no browser storage writes)
  const accessToken = ref(null)
  const user = ref(null)
  const loading = ref(false)
  const error = ref(null)
  const quota = ref({ used_bytes: 0, limit_bytes: 0 })

  // Deduplicates concurrent refresh() calls so a single cookie rotation handles
  // multiple simultaneous 401s (e.g. QuotaBar + App.vue firing on page reload).
  let _refreshInFlight = null

  /**
   * Register a new account.
   * Does NOT auto-login — caller should redirect to /login after success.
   */
  async function register(handle, email, password) {
    loading.value = true
    error.value = null
    try {
      const data = await api.register({ handle, email, password })
      return data
    } catch (e) {
      error.value = e.message
      throw e
    } finally {
      loading.value = false
    }
  }

  /**
   * Login with email + password, optionally with TOTP or backup code.
   *
   * options:
   *   { totpCode?: string, backupCode?: string }
   *
   * Return values:
   *   { requires_totp: true }            — TOTP challenge (no tokens issued)
   *   { requires_password_change: true, user_id: string } — must change pw first
   *   undefined                          — full success (accessToken + user set)
   */
  async function login(email, password, options = {}) {
    loading.value = true
    error.value = null
    try {
      const data = await api.login({
        email,
        password,
        totp_code: options.totpCode ?? null,
        backup_code: options.backupCode ?? null,
      })

      if (data.requires_totp) {
        return { requires_totp: true }
      }

      if (data.requires_password_change) {
        return { requires_password_change: true, user_id: data.user_id }
      }

      // Full login success
      accessToken.value = data.access_token
      user.value = data.user
    } catch (e) {
      error.value = e.message
      throw e
    } finally {
      loading.value = false
    }
  }

  /**
   * Refresh the access token using the httpOnly refresh cookie.
   * Called automatically by api/client.js on 401.
   * Throws on failure (session expired — caller should redirect to /login).
   *
   * Concurrent calls share one in-flight promise so the refresh cookie is
   * rotated exactly once even when multiple 401s fire simultaneously.
   */
  function refresh() {
    if (_refreshInFlight) return _refreshInFlight
    _refreshInFlight = api.refreshToken()
      .then(data => {
        accessToken.value = data.access_token
        user.value = data.user
      })
      .finally(() => {
        _refreshInFlight = null
      })
    return _refreshInFlight
  }

  /**
   * Logout: revoke the current refresh token and clear local state.
   * Always clears accessToken + user, even if the API call fails.
   */
  async function logout() {
    try {
      await api.logout()
    } catch {
      // Ignore errors — clear state regardless
    } finally {
      accessToken.value = null
      user.value = null
    }
  }

  /**
   * Sign out of all devices: revoke ALL refresh tokens for the current user.
   */
  async function logoutAll() {
    try {
      await api.logoutAll()
    } catch {
      // Ignore errors
    } finally {
      accessToken.value = null
      user.value = null
    }
  }

  /**
   * Fetch current user quota from the server and update quota ref.
   * Silently ignores errors — QuotaBar handles error display by hiding itself (UI-SPEC).
   */
  async function fetchQuota() {
    try {
      const data = await api.getMyQuota()
      quota.value = { used_bytes: data.used_bytes, limit_bytes: data.limit_bytes }
    } catch {
      // Silently ignore — QuotaBar hides itself on fetch error (UI-SPEC)
    }
  }

  return {
    accessToken,
    user,
    loading,
    error,
    quota,
    register,
    login,
    logout,
    logoutAll,
    refresh,
    fetchQuota,
  }
})
