<script setup lang="ts">
import { ref, onMounted, watch } from "vue";
import { apiGet } from "@/api/client";
import { useToastStore } from "@/stores/toast";
import PaginationBar from "@/components/PaginationBar.vue";
import { fmtLogStamp } from "@/utils/dateFormat";

const toastStore = useToastStore();

interface LogEntry {
  id: number;
  category: string;
  user_id: number | null;
  username: string | null;
  action: string | null;
  endpoint: string | null;
  method: string | null;
  status_code: number | null;
  duration_ms: number | null;
  ip_address: string | null;
  details: string | null;
  created_at: string;
}

interface LogStats {
  audit: number;
  usage: number;
  error: number;
  total: number;
}

const logs = ref<LogEntry[]>([]);
const stats = ref<LogStats>({ audit: 0, usage: 0, error: 0, total: 0 });
const total = ref(0);
const loading = ref(true);
const expandedId = ref<number | null>(null);

// Filters
const category = ref("");
const search = ref("");
const dateFrom = ref("");
const dateTo = ref("");
const limit = 50;
const offset = ref(0);

let searchTimeout: ReturnType<typeof setTimeout> | null = null;

onMounted(async () => {
  await Promise.all([fetchLogs(), fetchStats()]);
  loading.value = false;
});

watch([category, dateFrom, dateTo], () => {
  offset.value = 0;
  fetchLogs();
});

watch(search, () => {
  if (searchTimeout) clearTimeout(searchTimeout);
  searchTimeout = setTimeout(() => {
    offset.value = 0;
    fetchLogs();
  }, 300);
});

watch(offset, () => {
  fetchLogs();
});

async function fetchLogs() {
  try {
    const params = new URLSearchParams();
    if (category.value) params.set("category", category.value);
    if (search.value) params.set("search", search.value);
    if (dateFrom.value) params.set("date_from", dateFrom.value);
    if (dateTo.value) params.set("date_to", dateTo.value);
    params.set("limit", String(limit));
    params.set("offset", String(offset.value));

    const data = await apiGet<{ logs: LogEntry[]; total: number }>(
      `/api/admin/logs?${params}`
    );
    logs.value = data.logs;
    total.value = data.total;
  } catch {
    toastStore.show("Failed to load logs", "error");
  }
}

async function fetchStats() {
  try {
    stats.value = await apiGet<LogStats>("/api/admin/logs/stats");
  } catch {
    // Ignore
  }
}

function toggleExpand(id: number) {
  expandedId.value = expandedId.value === id ? null : id;
}

function formatDetails(details: string | null): string {
  if (!details) return "";
  try {
    return JSON.stringify(JSON.parse(details), null, 2);
  } catch {
    return details;
  }
}

function displayLabel(entry: LogEntry): string {
  if (entry.category === "audit" && entry.action) return entry.action;
  if (entry.endpoint) return entry.endpoint;
  return "—";
}

function clearFilters() {
  category.value = "";
  search.value = "";
  dateFrom.value = "";
  dateTo.value = "";
  offset.value = 0;
}
</script>

