<template>
  <div class="enforcement-reports">
    <div class="page-header">
      <h2>Enforcement Reports</h2>
    </div>
    <p class="setting-description">
      Latest GE-Enforce result reported by each PC. "Received" means the PC picked
      up the current published manifest; status shows self-heal and failures.
    </p>

    <div v-if="error" class="error-message">{{ error }}</div>

    <div class="filters">
      <input
        type="text"
        class="form-control"
        v-model="filterHost"
        placeholder="Filter hostname"
        @keyup.enter="load"
      />
      <input
        type="text"
        class="form-control"
        v-model="filterScope"
        placeholder="Filter PC type"
        @keyup.enter="load"
      />
      <button class="btn btn-primary" @click="load">Filter</button>
      <button class="btn btn-secondary" @click="clearFilters">Clear</button>
    </div>

    <div class="card">
      <div class="table-container">
        <table>
          <thead>
            <tr>
              <th>Host</th><th>Asset</th><th>PC type</th><th>Received</th><th>Version</th>
              <th>Status</th><th>Installed</th><th>Skipped</th><th>Failed</th>
              <th>Backup</th><th>Last check-in</th><th></th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="report in reports" :key="report.reportid">
              <td class="host-cell">
                <router-link v-if="report.computerid" :to="`/pcs/${report.computerid}`">
                  {{ report.hostname }}
                </router-link>
                <span v-else>{{ report.hostname }}</span>
                <!-- Coordinates give the same floor-plan preview the asset's own
                     page uses. No coordinates, no icon - an icon that opened an
                     empty map would be worse than none. -->
                <LocationMapTooltip
                  v-if="report.mapx != null && report.mapy != null"
                  :left="report.mapx" :top="report.mapy" :levelid="report.levelid"
                  :machineName="report.hostname"
                >
                  <span class="map-pin" :title="report.location || 'On the floor plan'">&#9678;</span>
                </LocationMapTooltip>
              </td>
              <td class="asset-cell">
                <!-- What this PC IS or DRIVES, whichever applies. A part-marker
                     or bay PC shows its machine; a measuring-tool PC its tool;
                     a display its role. -->
                <!-- /machines/:id and /measuringtools/:id key on the PLUGIN
                     extension id, NOT the core assetid. Linking with the assetid
                     lands on whichever record happens to share that number - a
                     wrong page that looks right. No plugin id means the plugin
                     is not installed or the asset has no extension row, so show
                     the number as plain text rather than a link that misleads. -->
                <router-link v-if="report.machinepluginid" class="asset-chip"
                             :to="`/machines/${report.machinepluginid}`"
                             title="Machine this PC controls">
                  {{ report.machinenumber }}
                </router-link>
                <span v-else-if="report.machinenumber" class="asset-chip muted"
                      title="Machine this PC controls">{{ report.machinenumber }}</span>
                <router-link v-if="report.toolpluginid" class="asset-chip"
                             :to="`/measuringtools/${report.toolpluginid}`"
                             title="Measuring tool this PC controls">
                  {{ report.toolassetnumber }}
                </router-link>
                <span v-else-if="report.toolassetnumber" class="asset-chip muted"
                      title="Measuring tool this PC controls">{{ report.toolassetnumber }}</span>
                <span v-if="report.displayrole" class="asset-chip muted"
                      title="Display role">{{ report.displayrole }}</span>
                <!-- Deliberately NOT the PC's own asset number: the collector
                     stores a PC's hostname AS its assetnumber, so echoing it
                     here would just repeat the Host column. Blank means this PC
                     drives nothing and is not a display. -->
                <span v-if="!report.machinenumber && !report.toolassetnumber && !report.displayrole"
                      class="muted">-</span>
                <div v-if="report.location" class="asset-location muted">{{ report.location }}</div>
              </td>
              <td>{{ report.scopename }}</td>
              <td>
                <span class="badge" :class="report.receivedlatest ? 'badge-success' : 'badge-warning'">
                  {{ report.receivedlatest ? 'yes' : 'behind' }}
                </span>
              </td>
              <td class="muted">{{ report.appliedversion ?? '-' }} / {{ report.latestversion ?? '-' }}</td>
              <!-- A PC that stops reporting keeps the status of its last good
                   cycle, so silence has to outrank it: 'stale' shows instead,
                   with what it last reported kept in the tooltip. -->
              <td>
                <span v-if="report.isstale" class="badge badge-warning"
                      :title="staleTitle(report)">stale</span>
                <span v-else class="badge" :class="statusClass(report.status)">{{ report.status }}</span>
              </td>
              <td>{{ report.installed }}</td>
              <td class="muted">{{ report.skipped }}</td>
              <td :class="{ 'fail-count': report.failed }">{{ report.failed }}</td>
              <!-- A VERDICT, not a date. An unchanged config writes no new
                   revision, so the timestamp is the last time the collector
                   CHECKED and found the backup intact - which read as neglect
                   when shown raw, because the healthy steady state is a
                   day-old confirmation. The badge names the kind and colours
                   it good/stale; the date moves to the tooltip. Any backup
                   kind added later inherits this with no change here. -->
              <td>
                <span v-if="report.backupkind"
                      class="badge"
                      :class="backupClass(report)"
                      :title="backupTitle(report)">{{ report.backupkind }}</span>
                <span v-else class="muted">-</span>
              </td>
              <td class="muted">{{ formatDate(report.lastcheckin || report.receivedat) }}</td>
              <td class="actions">
                <button class="btn btn-sm btn-secondary" @click="openDetail(report.reportid)">Detail</button>
              </td>
            </tr>
            <tr v-if="!reports.length"><td colspan="12" class="empty">No reports yet.</td></tr>
          </tbody>
        </table>
      </div>
    </div>

    <!-- Detail modal -->
    <div v-if="detail" class="modal-overlay" @click.self="detail = null">
      <div class="modal modal-report">
        <div class="modal-header">
          <h3>{{ detail.hostname }} - {{ detail.scopename }}</h3>
          <button class="modal-close" @click="detail = null">x</button>
        </div>
        <div class="modal-body">
          <p class="setting-description">
            Applied v{{ detail.appliedversion ?? '-' }} of v{{ detail.latestversion ?? '-' }};
            status {{ detail.status }}
          </p>
          <div class="table-container">
            <table>
              <thead>
                <tr><th>Entry</th><th>Action</th><th>Self-heal</th><th>Exit</th><th>Message</th></tr>
              </thead>
              <tbody>
                <tr v-for="(result, index) in detail.results" :key="index">
                  <td>{{ result.entryname }}</td>
                  <td><span class="badge" :class="actionClass(result.action)">{{ result.action }}</span></td>
                  <td>{{ result.selfhealed ? 'yes' : '' }}</td>
                  <td class="muted">{{ result.exitcode ?? '' }}</td>
                  <td class="muted">{{ result.message }}</td>
                </tr>
                <tr v-if="!detail.results.length"><td colspan="5" class="empty">No per-entry detail.</td></tr>
              </tbody>
            </table>
          </div>
        </div>
        <div class="modal-footer">
          <button class="btn btn-secondary" @click="detail = null">Close</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import api from '@/api'
