// Per-service verify endpoint.
//
// POST /api/connections/[service]/verify
//   body: { credentials?: { NAME: value, ... } }
//   → on success { ok: true, summary: { account_label, last_4 } }, secret(s)
//     written to vault, install row updated, audit row written.
//   → on failure { ok: false, reason: string }, never echoes credentials back.
//
// All outbound calls go through wrapFetch from lib/egress.js so external
// traffic appears in the tool_egress log.
//
// Supported services:
//   anthropic / openai / kimi / github / resend / supabase   — api_key
//   whatsapp                                                 — qr_pair (reads bridge health)
//   hermes                                                   — binary_detect (reads local FS)

import { getConnection, markConnectionVerified, credentialNamesFor } from '../../../../../lib/connections.js';
import { markInstanceVerified, normalizeScopeInput } from '../../../../../lib/connection-instances.js';
import { verifyViaHealthCheck } from '../../../../../lib/connections/health-check-verify.js';
import { summarizeConnection, fingerprintLast4 } from '../../../../../lib/connection-status.js';
import { setSecret } from '../../../../../lib/secrets.js';
import { wrapFetch } from '../../../../../lib/egress.js';
import { getDb, nowIso, newId } from '../../../../../lib/db.js';
import { getBridgeHealth } from '../../../../../lib/whatsapp.js';
import { getHermesStatus, resolveHermesDir } from '../../../../../lib/hermes.js';
import fs from 'node:fs';
import path from 'node:path';

export const dynamic = 'force-dynamic';
export const runtime = 'nodejs';

const FRIENDLY_ERRORS = {
  invalid_key: "That key didn't work. Double-check it and try again.",
  unauthorized: "The service rejected those credentials. Check the key and try again.",
  network: "We couldn't reach the service. Check your network or the provider's status page.",
  not_paired: 'WhatsApp is not paired yet. Scan the QR with WhatsApp → Linked Devices.',
  not_found: 'No connection by that name.',
  missing_field: 'A required field is missing.',
  unsupported: 'Verification is not supported for this connection yet.',
};

function friendly(reason) {
  return FRIENDLY_ERRORS[reason] || "We couldn't reach the service. Check the value and try again.";
}

function writeAudit(type, meta, status = 'ok', summary = '') {
  try {
    getDb().prepare(`
      INSERT INTO audit_logs (id, at, type, summary, status, meta)
      VALUES (?, ?, ?, ?, ?, ?)
    `).run(
      newId('audit'),
      nowIso(),
      type,
      summary || type,
      status,
      JSON.stringify(meta || {}),
    );
  } catch {
    // best-effort
  }
}

async function readJson(request) {
  try {
    return await request.json();
  } catch {
    return null;
  }
}

function getCredential(body, name) {
  const v = body?.credentials?.[name];
  if (typeof v !== 'string') return null;
  const trimmed = v.trim();
  return trimmed.length ? trimmed : null;
}

async function verifyAnthropic(creds) {
  const key = creds.ANTHROPIC_API_KEY;
  if (!key) return { ok: false, reason: 'missing_field', field: 'ANTHROPIC_API_KEY' };
  try {
    const fetchFn = wrapFetch('connection.verify.anthropic', null, fetch);
    const res = await fetchFn('https://api.anthropic.com/v1/models', {
      method: 'GET',
      headers: {
        'x-api-key': key,
        'anthropic-version': '2023-06-01',
      },
    });
    if (res.status === 401 || res.status === 403) return { ok: false, reason: 'unauthorized' };
    if (!res.ok) return { ok: false, reason: 'invalid_key' };
    return {
      ok: true,
      account_label: 'Anthropic',
      last_4: fingerprintLast4(key),
    };
  } catch {
    return { ok: false, reason: 'network' };
  }
}

