<script setup lang="ts">
import {
  ArrowRight,
  Boxes,
  CircleAlert,
  Clock3,
  KeyRound,
  Loader2,
  RefreshCw,
  Search,
  ServerCog,
  ShieldAlert,
  ShieldCheck,
  X,
} from '@lucide/vue'
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { RouterLink, useRouter } from 'vue-router'
import {
  refreshOpenClawModelAuthStatus,
  useOpenClawModelAuthQuery,
  type OpenClawModelAuthProviderDto,
} from '../api/openClawModels'
import AsyncStatePanel from '../components/mission-control/AsyncStatePanel.vue'

const router = useRouter()
const modelAuthQuery = useOpenClawModelAuthQuery()
const query = ref('')
const statusFilter = ref('all')
const selectedProvider = ref<OpenClawModelAuthProviderDto | null>(null)
const detailsDialog = ref<HTMLElement | null>(null)
const detailsCloseButton = ref<HTMLButtonElement | null>(null)
const detailsTrigger = ref<HTMLElement | null>(null)

const collection = computed(() => modelAuthQuery.data.value ?? null)
const modelAuthError = computed(() => {
  const error = modelAuthQuery.error.value
  return error instanceof Error ? error.message : ''
})
const providers = computed(() => collection.value?.items ?? [])
const statuses = computed(() =>
  [...new Set(providers.value.map(provider => provider.status))]
    .sort((left, right) => left.localeCompare(right)),
)
const filteredProviders = computed(() => {
  const needle = query.value.trim().toLocaleLowerCase()
  return providers.value.filter(provider => {
    const matchesStatus = statusFilter.value === 'all'
      || provider.status === statusFilter.value
    const matchesQuery = !needle
      || provider.displayName.toLocaleLowerCase().includes(needle)
      || provider.provider.toLocaleLowerCase().includes(needle)
      || provider.usage?.plan?.toLocaleLowerCase().includes(needle)
    return matchesStatus && matchesQuery
  })
})
const healthyProviders = computed(() =>
  providers.value.filter(provider => provider.status === 'ok' || provider.status === 'static').length,
)
const attentionProviders = computed(() =>
  providers.value.filter(provider => !['ok', 'static'].includes(provider.status)).length,
)
const oauthProfiles = computed(() =>
  providers.value.reduce(
    (total, provider) => total + provider.profiles
      .filter(profile => profile.type === 'oauth')
      .reduce((count, profile) => count + Number(profile.count), 0),
    0,
  ),
)

function statusLabel(status: string) {
  return {
    ok: 'Healthy',
    static: 'Static',
    expiring: 'Expiring',
    expired: 'Expired',
    missing: 'Missing',
    unknown: 'Unknown',
  }[status] ?? 'Unknown'
}

function statusTone(status: string) {
  if (status === 'ok' || status === 'static') return 'nexus-status--success'
  if (status === 'expiring' || status === 'missing') return 'nexus-status--warning'
  return 'nexus-status--danger'
}

function profileTypeLabel(type: string) {
  return {
    oauth: 'OAuth',
    token: 'Token',
    api_key: 'API key',
    unknown: 'Other',
  }[type] ?? 'Other'
}

function authenticationSummary(provider: OpenClawModelAuthProviderDto) {
  const profileCount = provider.profiles.reduce(
    (total, profile) => total + Number(profile.count),
    0,
  )
  const segments: string[] = []
  if (profileCount) segments.push(`${profileCount} profile${profileCount === 1 ? '' : 's'}`)
  if (provider.apiKey) segments.push(provider.apiKey.source === 'env' ? 'environment key' : 'config key')
  return segments.length ? segments.join(' · ') : 'No auth path reported'
}

function expirySummary(provider: OpenClawModelAuthProviderDto) {
  if (!provider.expiry) return provider.status === 'static' ? 'No token expiry' : 'No expiry reported'
  const prefix = Number(provider.expiry.remainingMs) < 0 ? 'Expired' : 'Expires in'
  return `${prefix} ${provider.expiry.label.replace(/^-/, '')}`
}

function formattedExpiry(provider: OpenClawModelAuthProviderDto) {
  if (!provider.expiry) return 'Not reported'
  const date = new Date(provider.expiry.at)
  if (Number.isNaN(date.getTime())) return expirySummary(provider)
  return `${new Intl.DateTimeFormat(undefined, {
    dateStyle: 'medium',
    timeStyle: 'short',
  }).format(date)} · ${expirySummary(provider)}`
}

