<template>
  <div class="p-4 sm:p-6 max-w-5xl mx-auto">

    <!-- Header -->
    <div class="mb-5 flex items-start justify-between gap-4 flex-wrap">
      <div>
        <h1 class="text-text-primary text-xl font-semibold mb-1">Security Alerts</h1>
        <p class="text-text-dim text-sm">
          Anomaly detections from the scoring pipeline.
          Acknowledge entries after review to track your triage state.
        </p>
      </div>

      <!-- Scorer controls -->
      <div class="flex items-center gap-3 shrink-0 flex-wrap">
        <!-- Status badge -->
        <span
          v-if="scorerStatus"
          :class="[
            'text-xs px-2 py-1 rounded border font-mono',
            scorerStatus.enabled
              ? scorerStatus.running
                ? 'border-accent text-accent animate-pulse'
                : 'border-surface-border text-text-dim'
              : 'border-surface-border text-text-dim opacity-60'
          ]"
          :title="scorerStatus.enabled ? `model: ${scorerStatus.model}` : 'TURNSTONE_ANOMALY_MODEL not set'"
        >
          {{ scorerStatus.running ? 'scoring…' : scorerStatus.enabled ? 'scorer ready' : 'scorer off' }}
        </span>

        <!-- Cybersec scorer status -->
        <span
          v-if="cybersecStatus"
          :class="[
            'text-xs px-2 py-1 rounded border font-mono',
            cybersecStatus.enabled
              ? cybersecStatus.running
                ? 'border-accent text-accent animate-pulse'
                : 'border-surface-border text-text-dim'
              : 'border-surface-border text-text-dim opacity-40'
          ]"
          :title="cybersecStatus.enabled ? `cybersec: ${cybersecStatus.model}` : 'TURNSTONE_CYBERSEC_MODEL not set'"
        >
          {{ cybersecStatus.running ? 'cybersec scoring…' : cybersecStatus.enabled ? 'cybersec on' : 'cybersec off' }}
        </span>

        <button
          @click="runScorer"
          :disabled="!scorerStatus?.enabled || triggerLoading || scorerStatus?.running"
          class="px-3 py-1.5 bg-accent text-surface text-xs rounded font-medium hover:opacity-90 transition-opacity disabled:opacity-40"
          title="Manually trigger an anomaly scoring pass"
        >
          {{ triggerLoading ? 'triggering…' : 'Run anomaly' }}
        </button>

        <button
          @click="runCybersec"
          :disabled="!cybersecStatus?.enabled || cybersecTriggerLoading || cybersecStatus?.running"
          class="px-3 py-1.5 bg-accent text-surface text-xs rounded font-medium hover:opacity-90 transition-opacity disabled:opacity-40"
          title="Manually trigger a cybersec scoring pass"
        >
          {{ cybersecTriggerLoading ? 'triggering…' : 'Run cybersec' }}
        </button>
      </div>
    </div>

    <!-- Scorer config warning (no model set) -->
    <div
      v-if="scorerStatus && !scorerStatus.enabled"
      class="mb-5 px-4 py-3 rounded border border-sev-warn/40 bg-surface-raised text-sev-warn text-sm"
    >
      Anomaly scoring is disabled — set <code class="font-mono text-xs bg-surface px-1 py-0.5 rounded">TURNSTONE_ANOMALY_MODEL</code>
      in your <code class="font-mono text-xs bg-surface px-1 py-0.5 rounded">.env</code> and restart Turnstone.
    </div>

    <!-- Stats row -->
    <div class="mb-5 flex flex-wrap gap-x-6 gap-y-2 text-xs text-text-dim">
      <template v-if="scorerStatus?.enabled">
        <span class="text-text-dim/60 uppercase tracking-wider font-medium">Anomaly:</span>
        <span>scored <span class="text-text-primary font-mono">{{ scorerStatus.total_scored ?? '—' }}</span></span>
        <span>detections <span class="text-text-primary font-mono">{{ scorerStatus.total_detections ?? '—' }}</span></span>
        <span v-if="scorerStatus.last_run_at">
          last run <span class="text-text-primary font-mono">{{ formatTs(scorerStatus.last_run_at) }}</span>
        </span>
        <span v-if="scorerStatus.last_error" class="text-sev-error">error: {{ scorerStatus.last_error }}</span>
      </template>
      <template v-if="cybersecStatus?.enabled">
        <span class="text-text-dim/60 uppercase tracking-wider font-medium ml-2">Cybersec:</span>
        <span>scored <span class="text-text-primary font-mono">{{ cybersecStatus.total_scored ?? '—' }}</span></span>
        <span>detections <span class="text-text-primary font-mono">{{ cybersecStatus.total_detections ?? '—' }}</span></span>
        <span v-if="cybersecStatus.last_run_at">
          last run <span class="text-text-primary font-mono">{{ formatTs(cybersecStatus.last_run_at) }}</span>
        </span>
        <span v-if="cybersecStatus.last_error" class="text-sev-error">error: {{ cybersecStatus.last_error }}</span>
      </template>
    </div>

    <!-- Filter / Tab bar -->
    <div class="mb-4 flex flex-col sm:flex-row sm:items-center gap-3">
      <!-- Tabs -->
      <div role="tablist" aria-label="Filter by acknowledgement" class="flex gap-1 border-b border-surface-border flex-1">
        <button
          v-for="(tab, idx) in tabs"
          :key="tab.value"
          role="tab"
          :aria-selected="activeTab === tab.value"
          :tabindex="activeTab === tab.value ? 0 : -1"
          @click="activeTab = tab.value as 'all' | 'unacked'; loadDetections()"
          @keydown="handleTabKey($event, idx)"
          :ref="(el) => collectTabRef(el as HTMLElement | null, idx)"
          :class="[
            'px-4 py-2 text-sm transition-colors border-b-2 -mb-px whitespace-nowrap',
            activeTab === tab.value
              ? 'border-accent text-accent'
              : 'border-transparent text-text-dim hover:text-text-primary'
          ]"
        >
          {{ tab.label }}
          <span v-if="tab.count !== null" class="ml-1 text-xs opacity-70">({{ tab.count }})</span>
        </button>
      </div>

      <!-- Scorer filter -->
      <div class="flex items-center gap-2 shrink-0">
        <label for="scorer-filter" class="text-xs text-text-dim whitespace-nowrap">Source:</label>
        <select
          id="scorer-filter"
          v-model="scorerFilter"
          @change="loadDetections()"
          class="text-xs bg-surface border border-surface-border rounded px-2 py-1 text-text-primary focus:outline-none focus:border-accent"
        >
          <option value="">All</option>
          <option value="anomaly">Anomaly scorer</option>
          <option value="cybersec">Cybersec scorer</option>
        </select>
      </div>

      <!-- Label filter -->
      <div class="flex items-center gap-2 shrink-0">
        <label for="label-filter" class="text-xs text-text-dim whitespace-nowrap">Label:</label>
        <select
          id="label-filter"
          v-model="labelFilter"
          @change="loadDetections()"
          class="text-xs bg-surface border border-surface-border rounded px-2 py-1 text-text-primary focus:outline-none focus:border-accent"
        >
          <option value="">All</option>
          <optgroup label="Anomaly labels">
            <option v-for="lbl in anomalyLabels" :key="lbl" :value="lbl">{{ lbl }}</option>
          </optgroup>
          <optgroup label="Cybersec labels">
            <option v-for="lbl in cybersecLabels" :key="lbl" :value="lbl">{{ lbl }}</option>
          </optgroup>
        </select>
      </div>
    </div>

    <!-- Loading state -->
    <div v-if="loading" class="text-text-dim py-12 text-center text-sm">Loading…</div>

    <!-- Empty state -->
    <div v-else-if="detections.length === 0" class="text-text-dim py-12 text-center text-sm">
      <p v-if="activeTab === 'unacked'">No unacknowledged detections — all clear.</p>
      <p v-else-if="!scorerStatus?.enabled">Enable anomaly scoring to start detecting.</p>
      <p v-else>No detections yet. Run the scorer after gleaning to populate this list.</p>
    </div>

    <!-- Detections table -->
    <div v-else class="rounded border border-surface-border overflow-hidden">
      <div class="overflow-x-auto">
        <table class="w-full text-sm min-w-[700px]">
          <thead class="bg-surface-raised border-b border-surface-border">
            <tr>
              <th class="text-left px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider w-20">Sev</th>
              <th class="text-left px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider">Label</th>
              <th class="text-left px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider w-16">Score</th>
              <th class="text-left px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider">Source</th>
              <th class="text-left px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider">Log entry</th>
              <th class="text-left px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider w-32">Detected</th>
              <th class="px-4 py-2.5 w-28"></th>
            </tr>
          </thead>
          <tbody>
            <tr
              v-for="det in filteredDetections"
              :key="det.id"
              :class="[
                'border-b border-surface-border transition-colors cursor-pointer',
                det.acknowledged ? 'opacity-50 hover:opacity-75' : 'hover:bg-surface-raised'
              ]"
              @click="openDrawer(det)"
            >
              <td class="px-4 py-2.5">
                <span :class="['text-xs font-semibold', severityTextClass(det.severity)]">
                  {{ det.severity }}
                </span>
              </td>
              <td class="px-4 py-2.5">
                <div class="flex items-center gap-1.5 flex-wrap">
                  <span class="font-mono text-xs text-accent bg-surface px-1.5 py-0.5 rounded border border-surface-border">
                    {{ det.anomaly_label }}
                  </span>
                  <span
                    v-if="det.scorer === 'cybersec'"
                    class="text-xs px-1.5 py-0.5 rounded bg-surface-raised border border-surface-border text-text-dim font-mono"
                  >cybersec</span>
                </div>
              </td>
              <td class="px-4 py-2.5">
                <div class="flex items-center gap-1.5">
                  <div class="w-10 h-1.5 bg-surface-raised rounded-full overflow-hidden">
                    <div
                      class="h-full rounded-full"
                      :class="scoreBarColor(det.anomaly_score)"
                      :style="{ width: `${Math.round(det.anomaly_score * 100)}%` }"
                    ></div>
                  </div>
                  <span class="text-xs text-text-dim font-mono">{{ Math.round(det.anomaly_score * 100) }}%</span>
                </div>
              </td>
              <td class="px-4 py-2.5 text-text-dim text-xs font-mono truncate max-w-[120px]">{{ det.source_id }}</td>
              <td class="px-4 py-2.5 text-text-dim text-xs truncate max-w-[260px]" :title="det.text">{{ det.text }}</td>
              <td class="px-4 py-2.5 text-text-dim text-xs whitespace-nowrap">{{ formatTs(det.detected_at) }}</td>
              <td class="px-4 py-2.5 text-right">
                <span
                  v-if="det.acknowledged"
                  class="text-xs text-text-dim italic"
                >reviewed</span>
                <button
                  v-else
                  @click.stop="openDrawer(det)"
                  class="text-xs px-2 py-1 rounded border border-surface-border text-text-dim hover:text-text-primary hover:border-accent transition-colors"
                >
                  Acknowledge
                </button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>

    <!-- Acknowledge drawer -->
    <Transition name="drawer">
      <div v-if="drawer" class="mt-6 rounded border border-accent bg-surface p-5">
        <div class="flex items-start justify-between mb-4 gap-4">
          <div class="min-w-0">
            <div class="flex items-center gap-2 flex-wrap mb-1">
              <span :class="['text-xs font-semibold', severityTextClass(drawer.severity)]">{{ drawer.severity }}</span>
              <span class="font-mono text-xs text-accent bg-surface-raised px-1.5 py-0.5 rounded border border-surface-border">
                {{ drawer.anomaly_label }}
              </span>
              <span class="text-xs text-text-dim font-mono">{{ Math.round(drawer.anomaly_score * 100) }}% confidence</span>
            </div>
            <p class="text-text-dim text-xs font-mono">
              source: {{ drawer.source_id }}
              <span v-if="drawer.timestamp_iso"> · {{ formatTs(drawer.timestamp_iso) }}</span>
            </p>
          </div>
          <button
            @click="drawer = null"
            class="text-text-dim hover:text-text-primary transition-colors shrink-0 text-lg leading-none"
            aria-label="Close drawer"
          >✕</button>
        </div>

        <!-- Full log text -->
        <div class="mb-4 bg-surface-raised rounded border border-surface-border p-3 text-xs font-mono text-text-primary break-all leading-relaxed max-h-40 overflow-y-auto">
          {{ drawer.text }}
        </div>

        <!-- Already acknowledged -->
        <div v-if="drawer.acknowledged" class="text-text-dim text-sm">
          <p class="mb-1">Acknowledged <span class="text-text-primary">{{ formatTs(drawer.acknowledged_at) }}</span></p>
          <p v-if="drawer.notes" class="text-xs italic">{{ drawer.notes }}</p>
        </div>

        <!-- Acknowledge form -->
        <div v-else>
          <label for="ack-notes" class="block text-xs text-text-dim mb-1.5">Notes (optional)</label>
          <textarea
            id="ack-notes"
            v-model="ackNotes"
            rows="2"
            placeholder="False positive, known pattern, remediated…"
            class="w-full bg-surface border border-surface-border rounded px-3 py-2 text-sm text-text-primary placeholder-text-dim focus:outline-none focus:border-accent resize-none mb-3"
          ></textarea>
          <div class="flex items-center gap-3">
            <button
              @click="acknowledge(drawer)"
              :disabled="ackLoading"
              class="px-4 py-2 bg-accent text-surface text-sm rounded font-medium hover:opacity-90 transition-opacity disabled:opacity-40"
            >
              {{ ackLoading ? 'Saving…' : 'Mark as reviewed' }}
            </button>
            <button
              @click="drawer = null"
              class="px-4 py-2 text-text-dim text-sm rounded border border-surface-border hover:text-text-primary transition-colors"
            >
              Cancel
            </button>
            <span v-if="ackError" class="text-xs text-sev-error">{{ ackError }}</span>
          </div>
        </div>
      </div>
    </Transition>

  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'

