/**
 * Enterprise Keys API — manage marketplace source overlays.
 *
 *   GET  /api/enterprise-keys
 *     → { sources: [{ tenant_id, display_name, repo_url, priority, pat_preview }] }
 *     Read-only view. PATs are masked.
 *
 *   POST /api/enterprise-keys
 *     body: { key: string }   ← short `<tenant>:<pat>` or long `<pat>@<repo>`
 *     → { ok: true, tenant_id } or { ok: false, error }
 *     Persists the new key and triggers a one-shot connector + workflow sync
 *     so the marketplace immediately reflects the new source.
 *
 *   DELETE /api/enterprise-keys?tenant_id=<id>
 *     → { ok: true } or { ok: false, error }
 *     Removes the key. Installed connectors/workflows STAY on disk (with
 *     their last-known manifests) — the user still has them, just no more
 *     updates from this source.
 *
 * Auth handled by middleware. All operations are local; the key value is
 * encrypted at rest with .encrypt-key. Never log raw keys.
 */

import { NextResponse } from 'next/server';
import {
  listEnterpriseSources,
  addEnterpriseKey,
  removeEnterpriseKey,
  updateEnterpriseKey,
} from '@/lib/enterprise';
import { syncRegistry, getLastSync, probeEnterpriseKey } from '@/lib/connectors/sync';
import { syncMarketplace as syncWorkflows } from '@/lib/workflow-marketplace';
import { syncSkills } from '@/lib/skills';

function patPreview(pat: string): string {
  if (!pat) return '';
  if (pat.length <= 12) return pat.slice(0, 4) + '…';
  return pat.slice(0, 7) + '…' + pat.slice(-4);
}

// Enterprise source id is `enterprise-<tenant_id>` per lib/enterprise.ts.
// Keep this lookup in sync with that convention.
function sourceIdFor(tenantId: string): string {
  return `enterprise-${tenantId}`;
}

export async function GET() {
  // E3: per-tenant dept list so the Settings UI can show
  // "fortinet · FortiNAC, FortiADC" instead of just the tenant name.
  // listSourceDepartments reads <sourceId>/wizards/_index.json which
  // sync writes when the registry advertises wizard_templates.
  const { listSourceDepartments } = await import('@/lib/connectors/wizard-template');
  const { listInstalledConnectors } = await import('@/lib/connectors/registry');
  // Active dept = the dept stamped on any connector installed from
  // this tenant (setConnectorDept writes _department_name during
  // applyConnectors). All connectors from one apply share the same
  // dept; using the first hit per tenant is fine.
  const installed = listInstalledConnectors();
  const activeDeptByTenant: Record<string, string> = {};
  for (const c of installed) {
    if (!c.installed_source_id || !c.installed_dept) continue;
    if (!activeDeptByTenant[c.installed_source_id]) {
      activeDeptByTenant[c.installed_source_id] = c.installed_dept;
    }
  }
  const sources = listEnterpriseSources().map((s) => {
    const sourceId = sourceIdFor(s.tenant_id);
    const last = getLastSync(sourceId);
    const departments = listSourceDepartments(sourceId);
    const activeDeptName = activeDeptByTenant[sourceId] || null;
    const active_dept = activeDeptName
      ? departments.find(d => d.display_name === activeDeptName || d.id === activeDeptName.toLowerCase())?.id ?? null
      : null;
    return {
      tenant_id: s.tenant_id,
      display_name: s.display_name,
      repo_url: s.repo_url,
      priority: s.priority,
      pat_preview: patPreview(s.github_pat),
      last_sync: last,
      departments,
      active_dept,
    };
  });
  return NextResponse.json({ sources });
}

