"use client"

import { IS_MOBILE_BUILD, SUITE_APP, appScheme, useNativeRuntime } from "@/lib/platform"
import type { OidcRuntimeConfig, RuntimeConfig } from "./types"

export type { RuntimeConfig, OidcRuntimeConfig } from "./types"

export const RUNTIME_CONFIG_STORAGE_KEY = "ulti-runtime-config"

let current: RuntimeConfig | null = null
const listeners = new Set<(cfg: RuntimeConfig | null) => void>()

/** Native redirect URI for this app shell, e.g. `ultimail://oauth/callback`. */
export function nativeRedirectUri(): string {
  return `${appScheme()}://oauth/callback`
}

function trimSlash(url: string): string {
  return url.endsWith("/") ? url : `${url}/`
}

function stripTrailingSlash(url: string): string {
  return url.replace(/\/+$/, "")
}

function wsFromOrigin(origin: string): string {
  try {
    const u = new URL(origin)
    const proto = u.protocol === "https:" ? "wss:" : "ws:"
    return `${proto}//${u.host}/ws`
  } catch {
    return `${origin.replace(/^http/, "ws")}/ws`
  }
}

// ---------------------------------------------------------------------------
// Current config accessors
// ---------------------------------------------------------------------------

export function getRuntimeConfig(): RuntimeConfig | null {
  if (current) return current
  if (typeof window !== "undefined" && useNativeRuntime()) {
    current = loadPersistedRuntimeConfig()
  }
  return current
}

export function setRuntimeConfig(cfg: RuntimeConfig | null) {
  current = cfg
  if (typeof window !== "undefined") {
    try {
      if (cfg) {
        localStorage.setItem(RUNTIME_CONFIG_STORAGE_KEY, JSON.stringify(cfg))
      } else {
        localStorage.removeItem(RUNTIME_CONFIG_STORAGE_KEY)
      }
    } catch {
      /* private mode / quota */
    }
  }
  for (const fn of listeners) fn(cfg)
}

export function onRuntimeConfigChange(
  fn: (cfg: RuntimeConfig | null) => void
): () => void {
  listeners.add(fn)
  return () => listeners.delete(fn)
}

export function loadPersistedRuntimeConfig(): RuntimeConfig | null {
  if (typeof window === "undefined") return null
  try {
    const raw = localStorage.getItem(RUNTIME_CONFIG_STORAGE_KEY)
    if (!raw) return null
    return JSON.parse(raw) as RuntimeConfig
  } catch {
    return null
  }
}

// ---------------------------------------------------------------------------
// Resolved values (web env fallback vs native runtime config)
// ---------------------------------------------------------------------------

/** Backend base URL (absolute on native, proxied `/api/v1` on web). */
export function getApiBaseUrl(): string {
  if (useNativeRuntime()) {
    const cfg = getRuntimeConfig()
    if (cfg) return cfg.apiBaseUrl
  }
  return process.env.NEXT_PUBLIC_API_URL ?? "/api/v1"
}

/** Realtime WS URL. */
export function getWsUrl(): string | null {
  if (useNativeRuntime()) {
    const cfg = getRuntimeConfig()
    if (cfg) return cfg.wsUrl
  }
  if (process.env.NEXT_PUBLIC_WS_URL) return process.env.NEXT_PUBLIC_WS_URL
  if (typeof window !== "undefined") {
    const proto = window.location.protocol === "https:" ? "wss:" : "ws:"
    return `${proto}//${window.location.host}/ws`
  }
  return null
}

export function getOnlyOfficeUrl(): string | undefined {
  if (useNativeRuntime()) {
    const cfg = getRuntimeConfig()
    if (cfg?.onlyOfficeUrl) return cfg.onlyOfficeUrl
  }
  return process.env.NEXT_PUBLIC_ONLYOFFICE_URL
}