function apiKeySummary(provider: OpenClawModelAuthProviderDto) {
  if (!provider.apiKey) return 'No API-key source reported'
  if (provider.apiKey.source === 'env') {
    return provider.apiKey.envVar
      ? `Environment variable ${provider.apiKey.envVar}`
      : 'Environment variable'
  }
  return 'OpenClaw configuration'
}

function openDetails(provider: OpenClawModelAuthProviderDto, event: MouseEvent) {
  detailsTrigger.value = event.currentTarget as HTMLElement
  selectedProvider.value = provider
  void nextTick(() => detailsCloseButton.value?.focus())
}

function closeDetails() {
  if (!selectedProvider.value) return
  selectedProvider.value = null
  void nextTick(() => detailsTrigger.value?.focus())
}

function onWindowKeydown(event: KeyboardEvent) {
  if (event.key === 'Escape') closeDetails()
}

function onDialogKeydown(event: KeyboardEvent) {
  if (event.key !== 'Tab' || !detailsDialog.value) return
  const focusable = [...detailsDialog.value.querySelectorAll<HTMLElement>(
    'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])',
  )]
  if (!focusable.length) return
  const first = focusable[0]
  const last = focusable[focusable.length - 1]
  if (event.shiftKey && document.activeElement === first) {
    event.preventDefault()
    last?.focus()
  } else if (!event.shiftKey && document.activeElement === last) {
    event.preventDefault()
    first?.focus()
  }
}

async function refresh() {
  try {
    await refreshOpenClawModelAuthStatus()
  } catch {
    // Vue Query keeps the previous snapshot plus the recoverable error state.
  }
}

onMounted(() => {
  window.addEventListener('keydown', onWindowKeydown)
})

onUnmounted(() => {
  window.removeEventListener('keydown', onWindowKeydown)
})
</script>

