<script setup lang="ts">
// Partner dashboard. Landing page for partner-admin role. Mirrors
// partner-screens.jsx PartnerDashboard (lines 196-365) line-for-line:
// MRR-card-plus-3-stat strip, customer-health 4-col grid, attention list,
// recent activity table.



import type { CustomerOrg } from '~/types/partner'

const toast = useToast()
const router = useRouter()
const partnerMode = usePartnerMode()

// Real partner identity from /api/me. Middleware redirects non-partner users
// away, but we default the name defensively so the header never crashes.
const { partner: realPartner } = useMe()
const partnerName = computed(() => realPartner.value?.name ?? 'Partner')

const wizardOpen = ref(false)
const entryCustomer = ref<CustomerOrg | null>(null)

// Real MRR from platform-api. Subscriptions are grouped by currency so a
// partner with mixed-currency customers (e.g. some DKK, some EUR) sees a
// per-currency total rather than an FX-fudged single number.
type Currency = 'DKK' | 'EUR' | 'USD'
interface MrrResponse {
  totals: Array<{ currency: Currency; monthlyMinor: number }>
  breakdown: Array<{
    tenantId: string
    tenantName: string
    currency: Currency
    monthlyMinor: number
    custom: boolean
  }>
}
const { data: mrr } = await useFetch<MrrResponse>('/api/partner/mrr', {
  key: 'partner-mrr',
  default: () => ({ totals: [], breakdown: [] }),
})

const totalsDisplay = computed(() =>
  (mrr.value?.totals ?? []).map((t) => ({
    currency: t.currency,
    majorAmount: Math.round(t.monthlyMinor / 100),
  })),
)
const hasCustomPriced = computed(() => (mrr.value?.breakdown ?? []).some((b) => b.custom))

// Compact one-line summary used in the page subtitle.
const totalsLine = computed(() => {
  const parts = totalsDisplay.value.map(
    (t) => `${t.majorAmount.toLocaleString('da-DK')} ${t.currency}`,
  )
  if (parts.length === 0) return '0 DKK / mo'
  return parts.join(' + ') + ' / mo'
})

// Real customer count from the breakdown.
const totalCustomers = computed(() => (mrr.value?.breakdown ?? []).length)

// Real end-user count = sum of active User docs across this partner's
// tenants. Reuses the cached /api/partner/tenants response (same key as
// the customers page) so this dashboard doesn't issue a second fetch.
interface PartnerTenant {
  _id: string
  slug: string
  name: string
  status: 'active' | 'pending' | 'suspended' | 'deleted'
  plan?: 'mvp' | 'pro' | 'enterprise'
  seats?: number
  userCount?: number
  newUserCount30d?: number  // active users created in the last 30 days
  createdAt?: string        // tenant creation timestamp, for the customers delta
  provisioningStatus?: {
    authentik?: 'pending' | 'ok' | 'error' | 'skipped'
    stalwart?: 'pending' | 'ok' | 'error' | 'skipped'
    ocis?: 'pending' | 'ok' | 'error' | 'skipped'
  }
}
const { data: tenants } = await useFetch<PartnerTenant[]>('/api/partner/tenants', {
  key: 'partner-tenants',
  default: () => [],
})
const totalUsers = computed(() =>
  (tenants.value ?? []).reduce((s, t) => s + (t.userCount ?? 0), 0),
)

// 30-day deltas. Customers delta is derived from tenant.createdAt (already
// on the doc); end-user delta uses the aggregated newUserCount30d. Both
// render as "+N / 30d" — or hide when 0 to keep the card clean on a quiet
// month.
const SINCE_30D = Date.now() - 30 * 24 * 60 * 60 * 1000
const newCustomers30d = computed(
  () => (tenants.value ?? []).filter((t) => t.createdAt && new Date(t.createdAt).getTime() >= SINCE_30D).length,
)
const newUsers30d = computed(
  () => (tenants.value ?? []).reduce((s, t) => s + (t.newUserCount30d ?? 0), 0),
)
const customersDelta = computed(() => (newCustomers30d.value > 0 ? `+${newCustomers30d.value} / 30d` : ''))
const usersDelta = computed(() => (newUsers30d.value > 0 ? `+${newUsers30d.value} / 30d` : ''))

