/**
 * SSR prefetch for Camp-B admin system panels (React Query dehydrate + email-mock props).
 * Query keys MUST match the client hooks byte-for-byte.
 */

import type { DehydratedState } from '@tanstack/react-query';
import type { DrizzleClient } from '@/server/db/client';
import { sql, gte } from 'drizzle-orm';
import { addressZipCacheMisses } from '@/server/db/schema.js';
import * as alertChannelQueries from '@/server/db/queries/alert-channels.js';
import { listMonitorCheckRows } from '@/server/db/queries/monitor-checks.js';
import { listDlqEvents } from '@/server/db/queries/dlq-events.js';
import { listDeadOutbox } from '@/server/db/queries/outbox.js';
import { CHECK_REGISTRY } from '@/server/monitor/checks/index.js';
import { redactErrorForClient } from '@/server/observability/redact-error.js';
import { env } from '@/server/env';
import { dehydrateForIsland, type PrefetchFn } from '@/lib/query/ssr';
import { monitorQueryKeys } from '@/features/admin-system-health/internals/monitorQueryKeys';

export const ADDRESS_CACHE_TELEMETRY_KEY = ['admin-address-cache-telemetry'] as const;

export interface AddressCacheTelemetryData {
  r2Hits: number;
  lazyFetches: number;
  unresolvable: number;
  manualOverrides: number;
  byResolution: Record<string, number>;
}

export interface MockEmailRow {
  id: string;
  created_at: string;
  to_addr: string;
  subject: string;
  tags: string;
}

export async function loadAddressCacheTelemetry(
  db: DrizzleClient,
): Promise<AddressCacheTelemetryData> {
  // Prefetch-time 30-day window (variance accepted; bounds query rows, not display).
  const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

  const countsByResolution = await db
    .select({
      resolution: addressZipCacheMisses.resolution,
      count: sql<number>`COUNT(*)::int`,
    })
    .from(addressZipCacheMisses)
    .where(gte(addressZipCacheMisses.createdAt, since))
    .groupBy(addressZipCacheMisses.resolution);

  const totals: Record<string, number> = {};
  for (const row of countsByResolution) {
    totals[row.resolution] = row.count;
  }

  return {
    r2Hits: totals['r2_cache'] ?? 0,
    lazyFetches: totals['lazy_fetched'] ?? 0,
    unresolvable: totals['unresolvable'] ?? 0,
    manualOverrides: totals['manual_override'] ?? 0,
    byResolution: totals,
  };
}

async function loadMonitorChannels(db: DrizzleClient) {
  const rows = await alertChannelQueries.listAlertChannels(db);
  return rows.map(alertChannelQueries.toPublicChannel);
}

async function loadMonitorChecks(db: DrizzleClient) {
  const rows = await listMonitorCheckRows(db);
  const byKey = new Map(rows.map((r) => [r.key, r]));
  return CHECK_REGISTRY.map((a) => {
    const row = byKey.get(a.key);
    return {
      key: a.key,
      title: a.title,
      defaultConfig: a.defaultConfig,
      defaultIntervalHours: a.defaultIntervalHours,
      enabled: row?.enabled ?? a.enabledByDefault ?? true,
      config: row?.config ?? a.defaultConfig,
      severityFloor: row?.severityFloor ?? a.defaultSeverityFloor ?? null,
      intervalHours: row?.intervalHours ?? a.defaultIntervalHours,
      includeInDigest: row?.includeInDigest ?? true,
      lastRunAt: row?.lastRunAt?.toISOString() ?? null,
      lastSeverity: row?.lastSeverity ?? null,
      lastDetail: row?.lastDetail ?? null,
    };
  });
}

async function loadMonitorDlq(db: DrizzleClient) {
  const rows = await listDlqEvents(db);
  return rows.map((r) => ({
    queueKey: r.queueKey,
    deadCount: r.deadCount,
    acknowledgedCount: r.acknowledgedCount,
    depth: r.deadCount - r.acknowledgedCount,
    lastDeadAt: r.lastDeadAt?.toISOString() ?? null,
    lastReason: r.lastReason,
  }));
}

