#!/usr/bin/env node
import { Buffer } from 'node:buffer';
import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { MongoClient } from 'mongodb';
import { RoomServiceClient } from 'livekit-server-sdk';
import { config as loadEnv } from 'dotenv';

const envPath = process.env.DOTENV_CONFIG_PATH ?? (existsSync('.env') ? '.env' : 'backend/.env');
loadEnv({ path: envPath, quiet: true });

const strict = process.argv.includes('--strict');
const results = [];

const requiredEnv = [
  'LIVEKIT_URL',
  'LIVEKIT_API_KEY',
  'LIVEKIT_API_SECRET',
  'GITHUB_TOKEN',
  'GITHUB_REPO',
  'MONGODB_URI',
];

const frontendEnv = ['VITE_BACKEND_URL', 'VITE_LIVEKIT_URL'];
const optionalEnv = ['VOYAGE_API_KEY', 'VOYAGE_EMBEDDING_MODEL'];
const doFetch = globalThis.fetch;

function add(name, status, detail = '') {
  results.push({ name, status, detail });
}

function isSet(name) {
  return !!process.env[name]?.trim();
}

function configuredGeminiKey() {
  const candidates = ['GEMINI_API_KEY', 'GOOGLE_API_KEY', 'GOOGLE_GENERATIVE_AI_API_KEY'];
  for (const name of candidates) {
    const value = process.env[name]?.trim();
    if (!value) continue;
    if (/replace|todo|example|your|xxx/i.test(value) || value.length < 20) {
      throw new Error(`${name} looks like a placeholder or truncated key`);
    }
    return { name, value };
  }
  throw new Error('GEMINI_API_KEY is not set');
}

async function check(name, fn) {
  try {
    const detail = await fn();
    add(name, 'ok', detail);
  } catch (err) {
    add(name, 'fail', summarizeError(err));
  }
}

function summarizeProviderBody(text) {
  try {
    const parsed = JSON.parse(text);
    const error = parsed.error;
    if (error?.status || error?.message) {
      return [error.status, error.message].filter(Boolean).join(': ');
    }
  } catch {
    // Keep short plaintext bodies.
  }
  return text.slice(0, 240);
}

async function responseError(service, res) {
  const body = await res.text();
  return `${service} returned ${res.status}${body ? `: ${summarizeProviderBody(body)}` : ''}`;
}

function summarizeError(err) {
  return err instanceof Error ? err.message : String(err);
}

async function checkWorkspace() {
  const workspace = await readFile('pnpm-workspace.yaml', 'utf8');
  for (const pkg of ['frontend', 'backend', 'shared']) {
    if (!workspace.includes(`'${pkg}'`) && !workspace.includes(`- ${pkg}`)) {
      throw new Error(`pnpm-workspace.yaml missing ${pkg}`);
    }
  }
  return 'frontend, backend, and shared are listed';
}

function backendUrl() {
  const explicit = process.env.BACKEND_URL ?? process.env.VITE_BACKEND_URL;
  if (explicit) return explicit;
  if (requiredEnv.every(isSet)) return 'http://127.0.0.1:8787';
  throw new Error('BACKEND_URL or VITE_BACKEND_URL is not set');
}

function frontendUrl() {
  return process.env.FRONTEND_URL ?? process.env.VITE_BACKEND_URL ?? backendUrl();
}

async function checkFrontendShell() {
  const url = frontendUrl().replace(/\/$/, '');
  const res = await doFetch(`${url}/`);
  if (!res.ok) throw new Error(`GET / returned ${res.status}`);
  const html = await res.text();
  if (!html.includes('id="root"')) throw new Error('frontend HTML missing root mount node');

  const script = html.match(/<script[^>]+src="([^"]+)"/)?.[1];
  if (!script) throw new Error('frontend HTML missing bundled script');

  const assetUrl = new URL(script, `${url}/`);
  const assetRes = await doFetch(assetUrl);
  if (!assetRes.ok) throw new Error(`frontend bundle returned ${assetRes.status}`);
  const bundle = await assetRes.text();
  if (bundle.length < 10_000) throw new Error('frontend bundle was unexpectedly small');
  return `${url}, bundle ${Math.round(bundle.length / 1024)} KiB`;
}

async function checkBackendHealth() {
  const url = backendUrl();
  const res = await doFetch(`${url.replace(/\/$/, '')}/health`);
  if (!res.ok) throw new Error(`GET /health returned ${res.status}`);
  const body = await res.json();
  if (body.ok !== true) throw new Error(`unexpected /health body: ${JSON.stringify(body)}`);
  return url;
}