// Decorative MRR sparkline shape — historical MRR isn't stored yet (see
// useMrrTrendline). Purely visual until a daily MRR-snapshot job exists.
const sparkline = useMrrTrendline()

// Attention list — derived from real tenant state (no fixtures). Surfaces
// suspended customers, provisioning errors, seat pressure, and pending/trial
// tenants. Each links to /partner/customers.
interface DashAlert {
  id: string
  tone: 'bad' | 'warn' | 'info'
  cust: string
  msg: string
  action: string
  slug: string
}
const derivedAlerts = computed<DashAlert[]>(() => {
  const out: DashAlert[] = []
  for (const t of tenants.value ?? []) {
    if (t.status === 'suspended') {
      out.push({ id: `susp-${t._id}`, tone: 'bad', cust: t.name, msg: 'Customer suspended', action: 'Review', slug: t.slug })
      continue
    }
    const errored = Object.entries(t.provisioningStatus ?? {})
      .filter(([, s]) => s === 'error')
      .map(([k]) => k)
    if (errored.length) {
      out.push({ id: `prov-${t._id}`, tone: 'bad', cust: t.name, msg: `Provisioning error · ${errored.join(', ')}`, action: 'Reconcile', slug: t.slug })
    }
    const seats = t.seats ?? 0
    const used = t.userCount ?? 0
    if (seats > 0 && used / seats > 0.85) {
      out.push({ id: `seat-${t._id}`, tone: 'warn', cust: t.name, msg: `Approaching seat limit · ${used}/${seats} used`, action: 'Upsell', slug: t.slug })
    }
    if (t.status === 'pending') {
      out.push({ id: `pend-${t._id}`, tone: 'info', cust: t.name, msg: 'Awaiting provisioning', action: 'Follow up', slug: t.slug })
    }
  }
  return out
})
const alertCounts = computed(() => ({
  bad: derivedAlerts.value.filter((a) => a.tone === 'bad').length,
  warn: derivedAlerts.value.filter((a) => a.tone === 'warn').length,
}))
const issuesHint = computed(() => {
  const { bad, warn } = alertCounts.value
  if (bad === 0 && warn === 0) return 'all clear'
  return `${bad} critical · ${warn} warning`
})

function statusBadge(s: string): { tone: 'ok' | 'warn' | 'bad' | 'info' | 'neutral'; label: string } {
  switch (s) {
    case 'healthy':   return { tone: 'ok',   label: 'healthy' }
    case 'attention': return { tone: 'warn', label: 'attention' }
    case 'past_due':  return { tone: 'bad',  label: 'past-due' }
    case 'trial':     return { tone: 'info', label: 'trial' }
    default:          return { tone: 'neutral', label: s }
  }
}

function startEnter(c: CustomerOrg) {
  entryCustomer.value = c
}
function confirmEnter(reason: string) {
  if (!entryCustomer.value) return
  const c = entryCustomer.value
  partnerMode.enter(c.id)
  entryCustomer.value = null
  toast.info(`Entered ${c.name}`, reason ? `Reason: ${reason}` : 'No reason captured')
  router.push('/admin')
}

function onAlert(_a: DashAlert) {
  router.push('/partner/customers')
}

// ── Real health + activity (replace fixture cards) ───────────────────────

// Health badge derived from real tenant state. We have:
//   - Tenant.status: active / pending / suspended / deleted
//   - provisioningStatus per integration: ok / pending / error / skipped
// Map: any provisioning error or suspended/deleted → bad. Pending or
// awaiting provisioning → warn. Active + all integrations ok|skipped → ok.
function tenantHealth(t: PartnerTenant): 'ok' | 'warn' | 'bad' {
  if (t.status === 'suspended' || t.status === 'deleted') return 'bad'
  const states = Object.values(t.provisioningStatus ?? {}) as Array<string | undefined>
  if (states.some((s) => s === 'error')) return 'bad'
  if (t.status === 'pending' || states.some((s) => s === 'pending')) return 'warn'
  return 'ok'
}

