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

    <!-- Header -->
    <div class="mb-6 flex items-start justify-between gap-4">
      <div>
        <h1 class="text-text-primary text-xl font-semibold mb-1">Blocklist</h1>
        <p class="text-text-dim text-sm">
          Review destinations flagged from router logs and push approved entries to Pi-hole.
        </p>
      </div>
      <div class="flex items-center gap-3 shrink-0">
        <button
          @click="testConnection"
          :class="[
            'text-xs px-2 py-1 rounded border transition-colors',
            connectionStatus === null ? 'border-surface-border text-text-dim hover:text-text-primary' :
            connectionStatus ? 'border-green-600 text-green-400' : 'border-sev-error text-sev-error'
          ]"
          :title="connectionDetail || 'Click to test Pi-hole connection'"
        >
          Pi-hole
          <span v-if="connectionStatus === null">●</span>
          <span v-else-if="connectionStatus">✓</span>
          <span v-else>✗</span>
        </button>
        <button
          @click="scan"
          :disabled="scanning"
          class="px-4 py-2 bg-accent text-surface text-sm rounded font-medium hover:opacity-90 transition-opacity disabled:opacity-40"
        >
          {{ scanning ? 'Scanning…' : 'Scan now' }}
        </button>
      </div>
    </div>

    <!-- Status filter tabs -->
    <div role="tablist" aria-label="Filter by status" class="flex gap-1 mb-6 border-b border-surface-border">
      <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"
        @keydown="handleTabKey($event, idx)"
        :ref="(el) => collectTabRef(el, idx)"
        :class="[
          'px-4 py-2 text-sm transition-colors border-b-2 -mb-px',
          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>

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

    <div v-else-if="filteredCandidates.length === 0" class="text-text-dim py-12 text-center text-sm">
      <p v-if="activeTab === 'pending'">No pending candidates. Run a scan to find new destinations.</p>
      <p v-else>No candidates with status "{{ activeTab }}".</p>
    </div>

    <!-- Candidates grouped by device -->
    <div v-else class="space-y-6">
      <div
        v-for="(group, deviceIp) in groupedCandidates"
        :key="String(deviceIp)"
        class="rounded border border-surface-border overflow-hidden"
      >
        <div class="bg-surface-raised border-b border-surface-border px-4 py-2.5 text-xs text-text-dim font-medium">
          {{ group.name }} <span class="text-text-dim font-normal ml-1">({{ deviceIp }})</span>
        </div>
        <div class="divide-y divide-surface-border">
          <div
            v-for="c in group.candidates"
            :key="c.id"
            class="px-4 py-3"
          >
            <div class="flex items-center gap-3 flex-wrap">
              <span class="font-mono text-sm text-text-primary flex-1 min-w-0 truncate">{{ c.domain_or_ip }}</span>
              <span
                v-if="c.matched_rule"
                class="text-xs px-1.5 py-0.5 rounded bg-surface border border-surface-border text-text-dim font-mono shrink-0"
              >{{ c.matched_rule }}</span>
              <span class="text-xs text-text-dim shrink-0">{{ c.hit_count }} hit{{ c.hit_count !== 1 ? 's' : '' }}</span>
              <span class="text-xs text-text-dim shrink-0">{{ shortDate(c.first_seen) }} → {{ shortDate(c.last_seen) }}</span>

              <!-- Pending actions -->
              <template v-if="c.status === 'pending'">
                <button
                  @click="approve(c)"
                  :aria-label="`Approve ${c.domain_or_ip}`"
                  class="text-xs px-3 py-1 rounded border border-green-600/50 text-green-400 hover:bg-green-600/10 transition-colors"
                >Approve</button>
                <button
                  @click="reject(c)"
                  :aria-label="`Reject ${c.domain_or_ip}`"
                  class="text-xs px-3 py-1 rounded border border-surface-border text-text-dim hover:text-sev-error hover:border-sev-error transition-colors"
                >Reject</button>
              </template>

              <!-- Approved actions -->
              <template v-else-if="c.status === 'approved'">
                <span class="text-xs text-green-400">Approved</span>
                <button
                  @click="push(c)"
                  :aria-label="`Push ${c.domain_or_ip} to Pi-hole`"
                  class="text-xs px-3 py-1 rounded bg-accent text-surface hover:opacity-90 transition-opacity"
                >Push to Pi-hole</button>
              </template>

              <!-- Pushed actions -->
              <template v-else-if="c.status === 'pushed'">
                <span class="text-xs text-accent">Blocked</span>
                <button
                  @click="unblock(c)"
                  :aria-label="`Unblock ${c.domain_or_ip}`"
                  class="text-xs px-3 py-1 rounded border border-surface-border text-text-dim hover:text-text-primary transition-colors"
                >Unblock</button>
              </template>

              <!-- Other statuses -->
              <template v-else>
                <span class="text-xs text-text-dim capitalize">{{ c.status }}</span>
              </template>
            </div>

            <!-- Log evidence -->
            <div v-if="c.log_evidence.length" class="mt-1">
              <button
                @click="toggleEvidence(c.id)"
                :aria-expanded="expandedEvidence.has(c.id)"
                class="text-xs text-text-dim hover:text-text-primary transition-colors"
              >
                {{ expandedEvidence.has(c.id) ? '▾' : '▸' }} log evidence ({{ c.log_evidence.length }})
              </button>
              <div v-if="expandedEvidence.has(c.id)" class="mt-1 text-xs font-mono text-text-dim bg-surface rounded p-2 space-y-0.5">
                <p v-for="eid in c.log_evidence.slice(0, 3)" :key="eid" class="truncate">{{ eid }}</p>
              </div>
            </div>

            <!-- Inline error -->
            <p v-if="errors[c.id]" class="mt-1 text-xs text-sev-error">{{ errors[c.id] }}</p>
          </div>
        </div>
      </div>
    </div>

    <!-- Global scan error -->
    <div v-if="scanError" role="alert" class="mt-4 text-sm text-sev-error">{{ scanError }}</div>
  </div>