const BASE = import.meta.env.BASE_URL.replace(/\/$/, '')

// ── Types ───────────────────────────────────────────────────────────────────

interface Detection {
  id: string
  source_id: string
  entry_id: string
  anomaly_label: string
  anomaly_score: number
  severity: string
  text: string
  timestamp_iso: string | null
  detected_at: string
  acknowledged: number | boolean
  acknowledged_at: string | null
  notes: string
  scorer: string
}

interface ScorerStatus {
  enabled: boolean
  running: boolean
  model: string | null
  threshold: number
  device: string
  last_run_at: string | null
  last_scored: number
  last_detections: number
  last_error: string | null
  total_scored: number
  total_detections: number
}

interface CybersecStatus {
  enabled: boolean
  running: boolean
  model: string | null
  threshold: number
  device: string
  last_run_at: string | null
  last_duration_s: number | null
  last_scored: number
  last_detections: number
  last_error: string | null
  total_scored: number
  total_detections: number
}

// ── State ────────────────────────────────────────────────────────────────────

const detections          = ref<Detection[]>([])
const scorerStatus        = ref<ScorerStatus | null>(null)
const cybersecStatus      = ref<CybersecStatus | null>(null)
const loading             = ref(true)
const triggerLoading      = ref(false)
const cybersecTriggerLoading = ref(false)
const ackLoading          = ref(false)
const ackError       = ref<string | null>(null)
const ackNotes       = ref('')
const drawer         = ref<Detection | null>(null)
const activeTab      = ref<'all' | 'unacked'>('all')
const labelFilter    = ref('')
const scorerFilter   = ref('')
const tabRefs        = ref<(HTMLElement | null)[]>([])