import LocationMapTooltip from '@/components/LocationMapTooltip.vue'

const reports = ref([])
const detail = ref(null)
const error = ref('')
const filterHost = ref('')
const filterScope = ref('')

function payload(response) { return response.data.data }

async function load() {
  const params = {}
  if (filterHost.value) params.hostname = filterHost.value
  if (filterScope.value) params.scopename = filterScope.value
  try {
    reports.value = payload(await api.get('/geenforce/reports', { params }))
    error.value = ''
  } catch (e) { error.value = 'Failed to load reports' }
}
function clearFilters() { filterHost.value = ''; filterScope.value = ''; load() }
async function openDetail(reportid) {
  try {
    detail.value = payload(await api.get(`/geenforce/reports/${reportid}`))
  } catch (e) { error.value = 'Failed to load report detail' }
}

function statusClass(status) {
  return { ok: 'badge-success', selfhealed: 'badge-info', failed: 'badge-danger' }[status] || ''
}

// null verdict = nothing to judge (no revision, or the check is disabled).
// Deliberately NOT green: "never seen" must not read as healthy.
function backupClass(report) {
  if (report.backupok === true) return 'badge-success'
  if (report.backupok === false) return 'badge-danger'
  return ''
}

// Two dates, and they mean different things. checked = when the collector last
// CONFIRMED this config; taken = when the config was last CAPTURED, which only
// moves when something actually changed. Showing one date forced it to stand
// for both, which is what made a healthy machine look neglected.
function backupTitle(report) {
  const checked = report.backuplastseen ? formatDate(report.backuplastseen) : 'never'
  const taken = report.backupcollectedat ? formatDate(report.backupcollectedat) : null
  const kind = report.backupkind

  if (report.backupok === true) {
    return taken
      ? `${kind}: checked ${checked}. Verified the backup taken ${taken} is still current.`
      : `${kind}: checked and verified ${checked}.`
  }
  if (report.backupok === false) {
    const age = `more than ${report.backupstaleafterdays} day(s) ago`
    return taken
      ? `${kind}: last checked ${checked}, ${age} - the backup has stopped running.`
        + ` The newest copy is the one taken ${taken}.`
      : `${kind}: last checked ${checked}, ${age} - the backup has stopped running.`
  }
  return taken
    ? `${kind}: last checked ${checked}; backup taken ${taken}.`
    : `${kind}: last checked ${checked}.`
}