export function getAiOrigin(): string | undefined {
  if (useNativeRuntime()) {
    const cfg = getRuntimeConfig()
    if (cfg?.aiOrigin) return cfg.aiOrigin
  }
  return process.env.NEXT_PUBLIC_AI_ORIGIN
}

// ---------------------------------------------------------------------------
// Discovery: derive a full RuntimeConfig from an instance origin (server picker)
// ---------------------------------------------------------------------------

type OidcDiscoveryDoc = {
  authorization_endpoint: string
  token_endpoint: string
  end_session_endpoint?: string
  issuer?: string
}

/** Issuer path segment within an instance, configurable for exotic setups. */
function issuerPath(): string {
  return (
    process.env.NEXT_PUBLIC_OIDC_ISSUER_PATH ?? "/auth/application/o/ulti/"
  )
}

function mobileClientId(): string {
  // Shared public PKCE client across the suite enables cross-app SSO.
  return process.env.NEXT_PUBLIC_OIDC_CLIENT_ID ?? "ulti-mobile"
}

export class RuntimeConfigError extends Error {}

/**
 * Build a RuntimeConfig for an instance origin by running OIDC discovery.
 * Used by the server picker (both the UltiSpace preset and self-hosted URLs).
 */
export async function deriveRuntimeConfig(
  rawOrigin: string,
  opts?: { label?: string }
): Promise<RuntimeConfig> {
  let origin: string
  try {
    const u = new URL(
      rawOrigin.includes("://") ? rawOrigin : `https://${rawOrigin}`
    )
    origin = stripTrailingSlash(u.origin)
  } catch {
    throw new RuntimeConfigError(`URL d'instance invalide : ${rawOrigin}`)
  }

  const issuer = trimSlash(`${origin}${issuerPath()}`)
  const discoveryUrl = `${issuer}.well-known/openid-configuration`

  let doc: OidcDiscoveryDoc
  try {
    const res = await fetch(discoveryUrl, { headers: { Accept: "application/json" } })
    if (!res.ok) {
      throw new RuntimeConfigError(
        `Découverte OIDC échouée (${res.status}) sur ${origin}`
      )
    }
    doc = (await res.json()) as OidcDiscoveryDoc
  } catch (err) {
    if (err instanceof RuntimeConfigError) throw err
    throw new RuntimeConfigError(
      `Impossible de joindre ${origin}. Vérifie l'URL et ta connexion.`
    )
  }

  const oidc: OidcRuntimeConfig = {
    issuer: doc.issuer ? trimSlash(doc.issuer) : issuer,
    clientId: mobileClientId(),
    redirectUri: nativeRedirectUri(),
    authorizationEndpoint: doc.authorization_endpoint,
    tokenEndpoint: doc.token_endpoint,
    endSessionEndpoint:
      doc.end_session_endpoint ?? `${issuer}end-session/`,
    enrollmentUrl: `${origin}/auth/if/flow/ulti-enrollment/`,
  }

  return {
    label: opts?.label ?? origin.replace(/^https?:\/\//, ""),
    instanceOrigin: origin,
    apiBaseUrl: `${origin}/api/v1`,
    wsUrl: wsFromOrigin(origin),
    oidc,
    aiOrigin: origin,
    aiPublicPath: process.env.NEXT_PUBLIC_AI_PUBLIC_PATH ?? "/ai",
    onlyOfficeUrl: `${origin}/office`,
    hocuspocusUrl: `${wsFromOrigin(origin).replace(/\/ws$/, "")}/collab`,
  }
}

/** Origin of the hosted UltiSpace offering (preset option in the picker). */
export function ultiSpaceOrigin(): string {
  return process.env.NEXT_PUBLIC_ULTISPACE_ORIGIN ?? "https://space.ulti.app"
}

/** Whether a server has been selected yet (native only). */
export function hasRuntimeConfig(): boolean {
  return getRuntimeConfig() !== null
}

/** Convenience used by debug/logging. */
export const RUNTIME_INFO = {
  isMobileBuild: IS_MOBILE_BUILD,
  suiteApp: SUITE_APP,
}
