<script setup lang="ts">
// Customer-admin Domains page. Lists the tenant's email domains on real data
// from platform-api (useDomains → /api/tenants/:slug/domains). Each card shows
// the monospace name, an overall status badge, a "X records to fix" hint, a
// Re-check button, and a 4-record grid (MX/SPF/DKIM/DMARC) clickable to expand
// inline detail with the exact record to publish (sourced from Stalwart's zone,
// so DKIM keys etc. are authoritative). The explanatory copy per status is
// static (DNS_FIX); the record values come from the server.

import type { DomainRecordView, DomainView, RecordStatus } from '~/composables/useDomains'

const router = useRouter()
const toast = useToast()
const { domains, refresh, recheck, remove } = useDomains()

type Tone = 'ok' | 'warn' | 'bad'
type RecordKey = 'mx' | 'spf' | 'dkim' | 'dmarc'

// Static explanatory copy per record + status. Record VALUES are no longer here
// — those come from the server (the real MX host, DKIM public key, etc.). We
// keep only the human guidance, keyed by record kind + observed tone.
const DNS_FIX: Record<RecordKey, {
  label: string
  purpose: string
  states: Record<Tone | 'pending', { headline: string; body: string }>
}> = {
  mx: {
    label: 'MX · mail exchange',
    purpose: 'Routes inbound mail for this domain to dezky.',
    states: {
      ok:   { headline: 'Mail routing healthy', body: 'Inbound mail flows to dezky correctly.' },
      warn: { headline: 'Secondary MX detected', body: 'An MX outside of dezky was found. This is allowed for failover, but make sure it forwards back to dezky.' },
      bad:  { headline: 'No MX record found', body: 'Mail to this domain will not reach dezky. Add the record below at your DNS provider.' },
      pending: { headline: 'Not checked yet', body: 'Add the record below, then re-check.' },
    },
  },
  spf: {
    label: 'SPF · sender policy',
    purpose: 'Tells receiving servers which IPs are allowed to send for this domain.',
    states: {
      ok:   { headline: 'SPF aligned', body: 'Your SPF record correctly authorises dezky as a sender.' },
      warn: { headline: 'SPF present but weak', body: 'SPF resolves but ends with a softfail (~all) or is missing the dezky mechanism. Use the record below for stronger protection.' },
      bad:  { headline: 'No SPF record', body: 'Mail sent from this domain via dezky will fail Gmail/Outlook authentication.' },
      pending: { headline: 'Not checked yet', body: 'Add the record below, then re-check.' },
    },
  },
  dkim: {
    label: 'DKIM · message signing',
    purpose: 'Cryptographic signature proving the message was not altered in transit.',
    states: {
      ok:   { headline: 'DKIM signing live', body: 'Outbound mail is signed and verifiable.' },
      warn: { headline: 'DKIM record mismatch', body: 'A DKIM record exists but its public key differs from dezky’s. Replace it with the value(s) below.' },
      bad:  { headline: 'No DKIM record', body: 'Receiving servers cannot verify the signature on your outbound mail.' },
      pending: { headline: 'Not checked yet', body: 'Add the record(s) below, then re-check.' },
    },
  },
  dmarc: {
    label: 'DMARC · policy enforcement',
    purpose: 'Tells receiving servers what to do with mail that fails SPF or DKIM.',
    states: {
      ok:   { headline: 'DMARC enforcing', body: 'Spoofed mail will be quarantined or rejected at Gmail/Outlook.' },
      warn: { headline: 'DMARC at p=none', body: 'You’re collecting reports but not enforcing. Raise to quarantine once SPF/DKIM look stable.' },
      bad:  { headline: 'No DMARC record', body: 'Anyone can spoof this domain. Mail may fail Gmail / Outlook spam checks.' },
      pending: { headline: 'Not checked yet', body: 'Add the record below, then re-check.' },
    },
  },
}

const RECORD_KEYS: RecordKey[] = ['mx', 'spf', 'dkim', 'dmarc']

const expanded = reactive<Record<string, RecordKey | null>>({})
const copied = ref<string | null>(null)
const rechecking = ref<string | null>(null)

// Remove flow. A domain can only be removed when no mailboxes use it (enforced
// server-side too); the button is disabled otherwise. removeTarget drives the
// confirm dialog.
const removeTarget = ref<DomainView | null>(null)
const removing = ref(false)