async function verifyOpenAI(creds) {
  const key = creds.OPENAI_API_KEY;
  if (!key) return { ok: false, reason: 'missing_field', field: 'OPENAI_API_KEY' };
  try {
    const fetchFn = wrapFetch('connection.verify.openai', null, fetch);
    const res = await fetchFn('https://api.openai.com/v1/models', {
      method: 'GET',
      headers: { authorization: `Bearer ${key}` },
    });
    if (res.status === 401 || res.status === 403) return { ok: false, reason: 'unauthorized' };
    if (!res.ok) return { ok: false, reason: 'invalid_key' };
    return {
      ok: true,
      account_label: 'OpenAI',
      last_4: fingerprintLast4(key),
    };
  } catch {
    return { ok: false, reason: 'network' };
  }
}

async function verifyKimi(creds) {
  const key = creds.KIMI_API_KEY;
  if (!key) return { ok: false, reason: 'missing_field', field: 'KIMI_API_KEY' };
  const hosts = ['https://api.moonshot.ai/v1/models', 'https://api.moonshot.cn/v1/models'];
  let lastReason = 'network';
  for (const url of hosts) {
    try {
      const fetchFn = wrapFetch('connection.verify.kimi', null, fetch);
      const res = await fetchFn(url, {
        method: 'GET',
        headers: { authorization: `Bearer ${key}` },
      });
      if (res.ok) {
        return { ok: true, account_label: 'Moonshot (Kimi)', last_4: fingerprintLast4(key) };
      }
      if (res.status === 401 || res.status === 403) {
        lastReason = 'unauthorized';
      } else {
        lastReason = 'invalid_key';
      }
    } catch {
      // try next host
    }
  }
  return { ok: false, reason: lastReason };
}

async function verifyGithub(creds) {
  const token = creds.GITHUB_TOKEN;
  if (!token) return { ok: false, reason: 'missing_field', field: 'GITHUB_TOKEN' };
  try {
    const fetchFn = wrapFetch('connection.verify.github', null, fetch);
    const res = await fetchFn('https://api.github.com/user', {
      method: 'GET',
      headers: {
        authorization: `Bearer ${token}`,
        accept: 'application/vnd.github+json',
        'x-github-api-version': '2022-11-28',
        'user-agent': 'madarx-os',
      },
    });
    if (res.status === 401 || res.status === 403) return { ok: false, reason: 'unauthorized' };
    if (!res.ok) return { ok: false, reason: 'invalid_key' };
    const data = await res.json().catch(() => ({}));
    const login = typeof data?.login === 'string' ? data.login : null;
    return {
      ok: true,
      account_label: login ? `@${login}` : 'GitHub',
      last_4: fingerprintLast4(token),
      summary: { login },
    };
  } catch {
    return { ok: false, reason: 'network' };
  }
}

async function verifyResend(creds) {
  const key = creds.RESEND_API_KEY;
  const from = creds.RESEND_FROM_EMAIL;
  if (!key) return { ok: false, reason: 'missing_field', field: 'RESEND_API_KEY' };
  if (!from || !from.includes('@')) return { ok: false, reason: 'missing_field', field: 'RESEND_FROM_EMAIL' };
  try {
    const fetchFn = wrapFetch('connection.verify.resend', null, fetch);
    const res = await fetchFn('https://api.resend.com/domains', {
      method: 'GET',
      headers: { authorization: `Bearer ${key}` },
    });
    if (res.status === 401 || res.status === 403) return { ok: false, reason: 'unauthorized' };
    if (!res.ok) return { ok: false, reason: 'invalid_key' };
    return {
      ok: true,
      account_label: from,
      last_4: fingerprintLast4(key),
      summary: { from_email: from },
    };
  } catch {
    return { ok: false, reason: 'network' };
  }
}

