/**
 * Wizard template resolution — picks the right onboarding template
 * JSON for the current Forge instance.
 *
 * Priority (highest wins):
 *
 *   1. User-uploaded override at <dataDir>/config-template.json — admin
 *      explicitly pasted a custom template via the import API; that
 *      always wins.
 *   2. Enterprise sources, in configured priority order. Each enterprise
 *      registry.json may declare a `wizard_template` path; sync caches
 *      the resolved JSON alongside the registry (P1.W3). First enterprise
 *      source that has a cached template wins.
 *   3. Public source. forge-connectors registry.json may declare a public
 *      default (P1.W6 seeds this).
 *   4. Bundled fallback shipped inside Forge main (templates/
 *      connector-config-template.json). Callers JSON-import that
 *      directly — this module deliberately doesn't, so the bundled file
 *      stays a passive last-line-of-defence the caller owns.
 *
 * The returned `source` tag lets callers display a "using template from
 * Fortinet" affordance in the wizard, or log which tier resolved.
 *
 * No network — every read is from a per-source cache file the sync
 * layer wrote on its last successful pull. Returns `null` for tiers 1–3
 * when nothing is cached, leaving the caller free to fall through to
 * its bundled default.
 */

import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { getDataDir } from '../dirs';
import { listEnterpriseSourceMetas } from './sync';

export interface ResolvedWizardTemplate {
  /** `'user' | 'enterprise-<tenant_id>' | 'public'`. `'bundled'` is the caller's responsibility. */
  source: string;
  template: unknown;
}

function userOverridePath(): string {
  return join(getDataDir(), 'config-template.json');
}

function sourceTemplatePath(sourceId: string): string {
  return join(getDataDir(), 'connectors', 'sources', sourceId, 'wizard-template.json');
}

function sourceDeptPath(sourceId: string, deptId: string): string {
  return join(getDataDir(), 'connectors', 'sources', sourceId, 'wizards', `${deptId}.json`);
}

function sourceDeptIndexFile(sourceId: string): string {
  return join(getDataDir(), 'connectors', 'sources', sourceId, 'wizards', '_index.json');
}

export interface DeptInfo {
  id: string;
  display_name: string;
  /** False when the dept exists in the registry without a `path` — the
   *  user can still pick it as their department, but template
   *  customization falls through to public/bundled defaults. */
  has_template: boolean;
}

/**
 * Return the list of department templates cached for a source, or [] if
 * the source has only a single (non-dept-aware) template. The index is
 * written by sync.ts when the source's registry advertises
 * `wizard_templates: [...]`.
 */
export function listSourceDepartments(sourceId: string): DeptInfo[] {
  const idx = readJsonOrNull(sourceDeptIndexFile(sourceId));
  if (Array.isArray(idx)) {
    return idx
      .filter((d: any) => d && typeof d.id === 'string')
      .map((d: any) => ({
        id: d.id,
        display_name: d.display_name || d.id,
        // Legacy index entries (pre template-less) had no `has_template`
        // — assume true so existing caches keep behaving as before.
        has_template: d.has_template !== false,
      }));
  }
  return [];
}

function readJsonOrNull(path: string): unknown | null {
  if (!existsSync(path)) return null;
  try {
    return JSON.parse(readFileSync(path, 'utf-8'));
  } catch {
    return null;
  }
}

/**
 * Resolve the wizard template across all priority tiers. Returns
 * `null` if no tier above the bundled fallback has anything — callers
 * are expected to drop to their statically-imported bundled JSON in
 * that case.
 *
 * When `sourceId` is given, bypass the priority chain and read ONLY
 * that source's cached template. Used by the per-tenant wizard flow
 * (E2): the UI passes `?source_id=enterprise-fortinet` to scope to a
 * specific tenant instead of resolving across all of them. Returns
 * `null` if the named source has no cached template (caller decides
 * whether to fall back or surface a "not synced yet" error).
 */
export function resolveWizardTemplate(sourceId?: string, deptId?: string): ResolvedWizardTemplate | null {
  if (sourceId) {
    if (sourceId === 'user') {
      const user = readJsonOrNull(userOverridePath());
      return user ? { source: 'user', template: user } : null;
    }
    // E3: dept-scoped lookup. When `deptId` is given, read the per-dept
    // file. When omitted, prefer the dept index's first entry (so
    // sources that ship only dept templates still resolve a sensible
    // default), then fall back to the legacy single template.
    if (deptId) {
      const t = readJsonOrNull(sourceDeptPath(sourceId, deptId));
      return t ? { source: sourceId, template: t } : null;
    }
    const depts = listSourceDepartments(sourceId);
    if (depts.length > 0) {
      const first = readJsonOrNull(sourceDeptPath(sourceId, depts[0].id));
      if (first) return { source: sourceId, template: first };
    }
    const t = readJsonOrNull(sourceTemplatePath(sourceId));
    return t ? { source: sourceId, template: t } : null;
  }

  // Tier 1 — user override
  const user = readJsonOrNull(userOverridePath());
  if (user) return { source: 'user', template: user };

  // Tier 2 — enterprise sources, priority order. Each source may ship
  // either the legacy single wizard-template.json or the E3 multi-dept
  // wizards/ tree; both shapes count as "has a template" for this tier.
  for (const source of listEnterpriseSourceMetas()) {
    const depts = listSourceDepartments(source.id);
    if (depts.length > 0) {
      const first = readJsonOrNull(sourceDeptPath(source.id, depts[0].id));
      if (first) return { source: source.id, template: first };
    }
    const t = readJsonOrNull(sourceTemplatePath(source.id));
    if (t) return { source: source.id, template: t };
  }

  // Tier 3 — public source cache (same dual-shape lookup).
  const publicDepts = listSourceDepartments('public');
  if (publicDepts.length > 0) {
    const first = readJsonOrNull(sourceDeptPath('public', publicDepts[0].id));
    if (first) return { source: 'public', template: first };
  }
  const publicT = readJsonOrNull(sourceTemplatePath('public'));
  if (publicT) return { source: 'public', template: publicT };

  // Tier 4 — caller falls back to bundled
  return null;
}