async function loadMonitorOutboxDead(db: DrizzleClient) {
  const rows = await listDeadOutbox(db, 100);
  return rows.map((r) => ({
    id: r.id,
    eventType: r.eventType,
    aggregateType: r.aggregateType,
    retryCount: r.retryCount,
    lastError: redactErrorForClient(r.lastError),
    deadAt: r.deadAt?.toISOString() ?? null,
    failedAt: r.failedAt?.toISOString() ?? null,
    createdAt: r.createdAt.toISOString(),
  }));
}

export function makePrefetchAddressCacheTelemetry(db: DrizzleClient): PrefetchFn {
  return async (qc) => {
    const data = await loadAddressCacheTelemetry(db);
    await qc.prefetchQuery({
      queryKey: ADDRESS_CACHE_TELEMETRY_KEY,
      queryFn: () => Promise.resolve(data),
    });
  };
}

export interface MonitorPanelsDehydrated {
  channels: DehydratedState;
  checks: DehydratedState;
  dlq: DehydratedState;
  outboxDead: DehydratedState;
}

export async function makePrefetchMonitorPanels(
  db: DrizzleClient,
): Promise<MonitorPanelsDehydrated> {
  const [channels, checks, dlq, outboxDead] = await Promise.all([
    dehydrateForIsland([
      async (qc) => {
        const data = await loadMonitorChannels(db);
        await qc.prefetchQuery({
          queryKey: monitorQueryKeys.channels(),
          queryFn: () => Promise.resolve(data),
        });
      },
    ]),
    dehydrateForIsland([
      async (qc) => {
        const data = await loadMonitorChecks(db);
        await qc.prefetchQuery({
          queryKey: monitorQueryKeys.checks(),
          queryFn: () => Promise.resolve(data),
        });
      },
    ]),
    dehydrateForIsland([
      async (qc) => {
        const data = await loadMonitorDlq(db);
        await qc.prefetchQuery({
          queryKey: monitorQueryKeys.dlq(),
          queryFn: () => Promise.resolve(data),
        });
      },
    ]),
    dehydrateForIsland([
      async (qc) => {
        const data = await loadMonitorOutboxDead(db);
        await qc.prefetchQuery({
          queryKey: monitorQueryKeys.outboxDead(),
          queryFn: () => Promise.resolve(data),
        });
      },
    ]),
  ]);

  return {
    channels: channels.dehydratedState,
    checks: checks.dehydratedState,
    dlq: dlq.dehydratedState,
    outboxDead: outboxDead.dehydratedState,
  };
}

/** Email mock list — D1 binding, not Drizzle. Mirrors GET /api/admin/email-mock. */
export async function fetchEmailMockPage(
  limit: number,
  offset: number,
): Promise<{ emails: MockEmailRow[]; total: number }> {
  if (!env.EMAIL_MOCK_DB) return { emails: [], total: 0 };

  await env.EMAIL_MOCK_DB.exec(
    `CREATE TABLE IF NOT EXISTS mock_emails (id TEXT PRIMARY KEY, created_at TEXT NOT NULL, from_addr TEXT NOT NULL, to_addr TEXT NOT NULL, subject TEXT NOT NULL, html TEXT NOT NULL, tags TEXT NOT NULL)`,
  );

  const [rows, countRow] = await Promise.all([
    env.EMAIL_MOCK_DB.prepare(
      `SELECT id, created_at, from_addr, to_addr, subject, tags FROM mock_emails ORDER BY created_at DESC LIMIT ? OFFSET ?`,
    )
      .bind(limit, offset)
      .all<MockEmailRow>(),
    env.EMAIL_MOCK_DB.prepare(`SELECT COUNT(*) as total FROM mock_emails`).first<{
      total: number;
    }>(),
  ]);

  return { emails: rows.results, total: countRow?.total ?? 0 };
}