const PLAN_LABEL: Record<'mvp' | 'pro' | 'enterprise', string> = {
  mvp: 'Starter',
  pro: 'Business',
  enterprise: 'Enterprise',
}

const healthTiles = computed(() =>
  (tenants.value ?? []).map((t) => ({
    id: t._id,
    slug: t.slug,
    name: t.name,
    planLabel: PLAN_LABEL[t.plan ?? 'pro'],
    usedSeats: t.userCount ?? 0,
    totalSeats: t.seats ?? 0,
    tone: tenantHealth(t),
  })),
)
const healthyCount = computed(() => healthTiles.value.filter((t) => t.tone === 'ok').length)

// Real audit feed. Each event has resourceName + actor.email + at +
// outcome ('success'|'failure'|'pending'). We render the verb in
// dotted form (e.g. tenant.created) since the dashboard is a glance
// view — clickthrough to /partner/audit can show the full row context.
interface ActivityEvent {
  _id: string
  at: string
  action: string
  resourceName?: string
  tenantSlug?: string
  outcome?: 'success' | 'failure' | 'pending'
  actor?: { email?: string; userId?: string }
}
const { data: activityRaw } = await useFetch<ActivityEvent[]>('/api/partner/activity', {
  key: 'partner-activity',
  query: { limit: 8 },
  default: () => [],
})

function eventTone(e: ActivityEvent): 'ok' | 'warn' | 'bad' | 'info' {
  if (e.outcome === 'failure') return 'bad'
  if (e.outcome === 'pending') return 'warn'
  // Heuristic: actions ending in .deleted / .suspended / .terminated read as bad
  if (/\.(deleted|suspended|terminated|removed)$/.test(e.action)) return 'bad'
  return 'info'
}

function fmtTime(iso: string): string {
  const d = new Date(iso)
  return d.toLocaleTimeString('da-DK', { hour: '2-digit', minute: '2-digit' })
}

function tenantNameFromSlug(slug?: string): string {
  if (!slug) return '—'
  return tenants.value?.find((t) => t.slug === slug)?.name ?? slug
}

const realActivity = computed(() =>
  (activityRaw.value ?? []).map((e) => ({
    id: e._id,
    when: fmtTime(e.at),
    cust: tenantNameFromSlug(e.tenantSlug),
    who: e.actor?.email ?? 'system',
    action: e.action,
    tone: eventTone(e),
  })),
)

function provisioned() {
  toast.ok('Customer provisioned', 'Welcome email is on its way to the first admin')
}
</script>