<template>
  <main class="logs-page">
    <h1>Application Logs</h1>

    <section class="settings-section stats-section">
      <div class="stats-grid">
        <div class="stat-card">
          <span class="stat-count">{{ stats.total.toLocaleString() }}</span>
          <span class="stat-label">Total</span>
        </div>
        <div class="stat-card">
          <span class="stat-count stat-audit">{{ stats.audit.toLocaleString() }}</span>
          <span class="stat-label">Audit</span>
        </div>
        <div class="stat-card">
          <span class="stat-count stat-usage">{{ stats.usage.toLocaleString() }}</span>
          <span class="stat-label">Usage</span>
        </div>
        <div class="stat-card">
          <span class="stat-count stat-error">{{ stats.error.toLocaleString() }}</span>
          <span class="stat-label">Error</span>
        </div>
      </div>
    </section>

    <section class="settings-section">
      <h2>Filters</h2>
      <div class="filter-bar">
        <select v-model="category" class="filter-select">
          <option value="">All categories</option>
          <option value="audit">Audit</option>
          <option value="usage">Usage</option>
          <option value="error">Error</option>
        </select>
        <input
          v-model="search"
          type="text"
          placeholder="Search logs..."
          class="filter-input"
        />
        <input v-model="dateFrom" type="date" class="filter-date" title="From date" />
        <input v-model="dateTo" type="date" class="filter-date" title="To date" />
        <button
          v-if="category || search || dateFrom || dateTo"
          class="btn-ghost btn-compact"
          @click="clearFilters"
        >
          Clear
        </button>
      </div>
    </section>

    <section class="settings-section">
      <div v-if="loading" class="loading-msg">Loading logs...</div>

      <div v-else-if="logs.length === 0" class="empty-msg">No log entries found.</div>

      <template v-else>
        <table class="users-table logs-table">
          <thead>
            <tr>
              <th>Time</th>
              <th>Category</th>
              <th class="hide-mobile">User</th>
              <th>Action / Endpoint</th>
              <th class="hide-mobile">IP</th>
              <th class="hide-mobile">Status</th>
              <th class="hide-mobile">Duration</th>
            </tr>
          </thead>
          <tbody>
            <template v-for="entry in logs" :key="entry.id">
              <tr
                class="log-row"
                :class="{ 'row-expanded': expandedId === entry.id }"
                @click="toggleExpand(entry.id)"
              >
                <td class="cell-time">{{ fmtLogStamp(entry.created_at) }}</td>
                <td>
                  <span class="category-badge" :class="'cat-' + entry.category">
                    {{ entry.category }}
                  </span>
                </td>
                <td class="hide-mobile cell-user">{{ entry.username || "—" }}</td>
                <td class="cell-action">
                  <span v-if="entry.method" class="method-tag">{{ entry.method }}</span>
                  {{ displayLabel(entry) }}
                </td>
                <td class="hide-mobile cell-ip">{{ entry.ip_address || "—" }}</td>
                <td class="hide-mobile cell-status">
                  <span v-if="entry.status_code" :class="entry.status_code >= 400 ? 'text-error' : ''">
                    {{ entry.status_code }}
                  </span>
                  <span v-else>—</span>
                </td>
                <td class="hide-mobile cell-duration">
                  {{ entry.duration_ms != null ? entry.duration_ms + "ms" : "—" }}
                </td>
              </tr>
              <tr v-if="expandedId === entry.id && (entry.details || entry.ip_address)" class="detail-row">
                <td colspan="7">
                  <div v-if="entry.ip_address" class="detail-ip">IP: {{ entry.ip_address }}</div>
                  <pre v-if="entry.details" class="detail-json">{{ formatDetails(entry.details) }}</pre>
                </td>
              </tr>
            </template>
          </tbody>
        </table>

        <PaginationBar
          :total="total"
          :limit="limit"
          :offset="offset"
          @update:offset="offset = $event"
        />
      </template>
    </section>
  </main>
</template>

<style scoped>
.logs-page {
  max-width: 1200px;
  margin: 2rem auto;
  padding: 0 1rem;
}
.logs-page h1 {
  margin: 0 0 1.5rem;
}
.settings-section {
  background: var(--fs-surface-raised);
  border: 1px solid var(--fs-border-color);
  border-radius: var(--fs-radius-lg);
  padding: 1.25rem;
  margin-bottom: 1.5rem;
}
.settings-section h2 {
  margin: 0 0 0.75rem;
  font-size: 1.1rem;
}