function toggle(domain: string, key: RecordKey) {
  expanded[domain] = expanded[domain] === key ? null : key
}
function copyValue(text: string) {
  if (typeof navigator !== 'undefined' && navigator.clipboard) navigator.clipboard.writeText(text)
  copied.value = text
  setTimeout(() => { if (copied.value === text) copied.value = null }, 1400)
  toast.ok('Copied to clipboard')
}

function issuesFor(d: DomainView): RecordKey[] {
  return RECORD_KEYS.filter((k) => d.checks[k] !== 'ok')
}
function recordsOfKind(d: DomainView, k: RecordKey): DomainRecordView[] {
  return d.records.filter((r) => r.kind === k)
}
function tone(status: RecordStatus): Tone {
  return status === 'ok' ? 'ok' : status === 'warn' ? 'warn' : status === 'pending' ? 'warn' : 'bad'
}
function statusIcon(t: Tone): 'check' | 'shield' | 'x' {
  return t === 'ok' ? 'check' : t === 'warn' ? 'shield' : 'x'
}
function recordTint(t: Tone) {
  return t === 'bad'  ? 'rgba(226,48,48,0.12)'
       : t === 'warn' ? 'rgba(232,154,31,0.12)'
       :                'rgba(91,140,90,0.12)'
}
function badgeFor(d: DomainView): { tone: 'ok' | 'warn' | 'bad'; label: string } {
  if (d.status === 'active') return { tone: 'ok', label: 'verified' }
  if (d.status === 'error') return { tone: 'bad', label: 'error' }
  return { tone: 'warn', label: 'attention' }
}

async function recheckDomain(domain: string) {
  rechecking.value = domain
  try {
    await recheck(domain)
    await refresh()
    toast.ok(`Re-checked ${domain}`)
  } catch (err) {
    const e = err as { data?: { message?: string }; message?: string }
    toast.bad('Could not re-check', e?.data?.message ?? e?.message ?? 'Unknown error')
  } finally {
    rechecking.value = null
  }
}

async function confirmRemove() {
  const d = removeTarget.value
  if (!d) return
  removing.value = true
  try {
    await remove(d.domain)
    await refresh()
    toast.ok(`Removed ${d.domain}`)
    removeTarget.value = null
  } catch (err) {
    const e = err as { data?: { message?: string }; message?: string }
    toast.bad('Could not remove domain', e?.data?.message ?? e?.message ?? 'Unknown error')
  } finally {
    removing.value = false
  }
}
</script>