<template>
  <div>
    <PageHeader
      :eyebrow="`${partnerName} · Partner console`"
      title="Portfolio overview"
      :subtitle="`${totalCustomers} customer organizations · ${totalUsers} end users · ${totalsLine} MRR${hasCustomPriced ? ' (+ custom-priced)' : ''}`"
    >
      <template #actions>
        <UiButton variant="secondary" @click="toast.ok('Exporting', 'Portfolio PDF · sent to your inbox')">
          <template #leading><UiIcon name="download" :size="14" /></template>
          Export report
        </UiButton>
        <UiButton variant="primary" @click="wizardOpen = true">
          <template #leading><UiIcon name="plus" :size="14" /></template>
          New customer
        </UiButton>
      </template>
    </PageHeader>

    <div class="content">
      <!-- Top strip: MRR card (1.4fr) + 3 stat cards -->
      <div class="top-strip">
        <Card :pad="0" class="mrr-card">
          <div class="mrr-head">
            <Eyebrow>Current MRR</Eyebrow>
            <div class="mrr-totals">
              <template v-if="totalsDisplay.length === 0">
                <div class="mrr-value">0 <span class="dkk">DKK / mo</span></div>
              </template>
              <template v-else>
                <div v-for="t in totalsDisplay" :key="t.currency" class="mrr-line">
                  <span class="mrr-amount">{{ t.majorAmount.toLocaleString('da-DK') }}</span>
                  <span class="mrr-cur">{{ t.currency }} / mo</span>
                </div>
              </template>
              <Mono v-if="hasCustomPriced" dim>+ custom-priced</Mono>
            </div>
          </div>
          <div class="mrr-chart">
            <!-- Sparkline values are placeholder until a daily MRR-snapshot
                 job exists. Multi-currency makes a single sparkline less
                 meaningful anyway; the chart is decorative for now. -->
            <PartnerSparkline :values="sparkline" :width="420" :height="64" stroke="var(--text)" fill="var(--row-hover)" />
          </div>
        </Card>
        <Card>
          <Stat label="Customers" :value="totalCustomers" :delta="customersDelta" delta-tone="up" />
        </Card>
        <Card>
          <Stat label="End users" :value="totalUsers" :delta="usersDelta" delta-tone="up" />
        </Card>
        <Card>
          <Stat label="Issues" :value="derivedAlerts.length" :hint="issuesHint" />
        </Card>
      </div>

      <!-- Health grid + Attention -->
      <div class="grid-2">
        <Card :pad="0">
          <div class="card-head">
            <div>
              <Eyebrow>Health</Eyebrow>
              <div class="card-title">Customer status</div>
            </div>
            <Mono dim>{{ healthyCount }} healthy of {{ totalCustomers }}</Mono>
          </div>
          <div v-if="healthTiles.length === 0" class="empty-state">
            <Mono dim>// no customers yet — provision your first from the New customer button</Mono>
          </div>
          <div v-else class="health-grid">
            <NuxtLink
              v-for="t in healthTiles"
              :key="t.id"
              :to="`/partner/customers`"
              class="health-tile"
            >
              <div class="tile-head">
                <span class="tile-name">{{ t.name }}</span>
                <StatusDot :color="`var(--${t.tone})`" :size="6" :glow="false" />
              </div>
              <div class="tile-meta">{{ t.planLabel }} · {{ t.usedSeats }}/{{ t.totalSeats }}</div>
              <Mono dim class="tile-slug">{{ t.slug }}</Mono>
            </NuxtLink>
          </div>
        </Card>

        <Card :pad="0">
          <div class="card-head">
            <div>
              <Eyebrow>Attention</Eyebrow>
              <div class="card-title">What needs your attention</div>
            </div>
          </div>
          <div v-if="derivedAlerts.length === 0" class="empty-state">
            <Mono dim>// nothing needs attention right now</Mono>
          </div>
          <div v-else class="attn-list">
            <div
              v-for="a in derivedAlerts"
              :key="a.id"
              class="attn-row"
              :style="{ borderLeftColor: `var(--${a.tone})` }"
            >
              <div class="attn-meta">
                <div class="attn-top">
                  <span class="attn-cust">{{ a.cust }}</span>
                  <Mono dim>{{ a.tone }}</Mono>
                </div>
                <div class="attn-msg">{{ a.msg }}</div>
              </div>
              <UiButton size="sm" variant="secondary" @click="onAlert(a)">{{ a.action }}</UiButton>
            </div>
          </div>
        </Card>
      </div>

      <!-- Recent activity -->
      <Card :pad="0">
        <div class="card-head">
          <div>
            <Eyebrow>Activity</Eyebrow>
            <div class="card-title">Recent across portfolio</div>
          </div>
          <UiButton size="sm" variant="ghost" @click="router.push('/partner/audit')">
            View all
            <template #trailing><UiIcon name="chevRight" :size="12" /></template>
          </UiButton>
        </div>
        <div v-if="realActivity.length === 0" class="empty-state">
          <Mono dim>// no recent events yet</Mono>
        </div>
        <div v-else class="activity-list">
          <div
            v-for="(a, i) in realActivity"
            :key="a.id"
            class="activity-row"
            :class="{ last: i === realActivity.length - 1 }"
          >
            <Mono>{{ a.when }}</Mono>
            <div class="activity-cust">
              <span class="activity-cust-name">{{ a.cust }}</span>
            </div>
            <div class="activity-text">
              <span class="activity-who">{{ a.who }}</span> <Mono dim>{{ a.action }}</Mono>
            </div>
            <div class="activity-tone">
              <Badge :tone="a.tone" dot>{{ a.tone }}</Badge>
            </div>
          </div>
        </div>
      </Card>
    </div>

    <PartnerCustomerCreateWizard
      :open="wizardOpen"
      @close="wizardOpen = false"
      @done="provisioned"
    />
    <PartnerEnterCustomerConfirmModal
      :customer="entryCustomer"
      @close="entryCustomer = null"
      @confirm="confirmEnter"
    />
  </div>