async function verifySupabase(creds) {
  const url = creds.SUPABASE_URL;
  const serviceKey = creds.SUPABASE_SERVICE_ROLE_KEY;
  const anonKey = creds.SUPABASE_ANON_KEY;
  const accessToken = creds.SUPABASE_ACCESS_TOKEN;
  if (!url || !/^https?:\/\//.test(url)) {
    return { ok: false, reason: 'missing_field', field: 'SUPABASE_URL' };
  }
  const apiKey = serviceKey || anonKey;
  if (!apiKey) {
    return {
      ok: false,
      reason: 'missing_field',
      field: 'SUPABASE_SERVICE_ROLE_KEY',
      hint: 'Provide either the service role key or the anon key for verification.',
    };
  }
  try {
    const probe = url.replace(/\/$/, '') + '/rest/v1/';
    const fetchFn = wrapFetch('connection.verify.supabase', null, fetch);
    const res = await fetchFn(probe, {
      method: 'GET',
      headers: { apikey: apiKey, authorization: `Bearer ${apiKey}` },
    });
    if (res.status === 401 || res.status === 403) return { ok: false, reason: 'unauthorized' };
    if (!res.ok && res.status !== 404) return { ok: false, reason: 'invalid_key' };
    let host;
    try { host = new URL(url).host; } catch { host = url; }

    // Second probe: if a Management API PAT was provided, verify it independently.
    // A bad PAT does NOT block storage of a good URL/key pair — it's reported as
    // a field-level warning in the response summary.
    let patStatus = null;
    if (accessToken) {
      try {
        const patFetch = wrapFetch('connection.verify.supabase.pat', null, fetch);
        const patRes = await patFetch('https://api.supabase.com/v1/projects', {
          method: 'GET',
          headers: { authorization: `Bearer ${accessToken}` },
        });
        if (patRes.status === 401 || patRes.status === 403) patStatus = 'unauthorized';
        else if (!patRes.ok) patStatus = 'invalid_key';
        else patStatus = 'ok';
      } catch {
        patStatus = 'network';
      }
    }

    return {
      ok: true,
      account_label: host,
      last_4: fingerprintLast4(apiKey),
      summary: {
        project_url: url,
        has_service_role: Boolean(serviceKey),
        has_access_token: Boolean(accessToken),
        access_token_status: patStatus,
      },
      warning: patStatus && patStatus !== 'ok'
        ? { field: 'SUPABASE_ACCESS_TOKEN', reason: patStatus }
        : null,
    };
  } catch {
    return { ok: false, reason: 'network' };
  }
}

async function verifyWhatsapp() {
  try {
    const health = await getBridgeHealth();
    const pairingState = health?.pairing_state || (health?.ok === false ? 'offline' : 'unknown');
    if (pairingState !== 'paired') {
      return {
        ok: false,
        reason: 'not_paired',
        pairing_state: pairingState,
        hint: 'Open /whatsapp and scan the QR with WhatsApp → Linked Devices.',
      };
    }
    return {
      ok: true,
      account_label: health.user || 'Paired',
      summary: {
        pairing_state: pairingState,
        user: health.user || null,
        last_connected_at: health.last_connected_at || null,
      },
    };
  } catch {
    return { ok: false, reason: 'network' };
  }
}

function hermesStatusForOverride(overrideDir) {
  if (!overrideDir) return getHermesStatus();
  const hermesDir = path.resolve(overrideDir);
  const script = path.join(process.cwd(), 'scripts', 'hermes-command.py');
  const dirExists = fs.existsSync(hermesDir);
  const scriptExists = fs.existsSync(script);
  return {
    available: dirExists && scriptExists,
    hermes_dir: hermesDir,
    dir_exists: dirExists,
    script_exists: scriptExists,
    env_override: true,
  };
}

function verifyVercel(creds) {
  const token = creds.VERCEL_TOKEN;
  if (!token) return { ok: false, reason: 'missing_field', field: 'VERCEL_TOKEN' };
  return {
    ok: true,
    account_label: creds.VERCEL_TEAM_ID ? `Team ${creds.VERCEL_TEAM_ID}` : 'Vercel',
    last_4: fingerprintLast4(token),
    summary: { team_id: creds.VERCEL_TEAM_ID || null, saved_without_live_verify: true },
  };
}

