import { Preferences } from '@capacitor/preferences'

// Single source of truth for the bundled-app session: the auth token + the iDempiere
// "context" (org / warehouse / role / client / user / language and the related objects) that the
// existing /mobile pages and the auth middleware expect. Durable via Capacitor Preferences
// (survives a WebView process kill, unlike localStorage which Android may evict); mirrored into a
// useState ref for synchronous reads from the fetch interceptor.
//
// IMPORTANT: this composable is used ONLY by the app build (the 00.capacitor-api plugin). In the
// web build that plugin is dropped (app:resolve hook in nuxt.config), so this file — and the
// @capacitor/preferences import — tree-shakes out. The web build never touches Preferences.

const STORE_KEY = 'logship_app_session'

interface AppContext {
  organizationId?: string | number
  warehouseId?: string | number
  roleId?: string | number
  clientId?: string | number
  userId?: string | number
  language?: string
  user?: any
  client?: any
  role?: any
  organization?: any
  warehouse?: any
  organizations?: any
  clients?: any
}

interface AppSession {
  token: string | null
  refreshToken: string | null
  context: AppContext
  // Base64'd login credentials, kept ONLY in app-private Capacitor Preferences so the app can
  // silently re-login when the iDempiere refresh token has itself expired (the server cannot
  // refresh for the app — it never receives the refresh token). Same security posture as the web
  // build, which stores logship_xu / logship_py cookies for refreshTokenHelper's tokens fallback.
  credentials?: { u: string; p: string } | null
}

// The cookies the existing pages / auth middleware read client-side (the NON-httpOnly originals
// that the SSR login used to set). We re-create them in the app's own WebView cookie jar so those
// `useCookie('logship_*')` reads keep working with ZERO page edits.
const COOKIE_MAP: Record<string, keyof AppContext> = {
  logship_organization_id: 'organizationId',
  logship_warehouse_id: 'warehouseId',
  logship_role_id: 'roleId',
  logship_client_id: 'clientId',
  logship_user_id: 'userId',
  logship_language: 'language',
  logship_user: 'user',
  logship_client: 'client',
  logship_role: 'role',
  logship_organization: 'organization',
  logship_warehouse: 'warehouse',
  logship_organizations: 'organizations',
  logship_clients: 'clients',
}

// Scalar context sent to the server as X-Logship-* headers (the app-auth middleware maps them
// back into the cookies the existing routes read).
const HEADER_MAP: Array<[string, keyof AppContext]> = [
  ['X-Logship-Organization-Id', 'organizationId'],
  ['X-Logship-Warehouse-Id', 'warehouseId'],
  ['X-Logship-Role-Id', 'roleId'],
  ['X-Logship-Client-Id', 'clientId'],
  ['X-Logship-User-Id', 'userId'],
  ['X-Logship-Language', 'language'],
]

const b64 = (s: string) => {
  try { return btoa(unescape(encodeURIComponent(s))) } catch { return '' }
}
const unb64 = (s: string) => {
  try { return decodeURIComponent(escape(atob(s))) } catch { return '' }
}

const writeCookie = (name: string, value: any) => {
  if (typeof document === 'undefined') return
  if (value === undefined || value === null || value === '') return
  // Match how Nuxt's useCookie serializes: JSON for non-strings, URI-encoded. 30-day persistence.
  const v = typeof value === 'string' ? value : JSON.stringify(value)
  document.cookie = `${name}=${encodeURIComponent(v)}; path=/; max-age=${60 * 60 * 24 * 30}; samesite=lax`
}
const deleteCookie = (name: string) => {
  if (typeof document === 'undefined') return
  document.cookie = `${name}=; path=/; max-age=0`
}