</template>

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

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

interface Candidate {
  id: string
  domain_or_ip: string
  source_device_ip: string | null
  source_device_name: string | null
  first_seen: string
  last_seen: string
  hit_count: number
  status: 'pending' | 'approved' | 'rejected' | 'pushed' | 'unblocked'
  pushed_at: string | null
  log_evidence: string[]
  matched_rule: string | null
  llm_score: number | null
  llm_reason: string | null
}

const candidates       = ref<Candidate[]>([])
const loading          = ref(true)
const scanning         = ref(false)
const scanError        = ref<string | null>(null)
const activeTab        = ref('pending')
const expandedEvidence = ref(new Set<string>())
const errors           = ref<Record<string, string>>({})
const connectionStatus = ref<boolean | null>(null)
const connectionDetail = ref('')
const tabBtnRefs       = ref<HTMLButtonElement[]>([])

const tabDefs = [
  { value: 'pending',   label: 'Pending'   },
  { value: 'approved',  label: 'Approved'  },
  { value: 'pushed',    label: 'Pushed'    },
  { value: 'rejected',  label: 'Rejected'  },
  { value: 'unblocked', label: 'Unblocked' },
  { value: 'all',       label: 'All'       },
]

const tabs = computed(() =>
  tabDefs.map(t => ({
    ...t,
    count: ['pending', 'approved', 'pushed'].includes(t.value)
      ? candidates.value.filter(c => c.status === t.value).length
      : null,
  }))
)

const filteredCandidates = computed(() =>
  activeTab.value === 'all'
    ? candidates.value
    : candidates.value.filter(c => c.status === activeTab.value)
)