async function checkBackendPods() {
  const url = backendUrl();
  const res = await doFetch(`${url.replace(/\/$/, '')}/api/pods`);
  if (!res.ok) throw new Error(`GET /api/pods returned ${res.status}`);
  const body = await res.json();
  if (!Array.isArray(body)) throw new Error(`unexpected /api/pods body: ${JSON.stringify(body)}`);
  return `${body.length} pod(s)`;
}

async function checkBackendToken() {
  const url = backendUrl();
  const res = await doFetch(`${url.replace(/\/$/, '')}/api/token`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      room: process.env.POD_ROOM ?? 'demo-pod',
      identity: 'doctor',
      name: 'Doctor',
    }),
  });
  if (!res.ok) throw new Error(`POST /api/token returned ${res.status}`);
  const body = await res.json();
  if (typeof body.token !== 'string' || body.token.split('.').length !== 3) {
    throw new Error('token response did not contain a JWT');
  }
  if (typeof body.url !== 'string' || !body.url)
    throw new Error('token response missing LiveKit URL');
  return `minted JWT for ${body.url}`;
}

async function checkLiveKitApi() {
  if (!isSet('LIVEKIT_URL')) throw new Error('LIVEKIT_URL is not set');
  if (!isSet('LIVEKIT_API_KEY')) throw new Error('LIVEKIT_API_KEY is not set');
  if (!isSet('LIVEKIT_API_SECRET')) throw new Error('LIVEKIT_API_SECRET is not set');
  if (process.env.LIVEKIT_URL.includes('REPLACE_ME')) {
    throw new Error('LIVEKIT_URL is still the local placeholder');
  }
  const httpUrl = process.env.LIVEKIT_URL.replace(/^wss:/, 'https:').replace(/^ws:/, 'http:');
  const svc = new RoomServiceClient(
    httpUrl,
    process.env.LIVEKIT_API_KEY,
    process.env.LIVEKIT_API_SECRET,
  );
  const rooms = await svc.listRooms();
  return `${httpUrl}, ${rooms.length} room(s) visible`;
}

async function checkMongo() {
  if (!isSet('MONGODB_URI')) throw new Error('MONGODB_URI is not set');
  const client = new MongoClient(process.env.MONGODB_URI, { serverSelectionTimeoutMS: 5000 });
  try {
    await client.connect();
    const db = client.db();
    await db.command({ ping: 1 });
    return `connected to ${db.databaseName}`;
  } finally {
    await client.close();
  }
}

async function checkVectorIndex() {
  if (!isSet('MONGODB_URI')) throw new Error('MONGODB_URI is not set');
  const client = new MongoClient(process.env.MONGODB_URI, { serverSelectionTimeoutMS: 5000 });
  try {
    await client.connect();
    const db = client.db();
    const indexes = await db
      .collection('collisions')
      .listSearchIndexes('collision_embedding')
      .toArray();
    if (indexes.length === 0) throw new Error('Atlas Search index collision_embedding not found');
    return 'collision_embedding search index found';
  } finally {
    await client.close();
  }
}

async function checkGitHub() {
  if (!isSet('GITHUB_TOKEN')) throw new Error('GITHUB_TOKEN is not set');
  if (!isSet('GITHUB_REPO')) throw new Error('GITHUB_REPO is not set');
  const res = await doFetch(`https://api.github.com/repos/${process.env.GITHUB_REPO}`, {
    headers: {
      accept: 'application/vnd.github+json',
      authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
      'x-github-api-version': '2022-11-28',
    },
  });
  if (!res.ok) throw new Error(`GitHub repo check returned ${res.status}`);
  const body = await res.json();
  return body.full_name ?? process.env.GITHUB_REPO;
}

async function checkGeminiVision() {
  const key = configuredGeminiKey();
  const model = process.env.GEMINI_VISION_MODEL ?? 'gemini-2.0-flash';
  const res = await doFetch(
    `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(
      model,
    )}:generateContent?key=${encodeURIComponent(key.value)}`,
    {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ contents: [{ parts: [{ text: 'Return only: ok' }] }] }),
    },
  );
  if (!res.ok) throw new Error(await responseError('Gemini vision check', res));
  const body = await res.json();
  const text = body.candidates?.[0]?.content?.parts?.map((p) => p.text).join('') ?? '';
  if (!text.trim()) throw new Error('Gemini vision response had no text');
  return model;
}