<template>
  <div class="models-page nexus-page">
    <header class="nexus-page-header">
      <div class="nexus-page-header__identity">
        <div class="nexus-page-header__icon"><ShieldCheck :size="20" aria-hidden="true" /></div>
        <div>
          <span class="eyebrow">OPENCLAW PROVIDER HEALTH</span>
          <h1>Models</h1>
          <p class="page-subtitle">
            Sanitized provider authentication health from OpenClaw, without credentials or account data.
          </p>
        </div>
      </div>
      <button
        type="button"
        class="nexus-button"
        :disabled="modelAuthQuery.isFetching.value"
        @click="refresh"
      >
        <Loader2
          v-if="modelAuthQuery.isFetching.value"
          :size="15"
          class="spin"
          aria-hidden="true"
        />
        <RefreshCw v-else :size="15" aria-hidden="true" />
        Refresh health
      </button>
    </header>

    <section class="model-route nexus-panel" aria-labelledby="model-route-title">
      <div>
        <span class="route-kicker">Credential boundary</span>
        <h2 id="model-route-title">OpenClaw owns provider access; Nexus observes readiness</h2>
        <p>
          Nexus reads <code>models.authStatus</code> through the gateway. Profile IDs, account
          identities, billing records and credential values are removed by the backend.
        </p>
      </div>
      <div class="route-flow" aria-label="Nexus reads sanitized provider health from OpenClaw">
        <span><Boxes :size="15" aria-hidden="true" /> Nexus</span>
        <ArrowRight :size="15" aria-hidden="true" />
        <span class="route-primary"><ServerCog :size="15" aria-hidden="true" /> OpenClaw</span>
        <ArrowRight :size="15" aria-hidden="true" />
        <span><ShieldCheck :size="15" aria-hidden="true" /> Provider health</span>
      </div>
    </section>

    <section class="model-metrics" aria-label="Provider authentication summary">
      <article class="nexus-card metric-card">
        <span>Providers</span>
        <strong>{{ providers.length }}</strong>
        <small>Reported by OpenClaw</small>
      </article>
      <article class="nexus-card metric-card">
        <span>Healthy</span>
        <strong>{{ healthyProviders }}</strong>
        <small>Healthy or static</small>
      </article>
      <article class="nexus-card metric-card">
        <span>Needs attention</span>
        <strong>{{ attentionProviders }}</strong>
        <small>Expiring, missing or failed</small>
      </article>
      <article class="nexus-card metric-card">
        <span>OAuth profiles</span>
        <strong>{{ oauthProfiles }}</strong>
        <small>Aggregated, no profile IDs</small>
      </article>
    </section>

    <AsyncStatePanel
      v-if="modelAuthQuery.isPending.value && !collection"
      state="loading"
      title="Provider-Status wird geladen"
      message="Nexus liest den bereinigten Authentifizierungsstatus aus OpenClaw."
    />
    <AsyncStatePanel
      v-else-if="modelAuthError && !collection"
      state="error"
      title="Provider-Status nicht verfügbar"
      :problem="modelAuthQuery.error.value"
      @action="refresh"
    />
    <AsyncStatePanel
      v-else-if="collection && collection.state !== 'ready'"
      :state="['error', 'disconnected', 'forbidden'].includes(collection.state) ? 'offline' : 'partial'"
      :title="collection.state === 'unsupported' ? 'Provider-Status nicht unterstützt' : `Provider-Status: ${collection.state}`"
      :message="collection.recovery || collection.message || 'OpenClaw hat keinen verwendbaren Provider-Snapshot geliefert.'"
      action-label="OpenClaw-Setup öffnen"
      @action="router.push('/settings')"
    />

    <template v-else-if="collection">
      <AsyncStatePanel
        v-if="modelAuthError"
        state="stale"
        title="Letzter bestätigter Provider-Stand"
        :problem="modelAuthQuery.error.value"
        message="Der Refresh ist fehlgeschlagen; der letzte bestätigte Snapshot bleibt sichtbar."
        compact
        @action="refresh"
      />

      <section class="model-toolbar nexus-panel" aria-label="Provider health filters">
        <label class="model-search">
          <span class="nexus-visually-hidden">Search providers</span>
          <Search :size="15" aria-hidden="true" />
          <input
            v-model="query"
            type="search"
            placeholder="Search provider or plan"
            autocomplete="off"
          />
        </label>
        <label class="status-filter">
          <span>Status</span>
          <select v-model="statusFilter">
            <option value="all">All states</option>
            <option v-for="status in statuses" :key="status" :value="status">
              {{ statusLabel(status) }}
            </option>
          </select>
        </label>
      </section>

      <section
        v-if="filteredProviders.length"
        class="model-grid"
        aria-label="OpenClaw model provider authentication health"
      >
        <button
          v-for="provider in filteredProviders"
          :key="provider.provider"
          type="button"
          class="model-card nexus-card nexus-card--interactive"
          :aria-label="`Open details for ${provider.displayName}, ${statusLabel(provider.status)}`"
          @click="openDetails(provider, $event)"
        >
          <span class="model-icon">
            <KeyRound v-if="provider.apiKey" :size="18" aria-hidden="true" />
            <ShieldCheck v-else :size="18" aria-hidden="true" />
          </span>
          <span class="model-copy">
            <strong>{{ provider.displayName }}</strong>
            <code>{{ provider.provider }}</code>
          </span>
          <span class="model-state nexus-status" :class="statusTone(provider.status)">
            <ShieldCheck
              v-if="provider.status === 'ok' || provider.status === 'static'"
              :size="13"
              aria-hidden="true"
            />
            <ShieldAlert v-else :size="13" aria-hidden="true" />
            {{ statusLabel(provider.status) }}
          </span>
          <span class="model-auth">{{ authenticationSummary(provider) }}</span>
          <span class="model-expiry">
            <Clock3 :size="12" aria-hidden="true" />
            {{ expirySummary(provider) }}
          </span>
          <span v-if="provider.usage?.plan" class="model-plan">{{ provider.usage.plan }}</span>
        </button>
      </section>
      <AsyncStatePanel
        v-else
        state="empty"
        :title="providers.length ? 'Keine passenden Provider' : 'Kein Provider-Status gemeldet'"
        :message="providers.length ? 'Passe Suche oder Statusfilter an.' : 'OpenClaw hat keine konfigurierten Auth-Profile gemeldet.'"
      />
    </template>

    <Teleport to="body">
      <div v-if="selectedProvider" class="model-dialog-overlay" @click.self="closeDetails">
        <section
          ref="detailsDialog"
          class="model-dialog"
          role="dialog"
          aria-modal="true"
          aria-labelledby="model-dialog-title"
          @keydown="onDialogKeydown"
        >
          <header>
            <div>
              <span class="eyebrow">{{ selectedProvider.provider }}</span>
              <h2 id="model-dialog-title">{{ selectedProvider.displayName }}</h2>
            </div>
            <button
              ref="detailsCloseButton"
              type="button"
              aria-label="Close provider details"
              @click="closeDetails"
            >
              <X :size="18" aria-hidden="true" />
            </button>
          </header>

          <dl>
            <div>
              <dt>Authentication health</dt>
              <dd>
                <span class="nexus-status" :class="statusTone(selectedProvider.status)">
                  {{ statusLabel(selectedProvider.status) }}
                </span>
              </dd>
            </div>
            <div><dt>Provider</dt><dd><code>{{ selectedProvider.provider }}</code></dd></div>
            <div><dt>Credential expiry</dt><dd>{{ formattedExpiry(selectedProvider) }}</dd></div>
            <div><dt>API-key source</dt><dd>{{ apiKeySummary(selectedProvider) }}</dd></div>
            <div><dt>Usage plan</dt><dd>{{ selectedProvider.usage?.plan || 'Not reported' }}</dd></div>
            <div><dt>Usage summary</dt><dd>{{ selectedProvider.usage?.summary || 'Not reported' }}</dd></div>
          </dl>

          <section class="profile-summary" aria-labelledby="profile-summary-title">
            <h3 id="profile-summary-title">Aggregated authentication profiles</h3>
            <ul v-if="selectedProvider.profiles.length">
              <li
                v-for="profile in selectedProvider.profiles"
                :key="`${profile.type}:${profile.status}`"
              >
                <span>{{ profileTypeLabel(profile.type) }}</span>
                <strong>{{ profile.count }}</strong>
                <span class="nexus-status" :class="statusTone(profile.status)">
                  {{ statusLabel(profile.status) }}
                </span>
              </li>
            </ul>
            <p v-else>No profile-backed authentication was reported.</p>
          </section>

          <p class="model-note">
            This view receives aggregate health only. Credential values, profile IDs, account
            identities and billing details remain inside OpenClaw.
          </p>
          <div class="dialog-actions">
            <RouterLink class="nexus-button" to="/settings" @click="closeDetails">
              Open setup center
            </RouterLink>
            <button type="button" class="nexus-button nexus-button--primary" @click="closeDetails">
              Done
            </button>
          </div>
        </section>
      </div>
    </Teleport>
  </div>
