import { createDbService } from '@/server/services/db.js';
/**
 * Translation model healthcheck — stateful gate.
 *
 * `verifyConfiguredModelExists` checks whether the configured Gemini model
 * is available via the Gemini OpenAI-compat /models endpoint, then persists
 * the result into system_config:
 *   - translation_health_ok       = 'true' | 'false'
 *   - translation_health_checked_at = ISO timestamp
 *
 * On a true→false transition, an outbox alert row is written so the
 * process-outbox cron can fan it out to admin email.
 *
 * Consumers read `translation_health_ok` from system_config once per batch
 * (cached externally) — they do NOT call /models per-message.
 *
 * Called by:
 *   - Daily cron dispatcher (cron/index.ts, `0 0 * * *`)
 *   - Admin on-demand via POST /api/admin/translation/healthcheck
 */

import { getSystemConfig, setSystemConfig } from '../db/queries/system-config.js';
import { enqueueOutbox } from '../db/queries/outbox.js';
import { withSentry } from '@/server/observability/with-sentry';
import { captureCaught } from '@/server/observability/capture.server';
import type { DrizzleClient } from '../db/client.js';

const GEMINI_MODELS_URL = 'https://generativelanguage.googleapis.com/v1beta/openai/models';
import { requireConfiguredLlmModel } from '../ai/model-config.js';

export interface HealthcheckResult {
  ok: boolean;
  checkedAt: string;
  modelId: string;
  reason?: string;
}

export interface HealthcheckEnv {
  DATABASE_URL: string;
  GOOGLE_API_KEY?: string;
}

/**
 * Verify the configured Gemini model exists and is reachable.
 *
 * Writes `translation_health_ok` and `translation_health_checked_at` to
 * system_config. On ok=false (and only when transitioning from ok=true),
 * writes a TRANSLATION_HEALTH_DEGRADED outbox row for admin alerting.
 */
export async function verifyConfiguredModelExists(
  db: DrizzleClient,
  apiKey: string,
): Promise<HealthcheckResult> {
  const checkedAt = new Date().toISOString();

  // Resolve model ID from config (DB override) or fall back to default.
  const configuredModelId = await getSystemConfig(db, 'translation_model_id');
  const modelId = requireConfiguredLlmModel(configuredModelId, 'translation healthcheck');

  // Read previous health state for transition detection.
  const previousHealthOk = await getSystemConfig(db, 'translation_health_ok');

  let ok = false;
  let reason: string | undefined;

  try {
    const response = await fetch(GEMINI_MODELS_URL, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (!response.ok) {
      reason = `Gemini /models returned HTTP ${response.status}`;
    } else {
      const json = (await response.json()) as { data?: Array<{ id: string }> };
      const models = json.data ?? [];
      // Gemini may return the model id with or without "models/" prefix.
      const found = models.some((m) => m.id === modelId || m.id === `models/${modelId}`);
      if (found) {
        ok = true;
      } else {
        reason = `Model "${modelId}" not found in Gemini catalog (${models.length} models listed)`;
      }
    }
  } catch (err) {
    reason = `Fetch error: ${err instanceof Error ? err.message : String(err)}`;
    captureCaught(err, {
      scope: 'translation.healthcheck',
      severity: 'error',
      extra: { modelId },
    });
  }

  // Persist gate state.
  await setSystemConfig(
    db,
    'translation_health_ok',
    ok ? 'true' : 'false',
    'cron.translation-healthcheck',
  );
  await setSystemConfig(
    db,
    'translation_health_checked_at',
    checkedAt,
    'cron.translation-healthcheck',
  );

  // Write admin alert outbox row on true→false transition only (no spam on repeated failures).
  if (!ok && previousHealthOk === 'true') {
    try {
      await enqueueOutbox(db, {
        aggregateType: 'translation_health',
        aggregateId: '00000000-0000-0000-0000-000000000000',
        eventType: 'TRANSLATION_HEALTH_DEGRADED',
        payload: { modelId, reason: reason ?? 'unknown', checkedAt },
      });
    } catch (alertErr) {
      // Non-fatal — the main result is already persisted.
      captureCaught(alertErr, {
        scope: 'translation.healthcheck.alert',
        severity: 'warning',
        extra: { modelId },
      });
      console.error('[translation-healthcheck] Failed to enqueue degradation alert:', alertErr);
    }
  }

  return { ok, checkedAt, modelId, ...(reason ? { reason } : {}) };
}

/**
 * Cron-wrapped entry point — called from cron/index.ts.
 * Resolves API key using the same priority order as the LLM job processor:
 *   1. system_config.google_api_key (DB admin override)
 *   2. env.GOOGLE_API_KEY (CF secret)
 */
export const runTranslationHealthcheck = withSentry(
  async function runTranslationHealthcheck(env: HealthcheckEnv): Promise<void> {
    const db = createDbService({ DATABASE_URL: env.DATABASE_URL });

    const dbApiKey = await getSystemConfig(db, 'google_api_key');
    const effectiveApiKey = (dbApiKey || null) ?? env.GOOGLE_API_KEY ?? '';

    if (!effectiveApiKey) {
      console.warn(
        '[translation-healthcheck] No GOOGLE_API_KEY — skipping healthcheck, marking unhealthy',
      );
      const checkedAt = new Date().toISOString();
      await setSystemConfig(db, 'translation_health_ok', 'false', 'cron.translation-healthcheck');
      await setSystemConfig(
        db,
        'translation_health_checked_at',
        checkedAt,
        'cron.translation-healthcheck',
      );
      return;
    }

    await verifyConfiguredModelExists(db, effectiveApiKey);
  },
  { name: 'cron.translation-healthcheck', kind: 'cron' },
);