<template>
  <div>
    <PageHeader
      eyebrow="Identity"
      title="Domains"
      subtitle="Your verified domains for mail, SSO, and user provisioning."
    >
      <template #actions>
        <UiButton variant="primary" @click="router.push('/admin/domains/add')">
          <template #leading><UiIcon name="plus" :size="14" /></template>
          Add domain
        </UiButton>
      </template>
    </PageHeader>

    <div class="content">
      <Card v-if="domains && domains.length === 0" class="empty">
        <UiIcon name="globe" :size="22" stroke="var(--text-mute)" />
        <div>
          <div class="empty-title">No domains yet</div>
          <div class="empty-sub">Add your first email domain to route mail and enable sign-in for your team.</div>
        </div>
        <UiButton variant="primary" @click="router.push('/admin/domains/add')">
          <template #leading><UiIcon name="plus" :size="14" /></template>
          Add domain
        </UiButton>
      </Card>

      <Card v-for="d in domains" :key="d.id">
        <div class="head">
          <UiIcon name="globe" :size="20" stroke="var(--text-mute)" />
          <div class="title">
            <div class="domain-name">{{ d.domain }}</div>
            <div class="domain-sub">
              {{ d.mailboxes }} mailbox{{ d.mailboxes === 1 ? '' : 'es' }}
              <template v-if="issuesFor(d).length">
                · <span class="warn">{{ issuesFor(d).length }} record{{ issuesFor(d).length === 1 ? '' : 's' }} to fix</span>
              </template>
            </div>
          </div>
          <UiButton size="sm" variant="secondary" :disabled="rechecking === d.domain" @click.stop="recheckDomain(d.domain)">
            <template #leading><UiIcon name="refresh" :size="12" /></template>
            {{ rechecking === d.domain ? 'Checking…' : 'Re-check now' }}
          </UiButton>
          <button
            class="remove"
            :disabled="d.mailboxes > 0"
            :title="d.mailboxes > 0
              ? `${d.mailboxes} mailbox${d.mailboxes === 1 ? '' : 'es'} use this domain — remove or reassign those users first`
              : 'Remove domain'"
            @click.stop="removeTarget = d"
          >
            <UiIcon name="trash" :size="14" />
          </button>
          <Badge :tone="badgeFor(d).tone" dot>{{ badgeFor(d).label }}</Badge>
        </div>

        <div v-if="d.stalwartError" class="prov-error">
          <UiIcon name="x" :size="13" stroke="var(--bad)" />
          Provisioning error: {{ d.stalwartError }}
        </div>

        <div class="records">
          <button
            v-for="k in RECORD_KEYS"
            :key="k"
            class="rec"
            :class="{ active: expanded[d.domain] === k }"
            @click="toggle(d.domain, k)"
          >
            <Mono>{{ k.toUpperCase() }}</Mono>
            <div class="rec-right">
              <Badge :tone="tone(d.checks[k])" dot>{{ d.checks[k] }}</Badge>
              <UiIcon :name="expanded[d.domain] === k ? 'chevDown' : 'chevRight'" :size="11" stroke="var(--text-mute)" />
            </div>
          </button>
        </div>

        <div v-if="expanded[d.domain]" class="detail" :data-tone="tone(d.checks[expanded[d.domain]!])">
          <div class="detail-head">
            <div class="detail-icon" :style="{ background: recordTint(tone(d.checks[expanded[d.domain]!])), color: `var(--${tone(d.checks[expanded[d.domain]!])})` }">
              <UiIcon :name="statusIcon(tone(d.checks[expanded[d.domain]!]))" :size="14" :stroke-width="d.checks[expanded[d.domain]!] === 'ok' ? 2.5 : 2" />
            </div>
            <div class="detail-body">
              <div class="detail-title">
                {{ DNS_FIX[expanded[d.domain]!].states[d.checks[expanded[d.domain]!]].headline }}
                <Mono dim>{{ DNS_FIX[expanded[d.domain]!].label }}</Mono>
              </div>
              <div class="detail-text">{{ DNS_FIX[expanded[d.domain]!].states[d.checks[expanded[d.domain]!]].body }}</div>
              <Mono dim style="display: block; margin-top: 10px">{{ DNS_FIX[expanded[d.domain]!].purpose }}</Mono>
            </div>
            <button class="detail-close" @click="expanded[d.domain] = null"><UiIcon name="x" :size="14" /></button>
          </div>

          <template v-if="d.checks[expanded[d.domain]!] !== 'ok'">
            <div class="rec-action">
              <Eyebrow>Add {{ recordsOfKind(d, expanded[d.domain]!).length > 1 ? 'these records' : 'this record' }} at your DNS provider</Eyebrow>
              <div v-for="(rec, i) in recordsOfKind(d, expanded[d.domain]!)" :key="i" class="rec-grid">
                <div class="rec-grid-label">Type</div>
                <div class="rec-grid-val">{{ rec.type }}</div>
                <div class="rec-grid-ttl">TTL 3600</div>

                <div class="rec-grid-label sep">Host</div>
                <div class="rec-grid-span sep">
                  <span>{{ rec.host }} <span class="muted">· resolves to {{ rec.fqdn }}</span></span>
                  <button class="copy" @click="copyValue(rec.host)"><UiIcon name="copy" :size="12" /></button>
                </div>

                <div class="rec-grid-label sep">Value</div>
                <div class="rec-grid-span sep">
                  <span class="break">{{ rec.expected }}</span>
                  <button class="copy" @click="copyValue(rec.expected)"><UiIcon name="copy" :size="12" /></button>
                </div>

                <template v-if="rec.priority !== undefined">
                  <div class="rec-grid-label sep">Priority</div>
                  <div class="rec-grid-span sep">{{ rec.priority }}</div>
                </template>
              </div>

              <div class="rec-actions-row">
                <UiButton size="sm" variant="ghost" :disabled="rechecking === d.domain" @click="recheckDomain(d.domain)">
                  <template #leading><UiIcon name="refresh" :size="13" /></template>
                  {{ rechecking === d.domain ? 'Checking…' : 'Re-check this record' }}
                </UiButton>
                <div class="spacer" />
                <Mono dim>changes can take up to 24h to propagate</Mono>
              </div>
            </div>
          </template>

          <template v-else>
            <div v-for="(rec, i) in recordsOfKind(d, expanded[d.domain]!)" :key="i" class="currently-set">
              <Eyebrow>Currently set</Eyebrow>
              <div class="set-value">{{ rec.observed || rec.expected }}</div>
            </div>
          </template>
        </div>
      </Card>
    </div>

    <ConfirmDialog
      :open="!!removeTarget"
      eyebrow="Identity · Domains"
      :title="`Remove ${removeTarget?.domain}?`"
      confirm-label="Remove domain"
      tone="danger"
      :busy="removing"
      @close="removeTarget = null"
      @confirm="confirmRemove"
    >
      Mail routing and DKIM signing for <strong>{{ removeTarget?.domain }}</strong> are deleted from the mail
      server immediately. Inbound mail to this domain will stop being delivered. This can't be undone — you'd
      need to add the domain again and re-publish its DNS records.
    </ConfirmDialog>
  </div>