const groupedCandidates = computed(() => {
  const groups: Record<string, { name: string; candidates: Candidate[] }> = {}
  for (const c of filteredCandidates.value) {
    const key = c.source_device_ip ?? 'unknown'
    if (!groups[key]) groups[key] = { name: c.source_device_name ?? key, candidates: [] }
    groups[key]!.candidates.push(c)
  }
  return groups
})

async function load() {
  loading.value = true
  try {
    const res = await fetch(`${BASE}/api/blocklist/candidates`)
    if (res.ok) candidates.value = (await res.json()).candidates
  } finally {
    loading.value = false
  }
}

async function scan() {
  scanning.value = true
  scanError.value = null
  try {
    const res = await fetch(`${BASE}/api/blocklist/scan`, { method: 'POST' })
    if (!res.ok) throw new Error((await res.json()).detail ?? res.statusText)
    setTimeout(load, 1500)
  } catch (e) {
    scanError.value = e instanceof Error ? e.message : 'Scan failed'
  } finally {
    scanning.value = false
  }
}

async function approve(c: Candidate) { await updateStatus(c, 'approved') }
async function reject(c: Candidate)  { await updateStatus(c, 'rejected') }

async function updateStatus(c: Candidate, status: string) {
  errors.value[c.id] = ''
  try {
    const res = await fetch(`${BASE}/api/blocklist/candidates/${c.id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ status }),
    })
    if (!res.ok) throw new Error((await res.json()).detail ?? res.statusText)
    const updated: Candidate = await res.json()
    candidates.value = candidates.value.map(x => x.id === c.id ? updated : x)
  } catch (e) {
    errors.value[c.id] = e instanceof Error ? e.message : 'Update failed'
  }
}

async function push(c: Candidate) {
  errors.value[c.id] = ''
  try {
    const res = await fetch(`${BASE}/api/blocklist/push/${c.id}`, { method: 'POST' })
    if (!res.ok) throw new Error((await res.json()).detail ?? res.statusText)
    await load()
  } catch (e) {
    errors.value[c.id] = e instanceof Error ? e.message : 'Push failed'
  }
}

async function unblock(c: Candidate) {
  errors.value[c.id] = ''
  try {
    const res = await fetch(`${BASE}/api/blocklist/push/${c.id}`, { method: 'DELETE' })
    if (!res.ok) throw new Error((await res.json()).detail ?? res.statusText)
    await load()
  } catch (e) {
    errors.value[c.id] = e instanceof Error ? e.message : 'Unblock failed'
  }
}

async function testConnection() {
  connectionStatus.value = null
  try {
    const res = await fetch(`${BASE}/api/blocklist/test`, { method: 'POST' })
    const data = await res.json()
    connectionStatus.value = data.ok === true
    connectionDetail.value = data.ok
      ? `Pi-hole ${data.version} — ${data.domain_count} domains blocked`
      : `Error: ${data.error ?? 'connection failed'}`
  } catch {
    connectionStatus.value = false
    connectionDetail.value = 'Network error'
  }
}

function toggleEvidence(id: string) {
  const s = new Set(expandedEvidence.value)
  s.has(id) ? s.delete(id) : s.add(id)
  expandedEvidence.value = s
}

function shortDate(iso: string | null): string {
  if (!iso) return '—'
  try { return new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) }
  catch { return iso }
}

function collectTabRef(el: unknown, idx: number) {
  if (el instanceof HTMLButtonElement) tabBtnRefs.value[idx] = el
}

function handleTabKey(e: KeyboardEvent, idx: number) {
  let next = idx
  if (e.key === 'ArrowRight') next = idx + 1
  else if (e.key === 'ArrowLeft')  next = idx - 1
  else return
  e.preventDefault()
  const clamped = Math.max(0, Math.min(tabDefs.length - 1, next))
  activeTab.value = tabDefs[clamped]!.value
  tabBtnRefs.value[clamped]?.focus()
}

onMounted(load)
</script>
