import { createDbService } from '@/server/services/db.js';
import { CHECK_REGISTRY } from './checks/index.js';
import { getChannelAdapter } from './channels/index.js';
import { listEnabledChannelsForSeverity } from '@/server/db/queries/alert-channels.js';
import { listMonitorCheckRows } from '@/server/db/queries/monitor-checks.js';
import type { CheckResult, MonitorEnv, AlertPayload } from './types.js';

export async function tickOpsDigest(env: MonitorEnv): Promise<void> {
  const db = createDbService({ DATABASE_URL: env.DATABASE_URL });
  const now = new Date();
  try {
    const rows = await listMonitorCheckRows(db);
    const rowByKey = new Map(rows.map((r) => [r.key, r]));
    const included = CHECK_REGISTRY.filter((a) => {
      const row = rowByKey.get(a.key);
      return (row?.includeInDigest ?? true) && (row?.enabled ?? a.enabledByDefault ?? true);
    });

    const settled = await Promise.allSettled(
      included.map((a) => {
        const config = {
          ...a.defaultConfig,
          ...((rowByKey.get(a.key)?.config as Record<string, unknown>) ?? {}),
        };
        return a.run({ db, env, now, config });
      }),
    );
    const results: CheckResult[] = settled.map((s, i) =>
      s.status === 'fulfilled'
        ? s.value
        : {
            key: included[i]!.key,
            severity: 'warn',
            degraded: true,
            title: included[i]!.title,
            detail: 'check threw during digest',
          },
    );

    // Digest is informational — never page. Send to warn-floor channels.
    const channels = await listEnabledChannelsForSeverity(db, 'warn');
    if (channels.length === 0) return;

    const payload: AlertPayload = {
      severity: 'warn',
      subject: `[multideal-ops] daily digest — ${results.filter((r) => r.severity === 'page').length} page / ${results.filter((r) => r.severity === 'warn').length} warn`,
      results,
      kind: 'digest',
    };
    for (const ch of channels) {
      // Per-channel try: one channel's decrypt/send failure must not skip the remaining channels.
      try {
        const adapter = getChannelAdapter(ch.kind);
        if (!adapter) continue;
        const res = await adapter.send(payload, ch, env);
        if (!res.ok) console.error('[ops-digest] channel send failed', ch.id, res.error);
      } catch (err) {
        // decrypt now happens inside adapter.send and is still caught by this per-channel try.
        console.error(
          '[ops-digest] channel delivery threw',
          ch.id,
          err instanceof Error ? err.name : 'unknown',
        );
      }
    }
  } catch (err) {
    // Never log the raw error object — a DB error can embed the DSN. Name only.
    console.error('[ops-digest] failed', err instanceof Error ? err.name : 'unknown');
  }
}