export const useAuthSession = () => {
  const session = useState<AppSession>('appSession', () => ({ token: null, refreshToken: null, context: {}, credentials: null }))

  const token = computed(() => session.value.token)
  const refreshToken = computed(() => session.value.refreshToken)
  const context = computed(() => session.value.context)

  const persist = async () => {
    try { await Preferences.set({ key: STORE_KEY, value: JSON.stringify(session.value) }) } catch { /* non-fatal */ }
  }

  // Write the context into the WebView's own cookie jar so the existing pages' useCookie reads
  // (and the auth middleware) resolve unchanged.
  const syncCookies = () => {
    for (const [cookieName, ctxKey] of Object.entries(COOKIE_MAP)) {
      writeCookie(cookieName, session.value.context[ctxKey])
    }
  }

  const load = async () => {
    try {
      const { value } = await Preferences.get({ key: STORE_KEY })
      if (value) {
        const parsed = JSON.parse(value) as AppSession
        session.value = {
          token: parsed.token ?? null,
          refreshToken: parsed.refreshToken ?? null,
          context: parsed.context ?? {},
          credentials: parsed.credentials ?? null,
        }
        syncCookies()
      }
    } catch { /* corrupt/empty store → start fresh */ }
  }

  // Map a login (app-auth/login) response body into the session, persist, and write cookies.
  const setFromLogin = (data: any) => {
    if (!data || !data.token) return
    session.value = {
      token: data.token ?? session.value.token ?? null,
      refreshToken: data.refresh_token ?? session.value.refreshToken ?? null,
      context: {
        organizationId: data.organization?.id ?? data.organizationId,
        warehouseId: data.warehouse?.id ?? data.warehouseId,
        roleId: data.role?.id ?? data.roleId,
        clientId: data.client?.id ?? data.clientId,
        userId: data.user?.id ?? data.userId,
        language: data.language,
        user: data.user,
        client: data.client,
        role: data.role,
        organization: data.organization,
        warehouse: data.warehouse,
        organizations: data.organizations,
        clients: data.clients,
      },
      // Preserve stored credentials across a re-login so silent recovery keeps working.
      credentials: session.value.credentials ?? null,
    }
    syncCookies()
    persist()
  }

  const setTokens = (newToken: string, newRefresh?: string | null) => {
    session.value.token = newToken
    if (newRefresh) session.value.refreshToken = newRefresh
    persist()
  }

  // Persist the login credentials (base64) so the app can silently re-login when both the access
  // and refresh tokens have expired. Wiped on explicit logout via clear().
  const setCredentials = (userName: string, password: string) => {
    if (!userName || !password) return
    session.value.credentials = { u: b64(userName), p: b64(password) }
    persist()
  }
  const getCredentials = (): { userName: string; password: string } | null => {
    const c = session.value.credentials
    if (!c?.u || !c?.p) return null
    const userName = unb64(c.u)
    const password = unb64(c.p)
    if (!userName || !password) return null
    return { userName, password }
  }

  const contextHeaders = (): Record<string, string> => {
    const h: Record<string, string> = { 'X-Logship-App': '1' }
    const ctx = session.value.context
    for (const [header, key] of HEADER_MAP) {
      const v = ctx[key]
      if (v !== undefined && v !== null && v !== '') h[header] = String(v)
    }
    // The auth middleware's /authenticated check returns the user object — carry it so the
    // server can repopulate the logship_user cookie for that request.
    if (ctx.user) h['X-Logship-User'] = b64(JSON.stringify(ctx.user))
    return h
  }

  const isAuthenticated = computed(() => !!session.value.token)

  // Restore a session from a biometric-unlock blob ({ refreshToken, context }). No access token
  // yet — the caller mints a fresh one via /api/app-auth/token-refresh. Writes the context cookies
  // so the existing pages/auth middleware work, and persists.
  const restoreSession = (data: { refreshToken?: string | null, context?: AppContext }) => {
    session.value = {
      token: null,
      refreshToken: data?.refreshToken ?? null,
      context: data?.context ?? {},
      credentials: session.value.credentials ?? null,
    }
    syncCookies()
    persist()
  }

  // A minimal, durable snapshot for biometric storage: the (revocable) refresh token + the context
  // needed to rebuild cookies/headers. The short-lived access token is intentionally excluded.
  const biometricSnapshot = () => ({
    refreshToken: session.value.refreshToken,
    context: session.value.context,
  })

  const clear = async () => {
    session.value = { token: null, refreshToken: null, context: {} }
    for (const name of Object.keys(COOKIE_MAP)) deleteCookie(name)
    try { await Preferences.remove({ key: STORE_KEY }) } catch { /* non-fatal */ }
  }

  return { token, refreshToken, context, isAuthenticated, load, setFromLogin, setTokens, setCredentials, getCredentials, contextHeaders, restoreSession, biometricSnapshot, clear }
}