function verifyHermes(creds = {}) {
  const status = hermesStatusForOverride(creds.HERMES_AGENT_DIR);
  if (!status.available) {
    return {
      ok: false,
      reason: 'not_found',
      hint: status.dir_exists
        ? 'Hermes directory found but scripts/hermes-command.py is missing.'
        : `No Hermes checkout at ${status.hermes_dir || resolveHermesDir()}. Set HERMES_AGENT_DIR or clone hermes-agent next to this repo.`,
      hermes_dir: status.hermes_dir,
      script_exists: status.script_exists,
      dir_exists: status.dir_exists,
    };
  }
  return {
    ok: true,
    account_label: status.hermes_dir,
    summary: {
      hermes_dir: status.hermes_dir,
      env_override: status.env_override,
    },
  };
}

const VERIFIERS = {
  anthropic: { fn: verifyAnthropic, persist: ['ANTHROPIC_API_KEY'] },
  openai: { fn: verifyOpenAI, persist: ['OPENAI_API_KEY'] },
  kimi: { fn: verifyKimi, persist: ['KIMI_API_KEY'] },
  github: { fn: verifyGithub, persist: ['GITHUB_TOKEN'] },
  resend: { fn: verifyResend, persist: ['RESEND_API_KEY', 'RESEND_FROM_EMAIL'] },
  supabase: {
    fn: verifySupabase,
    persist: ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY', 'SUPABASE_ANON_KEY', 'SUPABASE_ACCESS_TOKEN'],
  },
  vercel: { fn: verifyVercel, persist: ['VERCEL_TOKEN', 'VERCEL_TEAM_ID'] },
  whatsapp: { fn: () => verifyWhatsapp(), persist: [] },
  hermes: { fn: verifyHermes, persistFromBody: ['HERMES_AGENT_DIR'] },
};