function staleTitle(report) {
  const received = report.receivedat ? formatDate(report.receivedat) : 'never'
  const checkin = report.lastcheckin ? formatDate(report.lastcheckin) : 'not reported'
  return `Nothing heard for over ${report.staleafterminutes} minutes.`
    + ` Last report received ${received}, PC last checked in ${checkin},`
    + ` reporting "${report.status}".`
}
function actionClass(action) {
  return { installed: 'badge-info', skipped: 'badge-success', failed: 'badge-danger',
    filtered: '' }[action] || ''
}
function formatDate(value) { return value ? new Date(value).toLocaleString() : '' }

load()
</script>

<style scoped>
/* No max-width. This is a fleet table - twelve columns now that a row carries
   the asset it drives, its location and its backup state - and capping the page
   at 1100px squeezed them into a box narrower than the window while empty space
   sat to the right. The route already gives this view a full-width shell. */
.enforcement-reports { width: 100%; }
.muted { color: var(--text-light); }
.fail-count { color: var(--danger); font-weight: 600; }
.empty { color: var(--text-light); text-align: center; padding: 1rem; }
/* Wide enough for the 5-column per-entry table (Message is the long one).
   Caps at 92vw so it still fits a small screen. */
.modal-report { max-width: min(1000px, 92vw); }
/* Message wraps instead of forcing horizontal scroll inside the modal; the
   other cells stay on one line (global .table-container is white-space:nowrap). */
.modal-report td:last-child { white-space: normal; min-width: 22ch; }
.modal-close {
  background: transparent;
  border: none;
  color: var(--text-light);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.25rem 0.5rem;
}
.modal-close:hover { color: var(--text); }
.host-cell { display: flex; align-items: center; gap: 0.35rem; }
.map-pin { cursor: pointer; color: var(--primary); font-size: 0.95rem; line-height: 1; }
.asset-cell { white-space: nowrap; }
.asset-chip {
  display: inline-block;
  font-size: 0.8rem;
  padding: 0.05rem 0.4rem;
  margin-right: 0.25rem;
  border: 1px solid var(--border);
  border-radius: 3px;
}
.asset-location { font-size: 0.75rem; margin-top: 0.15rem; }
.backup-kind { font-size: 0.72rem; }
</style>