const anomalyLabels = [
  'SECURITY_ANOMALY', 'SYSTEM_FAILURE', 'PERFORMANCE_ISSUE',
  'NETWORK_ANOMALY', 'CONFIG_ERROR', 'HARDWARE_ISSUE',
  'CRITICAL', 'ERROR',
]

const cybersecLabels = [
  'authentication failure or brute force attack',
  'privilege escalation or unauthorized access',
  'network intrusion or port scan',
  'malware or suspicious process activity',
  'data exfiltration or unusual outbound traffic',
]

// ── Tabs ─────────────────────────────────────────────────────────────────────

const unackedCount = computed(() => detections.value.filter(d => !d.acknowledged).length)

const tabs = computed(() => [
  { value: 'all',    label: 'All',              count: detections.value.length },
  { value: 'unacked', label: 'Unacknowledged', count: unackedCount.value },
])

const filteredDetections = computed(() =>
  activeTab.value === 'unacked'
    ? detections.value.filter(d => !d.acknowledged)
    : detections.value
)

// ── Data loading ─────────────────────────────────────────────────────────────

async function loadDetections() {
  loading.value = true
  const params = new URLSearchParams({ limit: '200' })
  if (labelFilter.value) params.set('label', labelFilter.value)
  if (scorerFilter.value) params.set('scorer', scorerFilter.value)
  try {
    const res = await fetch(`${BASE}/api/anomaly/detections?${params}`)
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    const data = await res.json()
    detections.value = (data.detections ?? []).map((d: Detection) => ({
      ...d,
      acknowledged: !!d.acknowledged,
    }))
  } catch (e) {
    console.error('Failed to load detections', e)
  } finally {
    loading.value = false
  }
}