</template>

<style scoped>
.models-page { padding-bottom: 30px; }

.model-route {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 22px;
  background: linear-gradient(135deg, var(--glass), var(--grad-soft));
}

.model-route h2 { margin: 3px 0 6px; font-size: 18px; }
.model-route p { max-width: 680px; margin: 0; color: var(--tx-2); line-height: 1.55; }
.model-route code { color: var(--a-mid); font-family: var(--font-mono-v2); font-size: 12px; }
.route-kicker { color: var(--a-mid); font-family: var(--font-mono-v2); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; }
.route-flow { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; color: var(--tx-3); }
.route-flow span { display: inline-flex; align-items: center; gap: 7px; min-height: 36px; padding: 0 11px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--glass); color: var(--tx-2); }
.route-flow .route-primary { border-color: var(--line-3); background: var(--grad-soft); color: var(--tx); }

.model-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.metric-card { display: grid; gap: 4px; padding: 15px; }
.metric-card span, .metric-card small { color: var(--tx-3); }
.metric-card strong { font-family: var(--font-display); font-size: 24px; line-height: 1.1; }
.metric-card small { font-family: var(--font-mono-v2); font-size: 11px; }

.refresh-warning { display: flex; align-items: center; min-height: auto; padding: 12px 14px; }
.refresh-warning p { margin: 0; }
.state-actions { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; }

.model-toolbar { display: flex; align-items: center; gap: 12px; padding: 12px; }
.model-search { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 220px; padding-left: 11px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--field-surface); color: var(--tx-3); }
.model-search:focus-within { border-color: var(--a-blue); box-shadow: var(--focus-ring); }
.model-search input { flex: 1; min-height: 38px; padding: 0 10px 0 0; border: 0; background: transparent; box-shadow: none; }
.status-filter { display: flex; align-items: center; gap: 9px; color: var(--tx-3); }
.status-filter span { font-family: var(--font-mono-v2); font-size: 11px; text-transform: uppercase; }
.status-filter select { min-width: 150px; }