export async function POST(req: Request) {
  let body: any = {};
  try { body = await req.json(); } catch {}

  // action=resync — re-run syncRegistry against all sources and return the
  // per-source result so the panel can flag stuck sources without waiting
  // for the next marketplace fetch.
  if (body?.action === 'resync') {
    // refreshInstalled=true → also re-pull each installed connector's
    // manifest from the highest-priority source. force=true → re-pull
    // even when the cached registry version matches the installed one
    // (the only way to flip a connector from public→enterprise when
    // both sources publish the same version string).
    const refreshInstalled = !!body?.refreshInstalled;
    const force = !!body?.force;
    try {
      // Re-sync covers ALL data kinds from every source so the user
      // doesn't have to chase per-tab sync buttons:
      //   - connectors: registry.json + (when refreshInstalled) manifests
      //   - workflows: pipelines + recipes
      //   - skills: registry + per-skill metadata
      // syncWorkflows / syncSkills are best-effort — a flaky upstream
      // shouldn't fail the whole resync.
      const result = await syncRegistry({ refreshInstalled, force });
      try { await syncWorkflows(); } catch (e) { console.warn('[enterprise-keys] workflows sync after resync failed:', e); }
      try { await syncSkills(); } catch (e) { console.warn('[enterprise-keys] skills sync after resync failed:', e); }

      return NextResponse.json({ ok: true, result });
    } catch (e) {
      return NextResponse.json({ ok: false, error: e instanceof Error ? e.message : String(e) }, { status: 500 });
    }
  }

  const key = String(body?.key || '').trim();
  if (!key) return NextResponse.json({ ok: false, error: 'key is required' }, { status: 400 });

  // Probe BEFORE persisting — a key whose PAT is wrong / expired or whose
  // repo we can't reach should never land on disk. Surfaces the real
  // 401/403/404/network error to the user instead of silently failing
  // later inside the best-effort sync.
  const probe = await probeEnterpriseKey(key);
  if (!probe.ok) return NextResponse.json({ ok: false, error: probe.error }, { status: 400 });

  const r = addEnterpriseKey(key);
  if (!r.ok) return NextResponse.json(r, { status: 400 });

  // Trigger sync so the newly-added source is reflected in the marketplace
  // without the user having to click Refresh. Best-effort: ignore errors —
  // a network blip shouldn't fail the key-add itself.
  try {
    await syncRegistry({ refreshInstalled: false });
    await syncWorkflows();
  } catch (e) {
    console.warn('[enterprise-keys] post-add sync failed (best-effort):', e);
  }

  // E4: detect whether the newly-added source landed a wizard template
  // on disk so the frontend can hand off to /onboarding scoped to this
  // tenant. existsSync against both the legacy single-template file and
  // the multi-dept index covers the E3 split.
  const sourceId = `enterprise-${r.tenant_id}`;
  let hasWizardTemplate = false;
  try {
    const { existsSync } = await import('node:fs');
    const { join } = await import('node:path');
    const { getDataDir } = await import('@/lib/dirs');
    const base = join(getDataDir(), 'connectors', 'sources', sourceId);
    hasWizardTemplate = existsSync(join(base, 'wizard-template.json'))
      || existsSync(join(base, 'wizards', '_index.json'));
  } catch {}

  return NextResponse.json({
    ok: true,
    tenant_id: r.tenant_id,
    source_id: sourceId,
    has_wizard_template: hasWizardTemplate,
  });
}

/**
 *   PATCH /api/enterprise-keys?tenant_id=<id>
 *     body: { key: string }
 *     → { ok: true } or { ok: false, error }
 *     Replaces the key for `tenant_id` in place (the new key's parsed
 *     tenant_id must match). Useful for PAT rotation without losing
 *     priority/order. Re-syncs after success so the new auth is used
 *     immediately.
 */
export async function PATCH(req: Request) {
  const url = new URL(req.url);
  const tenant_id = (url.searchParams.get('tenant_id') || '').trim();
  if (!tenant_id) return NextResponse.json({ ok: false, error: 'tenant_id is required' }, { status: 400 });

  let body: any = {};
  try { body = await req.json(); } catch {}
  const key = String(body?.key || '').trim();
  if (!key) return NextResponse.json({ ok: false, error: 'key is required' }, { status: 400 });

  const probe = await probeEnterpriseKey(key);
  if (!probe.ok) return NextResponse.json({ ok: false, error: probe.error }, { status: 400 });

  const r = updateEnterpriseKey(tenant_id, key);
  if (!r.ok) return NextResponse.json(r, { status: 400 });

  try {
    await syncRegistry({ refreshInstalled: false });
    await syncWorkflows();
  } catch (e) {
    console.warn('[enterprise-keys] post-update sync failed (best-effort):', e);
  }

  return NextResponse.json({ ok: true });
}

export async function DELETE(req: Request) {
  const url = new URL(req.url);
  const tenant_id = (url.searchParams.get('tenant_id') || '').trim();
  if (!tenant_id) return NextResponse.json({ ok: false, error: 'tenant_id is required' }, { status: 400 });
  const removed = removeEnterpriseKey(tenant_id);
  if (!removed) return NextResponse.json({ ok: false, error: `no key for tenant '${tenant_id}'` }, { status: 404 });

  // No re-sync on delete — installed manifests stay valid; UI's next refresh
  // will repopulate the cache list. Skipping the network call keeps Remove
  // snappy.

  return NextResponse.json({ ok: true });
}