async function loadScorerStatus() {
  try {
    const [anomalyRes, cybersecRes] = await Promise.all([
      fetch(`${BASE}/api/anomaly/status`),
      fetch(`${BASE}/api/cybersec/status`),
    ])
    if (anomalyRes.ok) {
      scorerStatus.value = await anomalyRes.json()
    }
    if (cybersecRes.ok) {
      const data = await cybersecRes.json()
      cybersecStatus.value = data
    }
  } catch {
    // scorer status is non-critical — fail silently
  }
}

onMounted(() => {
  loadScorerStatus()
  loadDetections()
})

// ── Actions ──────────────────────────────────────────────────────────────────

async function runScorer() {
  triggerLoading.value = true
  try {
    await fetch(`${BASE}/api/anomaly/run`, { method: 'POST' })
    setTimeout(() => { loadScorerStatus(); loadDetections() }, 2000)
  } finally {
    triggerLoading.value = false
  }
}

async function runCybersec() {
  cybersecTriggerLoading.value = true
  try {
    await fetch(`${BASE}/api/cybersec/run`, { method: 'POST' })
    setTimeout(() => { loadScorerStatus(); loadDetections() }, 2000)
  } finally {
    cybersecTriggerLoading.value = false
  }
}

function openDrawer(det: Detection) {
  ackNotes.value = det.notes ?? ''
  ackError.value = null
  drawer.value = det
}