export async function POST(request, context) {
  const params = await Promise.resolve(context.params);
  const service = decodeURIComponent(params.service || '');
  if (!service) return Response.json({ ok: false, reason: 'missing_field', error: 'missing_service' }, { status: 400 });

  const connection = getConnection(service);
  if (!connection) {
    return Response.json({ ok: false, reason: 'not_found', error: friendly('not_found') }, { status: 404 });
  }

  // Curated verifier wins; otherwise fall back to a generic health_check verifier
  // driven entirely by the connection manifest. This gives every Tool-Smith /
  // OAuth connection that declares `health_check` a working "Test connection" with
  // no edits to this route. The fallback persists whatever credential fields the
  // manifest declares.
  let verifier = VERIFIERS[service];
  if (!verifier) {
    const healthCheck = connection.manifest?.health_check;
    if (healthCheck) {
      verifier = {
        fn: (creds) => verifyViaHealthCheck(healthCheck, creds, { serviceKey: service }),
        persist: credentialNamesFor(connection),
      };
    }
  }
  if (!verifier) {
    return Response.json(
      { ok: false, reason: 'unsupported', error: friendly('unsupported') },
      { status: 400 },
    );
  }

  const body = await readJson(request);
  const dryRun = Boolean(body?.dry_run);
  const credentials = {};
  for (const cred of (connection.connection?.credentials || connection.manifest?.credentials || [])) {
    const value = getCredential(body, cred.name);
    if (value !== null) credentials[cred.name] = value;
  }

  // Resolve and validate scope. The vault layer already supports
  // agent/project/global; the modal sends the user's choice via `scope`,
  // `project_id`, `agent_id`. Default to global for back-compat.
  let scopeTuple;
  try {
    scopeTuple = normalizeScopeInput({
      scope: body?.scope,
      project_id: body?.project_id,
      agent_id: body?.agent_id,
    });
  } catch (err) {
    return Response.json(
      { ok: false, reason: 'missing_field', error: String(err?.message || err) },
      { status: 400 },
    );
  }
  const supportsScopes = Array.isArray(connection.connection?.supports_scopes)
    ? connection.connection.supports_scopes
    : (Array.isArray(connection.manifest?.supports_scopes)
        ? connection.manifest.supports_scopes
        : ['global', 'project', 'agent']);
  if (!supportsScopes.includes(scopeTuple.scope)) {
    return Response.json(
      {
        ok: false,
        reason: 'unsupported',
        error: `${service} does not support scope '${scopeTuple.scope}'.`,
      },
      { status: 400 },
    );
  }
  const secretOpts = {
    scope: scopeTuple.scope === 'global'
      ? 'global'
      : (scopeTuple.scope === 'project' ? `project:${scopeTuple.project_id}` : `agent:${scopeTuple.agent_id}`),
    project_id: scopeTuple.project_id || undefined,
    agent_id: scopeTuple.agent_id || undefined,
    source: 'manual',
  };

  // Step 1: verify with provider BEFORE persisting any secret.
  const result = await verifier.fn(credentials);

  if (!result.ok) {
    writeAudit('connection.verify.failed', {
      service_key: service,
      reason: result.reason,
      // never include credential values in the audit
      provided_fields: Object.keys(credentials),
    }, 'failed', `Verify failed: ${service} (${result.reason})`);
    return Response.json(
      {
        ok: false,
        reason: result.reason,
        error: friendly(result.reason),
        hint: result.hint || null,
        pairing_state: result.pairing_state || null,
        field: result.field || null,
      },
      { status: 200 },
    );
  }

  if (dryRun) {
    return Response.json({
      ok: true,
      summary: {
        account_label: result.account_label || null,
        last_4: result.last_4 || null,
      },
      probe: result.summary || null,
    });
  }

  // Step 2: persist credentials to vault at the requested scope.
  if (Array.isArray(verifier.persist)) {
    for (const name of verifier.persist) {
      const value = credentials[name];
      if (value) {
        try {
          setSecret(name, value, secretOpts);
        } catch (err) {
          writeAudit('connection.verify.persist_failed', {
            service_key: service,
            name,
            scope: secretOpts.scope,
            error: String(err?.message || err),
          }, 'failed', `Failed to persist secret ${name} for ${service}`);
          return Response.json(
            { ok: false, reason: 'invalid_key', error: 'Could not store the credential. Please try again.' },
            { status: 500 },
          );
        }
      }
    }
  }
  // Optional non-vault env override (Hermes path is a directory, not a secret).
  if (Array.isArray(verifier.persistFromBody)) {
    for (const name of verifier.persistFromBody) {
      const value = credentials[name];
      if (value) {
        try {
          setSecret(name, value, secretOpts);
        } catch {
          // best-effort
        }
      }
    }
  }

  // Step 3a: upsert per-scope instance row (new shape).
  const instance = markInstanceVerified(service, scopeTuple, {
    account_label: result.account_label || null,
    last_4: result.last_4 || null,
    summary: result.summary || null,
  });

  // Step 3b: keep the legacy single-row in sync for global installs so
  // existing surfaces (status pill on the marketplace card) continue working.
  if (scopeTuple.scope === 'global') {
    try {
      markConnectionVerified(service, {
        account_label: result.account_label || null,
        last_4: result.last_4 || null,
        summary: result.summary || null,
      });
    } catch {
      // best-effort — the instance row is the source of truth going forward.
    }
  }

  writeAudit('connection.verify.succeeded', {
    service_key: service,
    scope: scopeTuple.scope,
    project_id: scopeTuple.project_id,
    agent_id: scopeTuple.agent_id,
    account_label: result.account_label || null,
    last_4: result.last_4 || null,
  }, 'ok', `Verify succeeded: ${service} (${scopeTuple.scope})`);

  const next = getConnection(service);
  return Response.json({
    ok: true,
    summary: {
      account_label: result.account_label || null,
      last_4: result.last_4 || null,
    },
    instance,
    warning: result.warning || null,
    connection: { ...next, status: summarizeConnection(next) },
  });
}