</template>

<style scoped>
.content { padding: 24px 40px 64px 40px; display: flex; flex-direction: column; gap: 12px; }

.empty { display: flex; align-items: center; gap: 16px; }
.empty-title { font-weight: 600; }
.empty-sub { font-size: 12px; color: var(--text-mute); margin-top: 2px; }

.head { display: flex; align-items: center; gap: 16px; }
.title { flex: 1; min-width: 0; }

.remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-mute);
  cursor: pointer;
  transition: color 120ms, border-color 120ms, background 120ms;
}
.remove:hover:not(:disabled) { color: var(--bad); border-color: var(--bad); }
.remove:disabled { opacity: 0.4; cursor: not-allowed; }
.domain-name { font-family: var(--font-mono); font-size: 16px; font-weight: 600; }
.domain-sub { font-size: 12px; color: var(--text-mute); margin-top: 2px; }
.warn { color: var(--warn); }

.prov-error {
  margin-top: 12px;
  font-size: 12px;
  color: var(--bad);
  display: flex;
  align-items: center;
  gap: 6px;
}

.records {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}
.rec {
  padding: 10px 12px;
  background: var(--bg);
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid transparent;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: inherit;
  text-align: left;
  transition: background 120ms, border-color 120ms;
}
.rec:hover { background: var(--surface); }
.rec.active { background: var(--surface); border-color: var(--text); }
.rec-right { display: flex; align-items: center; gap: 6px; }

.detail {
  margin-top: 16px;
  padding: 16px;
  background: var(--bg);
  border-radius: 6px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
}
.detail[data-tone='ok']   { border-left-color: var(--ok); }
.detail[data-tone='warn'] { border-left-color: var(--warn); }
.detail[data-tone='bad']  { border-left-color: var(--bad); }
.detail-head { display: flex; align-items: flex-start; gap: 12px; }
.detail-icon {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.detail-body { flex: 1; }
.detail-title {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 15px;
}
.detail-text { font-size: 13px; color: var(--text-dim); margin-top: 6px; line-height: 1.55; }
.detail-close { background: transparent; border: none; padding: 4px; border-radius: 4px; color: var(--text-mute); cursor: pointer; }

.rec-action { margin-top: 16px; }
.rec-grid {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  display: grid;
  grid-template-columns: 70px 1fr 80px;
  font-family: var(--font-mono);
  font-size: 12px;
  overflow: hidden;
  margin-top: 8px;
}
.rec-grid-label { padding: 10px 12px; color: var(--text-mute); border-right: 1px solid var(--border); }
.rec-grid-label.sep { border-top: 1px solid var(--border); }
.rec-grid-val { padding: 10px 12px; border-right: 1px solid var(--border); }
.rec-grid-ttl { padding: 10px 12px; color: var(--text-mute); }
.rec-grid-span {
  padding: 10px 12px;
  grid-column: 2 / 4;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.rec-grid-span.sep { border-top: 1px solid var(--border); }
.break { word-break: break-all; }
.muted { color: var(--text-mute); }
.copy { background: transparent; border: none; padding: 4px; border-radius: 4px; color: var(--text-mute); cursor: pointer; }
.copy:hover { background: var(--bg); }

.rec-actions-row { display: flex; align-items: center; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.spacer { flex: 1; }

.currently-set { margin-top: 12px; padding: 12px; background: var(--surface); border-radius: 6px; border: 1px solid var(--border); }
.set-value { font-family: var(--font-mono); font-size: 12px; color: var(--text-dim); word-break: break-all; margin-top: 4px; }
</style>