async function acknowledge(det: Detection) {
  ackLoading.value = true
  ackError.value = null
  try {
    const params = new URLSearchParams()
    if (ackNotes.value.trim()) params.set('notes', ackNotes.value.trim())
    const res = await fetch(
      `${BASE}/api/anomaly/detections/${det.id}/acknowledge?${params}`,
      { method: 'POST' }
    )
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    // update in-place so the row dims without a full reload
    const idx = detections.value.findIndex(d => d.id === det.id)
    const existing = idx !== -1 ? detections.value[idx] : null
    if (existing) {
      detections.value.splice(idx, 1, { ...existing, acknowledged: true, notes: ackNotes.value.trim() })
    }
    drawer.value = null
    ackNotes.value = ''
    loadScorerStatus()
  } catch (e) {
    ackError.value = 'Failed to save — try again'
    console.error(e)
  } finally {
    ackLoading.value = false
  }
}

// ── Helpers ──────────────────────────────────────────────────────────────────

function severityTextClass(sev: string | null): string {
  return ({
    CRITICAL: 'text-sev-critical',
    ERROR:    'text-sev-error',
    WARN:     'text-sev-warn',
    WARNING:  'text-sev-warn',
    INFO:     'text-sev-info',
    DEBUG:    'text-text-dim',
  } as Record<string, string>)[sev?.toUpperCase() ?? ''] ?? 'text-text-dim'
}

function scoreBarColor(score: number): string {
  if (score >= 0.90) return 'bg-sev-critical'
  if (score >= 0.80) return 'bg-sev-error'
  if (score >= 0.65) return 'bg-sev-warn'
  return 'bg-sev-info'
}

function formatTs(iso: string | null): string {
  if (!iso) return '—'
  try {
    return new Date(iso).toLocaleString(undefined, {
      month: 'short', day: 'numeric',
      hour: '2-digit', minute: '2-digit',
    })
  } catch { return iso }
}

// ── Keyboard nav for tabs ─────────────────────────────────────────────────────

function collectTabRef(el: HTMLElement | null, idx: number) {
  tabRefs.value[idx] = el
}

function handleTabKey(e: KeyboardEvent, idx: number) {
  const count = tabs.value.length
  let next = idx
  if (e.key === 'ArrowRight') next = (idx + 1) % count
  else if (e.key === 'ArrowLeft') next = (idx - 1 + count) % count
  else return
  e.preventDefault()
  tabRefs.value[next]?.focus()
}
</script>

<style scoped>
.drawer-enter-active,
.drawer-leave-active { transition: opacity 0.15s, transform 0.15s; }
.drawer-enter-from,
.drawer-leave-to    { opacity: 0; transform: translateY(-6px); }
</style>