.model-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.model-card { display: grid; grid-template-columns: 38px minmax(0, 1fr) auto; gap: 10px; align-items: center; width: 100%; padding: 15px; color: inherit; text-align: left; cursor: pointer; }
.model-icon { width: 38px; height: 38px; display: grid; place-items: center; border: 1px solid var(--line-2); border-radius: var(--r-sm); background: var(--grad-soft); color: var(--a-mid); }
.model-copy { display: grid; gap: 3px; overflow: hidden; }
.model-copy strong { overflow: hidden; color: var(--tx); text-overflow: ellipsis; white-space: nowrap; }
.model-copy code { overflow: hidden; color: var(--tx-3); font-family: var(--font-mono-v2); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
.model-state { justify-self: end; font-size: 11px; }
.model-auth, .model-expiry, .model-plan { color: var(--tx-3); font-family: var(--font-mono-v2); font-size: 11px; }
.model-auth { grid-column: 2; }
.model-expiry { grid-column: 2; display: inline-flex; align-items: center; gap: 5px; }
.model-plan { grid-column: 3; grid-row: 3 / 5; align-self: end; max-width: 110px; overflow: hidden; text-align: right; text-overflow: ellipsis; white-space: nowrap; }

.model-dialog-overlay { position: fixed; inset: 0; z-index: 300; display: grid; place-items: center; padding: 20px; background: color-mix(in srgb, var(--space-0) 78%, transparent); backdrop-filter: blur(12px); }
.model-dialog { width: min(100%, 620px); max-height: min(780px, calc(100dvh - 40px)); overflow: auto; border: 1px solid var(--line-3); border-radius: var(--r-lg); background: color-mix(in srgb, var(--space-2) 94%, transparent); box-shadow: var(--panel-shadow); }
.model-dialog > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 18px; border-bottom: 1px solid var(--line); }
.model-dialog h2 { margin: 3px 0 0; font-family: var(--font-display); font-size: 20px; }
.model-dialog header button { width: 36px; height: 36px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--accent-wash); color: var(--tx-2); cursor: pointer; }
.model-dialog dl { display: grid; gap: 0; margin: 0; padding: 8px 18px; }
.model-dialog dl div { display: grid; grid-template-columns: 155px minmax(0, 1fr); gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line); }
.model-dialog dl div:last-child { border-bottom: 0; }
.model-dialog dt { color: var(--tx-3); font-family: var(--font-mono-v2); font-size: 11px; text-transform: uppercase; }
.model-dialog dd { margin: 0; color: var(--tx-2); overflow-wrap: anywhere; }
.model-dialog code { font-family: var(--font-mono-v2); font-size: 11px; }
.profile-summary { margin: 4px 18px 14px; padding: 13px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--glass); }
.profile-summary h3 { margin: 0 0 10px; font-family: var(--font-display); font-size: 14px; }
.profile-summary ul { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
.profile-summary li { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 10px; color: var(--tx-2); }
.profile-summary p { margin: 0; color: var(--tx-3); }
.model-note { margin: 0 18px 12px; padding: 12px; border: 1px solid var(--line); border-radius: var(--r-sm); color: var(--tx-2); line-height: 1.55; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 10px; padding: 4px 18px 18px; }
.spin { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

@media (max-width: 1100px) {
  .model-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .model-route { align-items: flex-start; flex-direction: column; }
}

@media (max-width: 760px) {
  .model-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .model-grid { grid-template-columns: 1fr; }
  .model-toolbar { align-items: stretch; flex-direction: column; }
  .status-filter { justify-content: space-between; }
  .route-flow { max-width: 100%; flex-wrap: wrap; }
}

@media (max-width: 440px) {
  .model-plan { display: none; }
  .model-card { grid-template-columns: 38px minmax(0, 1fr); }
  .model-state { grid-column: 1 / -1; justify-self: start; }
  .model-dialog-overlay { padding: 10px; }
  .model-dialog { max-height: calc(100dvh - 20px); }
  .model-dialog dl div { grid-template-columns: 1fr; gap: 4px; }
  .profile-summary li { grid-template-columns: minmax(0, 1fr) auto; }
  .profile-summary li .nexus-status { grid-column: 1 / -1; justify-self: start; }
  .dialog-actions { align-items: stretch; flex-direction: column-reverse; }
}
</style>