async function checkGeminiVoiceModel() {
  const key = configuredGeminiKey();
  const model = process.env.GEMINI_LIVE_MODEL ?? 'gemini-3.1-flash-tts-preview';
  const voice = process.env.GEMINI_TTS_VOICE ?? 'Charon';
  const res = await doFetch(
    `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(key.value)}`,
  );
  if (!res.ok) throw new Error(await responseError('Gemini model list', res));
  const body = await res.json();
  const names = (body.models ?? []).map((m) => m.name?.replace(/^models\//, ''));
  if (!names.includes(model)) throw new Error(`${model} not present in Gemini model list`);
  if (!model.includes('tts')) return model;

  const tts = await doFetch(
    `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(
      model,
    )}:generateContent?key=${encodeURIComponent(key.value)}`,
    {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        contents: [
          {
            parts: [
              {
                text: 'Speak this as a calm engineering teammate. Say only: PodMan voice check.',
              },
            ],
          },
        ],
        generationConfig: {
          responseModalities: ['AUDIO'],
          speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: voice } } },
        },
      }),
    },
  );
  if (!tts.ok) throw new Error(await responseError('Gemini voice check', tts));
  const ttsBody = await tts.json();
  const audio = ttsBody.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data;
  if (!audio) throw new Error('Gemini voice response had no audio');
  return `${model}/${voice}, generated ${Buffer.from(audio, 'base64').byteLength} audio bytes`;
}

async function checkGeminiEmbeddings() {
  const key = configuredGeminiKey();
  const model = process.env.GEMINI_EMBEDDING_MODEL ?? 'gemini-embedding-001';
  const res = await doFetch(
    `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(
      model,
    )}:embedContent?key=${encodeURIComponent(key.value)}`,
    {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        content: { parts: [{ text: 'PodMan vector memory check' }] },
        taskType: 'RETRIEVAL_DOCUMENT',
        outputDimensionality: 768,
      }),
    },
  );
  if (!res.ok) throw new Error(await responseError('Gemini embedding check', res));
  const body = await res.json();
  const dims = body.embedding?.values?.length;
  if (!dims) throw new Error('Gemini embedding response had no vector');
  return `${model}, ${dims} dimensions`;
}

async function checkVoyage() {
  if (!isSet('VOYAGE_API_KEY')) throw new Error('VOYAGE_API_KEY is not set');
  const model = process.env.VOYAGE_EMBEDDING_MODEL ?? 'voyage-4-lite';
  const res = await doFetch('https://api.voyageai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      authorization: `Bearer ${process.env.VOYAGE_API_KEY}`,
      'content-type': 'application/json',
    },
    body: JSON.stringify({ input: 'podman deployment doctor', model, input_type: 'query' }),
  });
  if (!res.ok) throw new Error(await responseError('Voyage embedding check', res));
  const body = await res.json();
  const dims = body.data?.[0]?.embedding?.length;
  if (!dims) throw new Error('Voyage response did not contain an embedding');
  return `${model}, ${dims} dimensions`;
}

await check('workspace', checkWorkspace);

for (const name of requiredEnv) {
  add(`env:${name}`, isSet(name) ? 'ok' : 'fail', isSet(name) ? 'set' : 'missing');
}
try {
  const key = configuredGeminiKey();
  add(
    'env:GEMINI_API_KEY',
    'ok',
    key.name === 'GEMINI_API_KEY' ? 'set' : `using ${key.name} alias`,
  );
} catch (err) {
  add('env:GEMINI_API_KEY', 'fail', summarizeError(err));
}
for (const name of optionalEnv) {
  add(`env:${name}`, isSet(name) ? 'ok' : 'warn', isSet(name) ? 'set' : 'optional');
}
for (const name of frontendEnv) {
  add(
    `env:${name}`,
    isSet(name) ? 'ok' : strict ? 'fail' : 'warn',
    isSet(name) ? 'set' : 'required for production frontend builds',
  );
}

await check('frontend shell', checkFrontendShell);
await check('backend health', checkBackendHealth);
await check('backend pods', checkBackendPods);
await check('backend token minting', checkBackendToken);
await check('livekit room service', checkLiveKitApi);
await check('mongo ping', checkMongo);
await check('github repo access', checkGitHub);
await check('gemini vision model', checkGeminiVision);
await check('gemini voice model', checkGeminiVoiceModel);
await check('gemini embeddings', checkGeminiEmbeddings);

if (isSet('VOYAGE_API_KEY')) {
  await check('voyage embeddings', checkVoyage);
} else {
  add('voyage embeddings', 'warn', 'VOYAGE_API_KEY is optional; Gemini embeddings are active');
}
await check('atlas vector index', checkVectorIndex);

const failed = results.filter((r) => r.status === 'fail');
const warnings = results.filter((r) => r.status === 'warn');

for (const result of results) {
  const mark = result.status.toUpperCase().padEnd(4);
  console.log(`${mark} ${result.name}${result.detail ? ` - ${result.detail}` : ''}`);
}

console.log(
  JSON.stringify(
    {
      ok: failed.length === 0,
      strict,
      failed: failed.length,
      warnings: warnings.length,
    },
    null,
    2,
  ),
);

if (strict && failed.length > 0) {
  process.exitCode = 1;
}