/* Stats */
.stats-section {
  padding: 1rem 1.25rem;
}
.stats-grid {
  display: flex;
  gap: 1rem;
}
.stat-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
}
.stat-count {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--fs-text-primary);
}
.stat-label {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fs-text-tertiary);
}
.stat-audit {
  color: var(--fs-accent);
}
.stat-usage {
  color: var(--fs-success);
}
.stat-error {
  color: var(--fs-error);
}

/* Filters */
.filter-bar {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.filter-select,
.filter-input,
.filter-date {
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--fs-border-color);
  border-radius: var(--fs-radius-sm);
  background: var(--fs-surface-page);
  color: var(--fs-text-primary);
  font-size: 0.85rem;
}
.filter-select {
  min-width: 140px;
}
.filter-input {
  flex: 1;
  min-width: 150px;
}
.filter-date {
  width: 140px;
}

/* Table */
.loading-msg,
.empty-msg {
  text-align: center;
  color: var(--fs-text-tertiary);
  font-size: 0.9rem;
  padding: 1rem 0;
}
.logs-table {
  width: 100%;
  border-collapse: collapse;
}
.logs-table th {
  text-align: left;
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fs-text-tertiary);
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--fs-border-color);
}
.logs-table td {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--fs-border-color);
  font-size: 0.85rem;
}
.logs-table tbody tr:last-child td {
  border-bottom: none;
}
.log-row {
  cursor: pointer;
  transition: background 0.1s;
}
.log-row:hover {
  background: var(--fs-surface-raised);
}
.row-expanded {
  background: var(--fs-surface-raised);
}
.cell-time {
  white-space: nowrap;
  color: var(--fs-text-tertiary);
  font-size: 0.8rem;
}
.cell-user {
  color: var(--fs-text-secondary);
}
.cell-action {
  max-width: 280px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cell-status {
  font-family: monospace;
  font-size: 0.85rem;
}
.cell-ip {
  font-family: monospace;
  font-size: 0.8rem;
  color: var(--fs-text-tertiary);
  white-space: nowrap;
}
.cell-duration {
  color: var(--fs-text-tertiary);
  font-size: 0.8rem;
  white-space: nowrap;
}
.detail-ip {
  font-family: monospace;
  font-size: 0.8rem;
  color: var(--fs-text-tertiary);
  margin-bottom: 0.4rem;
}
.text-error {
  color: var(--fs-error);
}

/* Category badges */
.category-badge {
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.1rem 0.35rem;
  border-radius: var(--fs-radius-sm);
}
.cat-audit {
  color: var(--fs-accent);
  background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
}
.cat-usage {
  color: var(--fs-success);
  background: color-mix(in srgb, var(--fs-success) 15%, transparent);
}
.cat-error {
  color: var(--fs-error);
  background: color-mix(in srgb, var(--fs-error) 15%, transparent);
}

/* Method tag */
.method-tag {
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 700;
  font-family: monospace;
  padding: 0.05rem 0.25rem;
  border-radius: 3px;
  background: var(--fs-surface-raised);
  color: var(--fs-text-tertiary);
  margin-right: 0.25rem;
}

/* Detail row */
/* `.detail-row` is deliberately bare: a `<tr>` has nothing to style that its
   cells don't carry, and the row exists to scope the rule below (#2444). */
.detail-row td {
  padding: 0 0.75rem 0.75rem;
  border-bottom: 1px solid var(--fs-border-color);
}
.detail-json {
  margin: 0;
  padding: 0.75rem;
  background: var(--fs-surface-page);
  border: 1px solid var(--fs-border-color);
  border-radius: var(--fs-radius-sm);
  font-size: 0.8rem;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-all;
  max-height: 300px;
}

@media (max-width: 768px) {
  .stats-grid {
    flex-wrap: wrap;
  }
  .stat-card {
    min-width: calc(50% - 0.5rem);
  }
  .filter-bar {
    flex-direction: column;
  }
  .filter-select,
  .filter-input,
  .filter-date {
    width: 100%;
  }
  .cell-action {
    max-width: 160px;
  }
}
</style>