</template>

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

/* Top strip: MRR card (1.4fr) + 3 stat cards */
.top-strip {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
  gap: 12px;
}

.mrr-card { overflow: hidden; display: flex; flex-direction: column; }
.mrr-head { padding: 20px 24px 12px; }
.mrr-value-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: 8px;
}
.mrr-totals {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 8px;
}
.mrr-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.mrr-amount {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 28px;
  letter-spacing: -0.025em;
  line-height: 1.05;
}
.mrr-cur { font-size: 14px; color: var(--text-mute); font-weight: 500; }
.mrr-value {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 32px;
  letter-spacing: -0.025em;
  line-height: 1;
}
.dkk { font-size: 18px; color: var(--text-mute); font-weight: 500; }
.trend {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ok);
  font-weight: 500;
}
.mrr-chart { padding: 0 12px 12px; }
.mrr-chart :deep(svg) { width: 100%; height: 64px; }

/* 2-up grid */
.grid-2 {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 16px;
}

.card-head {
  padding: 20px 24px;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.card-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 18px;
  margin-top: 4px;
}

/* Health grid: 4 columns of tiles */
.health-grid {
  padding: 12px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}

.health-tile {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 12px;
  text-align: left;
  cursor: pointer;
  color: var(--text);
  font: inherit;
  transition: border-color 120ms;
}
.health-tile:hover { border-color: var(--text); }
.health-tile { text-decoration: none; display: block; }
.tile-slug { display: block; margin-top: 6px; font-size: 10px; }

.empty-state {
  padding: 32px 24px;
  text-align: center;
  color: var(--text-mute);
}
.tile-head { display: flex; align-items: center; gap: 8px; }
.tile-swatch { width: 16px; height: 16px; border-radius: 3px; flex-shrink: 0; }
.tile-name {
  flex: 1;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tile-meta {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-mute);
  margin-top: 8px;
}
.tile-mrr {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  margin-top: 6px;
}

/* Attention list */
.attn-list { padding: 8px 8px 12px; display: flex; flex-direction: column; gap: 4px; }
.attn-row {
  padding: 10px 14px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  gap: 10px;
  border-left: 2px solid var(--border);
}
.attn-meta { flex: 1; min-width: 0; }
.attn-top { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.attn-cust { font-size: 12px; font-weight: 600; }
.attn-msg { font-size: 12px; color: var(--text-dim); margin-top: 2px; }

/* Recent activity */
.activity-list { display: flex; flex-direction: column; }
.activity-row {
  display: grid;
  grid-template-columns: 60px 200px 1fr 100px;
  align-items: center;
  gap: 12px;
  padding: 12px 24px;
  border-bottom: 1px solid var(--border);
}
.activity-row.last { border-bottom: none; }
.activity-cust { display: flex; align-items: center; gap: 8px; }
.activity-swatch { width: 12px; height: 12px; border-radius: 2px; flex-shrink: 0; }
.activity-cust-name { font-size: 13px; font-weight: 500; }
.activity-text { font-size: 13px; color: var(--text-dim); }
.activity-who { color: var(--text); }
.activity-tone { text-align: right; }

@media (max-width: 1280px) {
  .top-strip { grid-template-columns: 1fr 1fr; }
  .grid-2 { grid-template-columns: 1fr; }
  .health-grid { grid-template-columns: repeat(3, 1fr); }
}
</style>
