/**
 * Converts Prisma DB models to mcpctl-apply-compatible YAML.
 * Produces output identical to `mcpctl get <resource> <name> -o yaml`.
 */
import yaml from 'js-yaml';
import type { PrismaClient } from '@prisma/client';

const INTERNAL_FIELDS = new Set([
  'id', 'createdAt', 'updatedAt', 'version', 'ownerId', 'summary',
  'chapters', 'linkStatus', 'serverId', 'passwordHash',
]);

const FIRST_KEYS = ['kind'];
const LAST_KEYS = ['link', 'content', 'prompt', 'data'];

/** Strip internal fields, transform relations, normalize — same logic as CLI's stripInternalFields. */
function toApplyDoc(kind: string, raw: Record<string, unknown>): Record<string, unknown> {
  const result: Record<string, unknown> = { kind };
  const isLinkedPrompt = !!raw.linkTarget;

  for (const [key, value] of Object.entries(raw)) {
    if (INTERNAL_FIELDS.has(key)) continue;
    if (value === null || value === undefined) continue;

    // Servers join array → string[] of names
    if (key === 'servers' && Array.isArray(value)) {
      const entries = value as Array<{ server?: { name: string } }>;
      if (entries.length > 0 && entries[0]?.server) {
        result.servers = entries.map((e) => e.server!.name);
      } else {
        result.servers = entries.length === 0 ? [] : value;
      }
      continue;
    }

    // linkTarget → link, strip content for linked prompts
    if (key === 'linkTarget') {
      if (value) {
        result.link = value;
      }
      continue;
    }

    // Content is fetched from link source — don't include in YAML for linked prompts
    if (key === 'content' && isLinkedPrompt) continue;

    // Normalize proxyModel from gated
    if (key === 'gated') continue; // handled with proxyModel
    if (key === 'proxyModel') {
      const pm = value as string;
      result.proxyModel = pm || (raw.gated === false ? 'content-pipeline' : 'default');
      continue;
    }

    // Project relation → project name
    if (key === 'project' && typeof value === 'object' && value !== null) {
      result.project = (value as { name: string }).name;
      continue;
    }
    if (key === 'projectId') continue; // stripped, use project name

    // Owner relation → strip
    if (key === 'owner' && typeof value === 'object') continue;

    // Group members → email array
    if (key === 'members' && Array.isArray(value)) {
      result.members = (value as Array<{ user?: { email: string } }>)
        .map((m) => m.user?.email)
        .filter(Boolean);
      continue;
    }

    // ServerOverrides: keep as-is if not empty
    if (key === 'serverOverrides') {
      if (value && typeof value === 'object' && Object.keys(value as object).length > 0) {
        result[key] = value;
      }
      continue;
    }

    result[key] = value;
  }

  return result;
}

/** Reorder keys: kind first, long fields last — matches CLI output format. */
function reorderKeys(obj: Record<string, unknown>): Record<string, unknown> {
  const ordered: Record<string, unknown> = {};
  for (const key of FIRST_KEYS) {
    if (key in obj) ordered[key] = obj[key];
  }
  for (const key of Object.keys(obj)) {
    if (!FIRST_KEYS.includes(key) && !LAST_KEYS.includes(key)) ordered[key] = obj[key];
  }
  for (const key of LAST_KEYS) {
    if (key in obj) ordered[key] = obj[key];
  }
  return ordered;
}

/** Convert a single resource to YAML string (apply-compatible). */
export function resourceToYaml(kind: string, resource: Record<string, unknown>): string {
  const doc = toApplyDoc(kind, resource);
  const ordered = reorderKeys(doc);
  return yaml.dump(ordered, { lineWidth: 120, noRefs: true }).trimEnd();
}

/** Compute the file path for a resource in the backup repo. */
export function resourcePath(kind: string, name: string): string {
  const dir = kind === 'rbac' ? 'rbac' : `${kind}s`;
  const safeName = name.replace(/[/\\:*?"<>|]/g, '_');
  return `${dir}/${safeName}.yaml`;
}

/** Resource kinds that are backed up. */
export const BACKUP_KINDS = ['server', 'secret', 'project', 'user', 'group', 'rbac', 'prompt', 'template'] as const;
export type BackupKind = (typeof BACKUP_KINDS)[number];

/** Apply order: dependencies before dependents. */
export const APPLY_ORDER: BackupKind[] = ['secret', 'server', 'template', 'user', 'group', 'project', 'rbac', 'prompt'];

/** Parse a file path to extract kind and name. Returns null if path doesn't match backup structure. */
export function parseResourcePath(filePath: string): { kind: BackupKind; name: string } | null {
  const match = filePath.match(/^(\w+)\/(.+)\.yaml$/);
  if (!match) return null;
  const [, dir, name] = match;
  // Map directory back to kind
  const kindMap: Record<string, BackupKind> = {
    servers: 'server', secrets: 'secret', projects: 'project',
    users: 'user', groups: 'group', rbac: 'rbac',
    prompts: 'prompt', templates: 'template',
  };
  const kind = kindMap[dir!];
  if (!kind) return null;
  return { kind, name: name! };
}

/** Dump all resources from DB to a map of filePath → yamlContent. */
export async function serializeAll(prisma: PrismaClient): Promise<Map<string, string>> {
  const files = new Map<string, string>();

  // Servers
  const servers = await prisma.mcpServer.findMany();
  for (const s of servers) {
    files.set(resourcePath('server', s.name), resourceToYaml('server', s as unknown as Record<string, unknown>));
  }

  // Secrets
  const secrets = await prisma.secret.findMany();
  for (const s of secrets) {
    files.set(resourcePath('secret', s.name), resourceToYaml('secret', s as unknown as Record<string, unknown>));
  }

  // Projects (with server names)
  const projects = await prisma.project.findMany({
    include: { servers: { include: { server: { select: { name: true } } } } },
  });
  for (const p of projects) {
    files.set(resourcePath('project', p.name), resourceToYaml('project', p as unknown as Record<string, unknown>));
  }

  // Users (without password hash)
  const users = await prisma.user.findMany();
  for (const u of users) {
    files.set(resourcePath('user', u.email), resourceToYaml('user', u as unknown as Record<string, unknown>));
  }

  // Groups (with member emails)
  const groups = await prisma.group.findMany({
    include: { members: { include: { user: { select: { email: true } } } } },
  });
  for (const g of groups) {
    files.set(resourcePath('group', g.name), resourceToYaml('group', g as unknown as Record<string, unknown>));
  }

  // RBAC definitions
  const rbacs = await prisma.rbacDefinition.findMany();
  for (const r of rbacs) {
    files.set(resourcePath('rbac', r.name), resourceToYaml('rbac', r as unknown as Record<string, unknown>));
  }

  // Prompts (with project name)
  const prompts = await prisma.prompt.findMany({
    include: { project: { select: { name: true } } },
  });
  for (const p of prompts) {
    files.set(resourcePath('prompt', p.name), resourceToYaml('prompt', p as unknown as Record<string, unknown>));
  }

  // Templates
  const templates = await prisma.mcpTemplate.findMany();
  for (const t of templates) {
    files.set(resourcePath('template', t.name), resourceToYaml('template', t as unknown as Record<string, unknown>));
  }

  return files;
}
