import { readFileSync } from 'fs';
import { resolve } from 'path';

export interface SkillRegistryEntry {
  id: string;
  name: string;
  version: string;
  source: string;
  summary: string;
  publisher?: string;
  homepage?: string;
  sha256?: string;
}

export interface SkillRegistryCatalog {
  skills: SkillRegistryEntry[];
}

export type SkillRegistrySource =
  | { type: 'file'; path: string }
  | { type: 'url'; url: string };

export interface SkillRegistryLoadOptions {
  timeoutMs?: number;
  fetchImpl?: typeof fetch;
}

const DEFAULT_TIMEOUT_MS = 10_000;
const SKILL_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;

function describePath(ctx: string, key: string): string {
  return `${ctx}.${key}`;
}

function readRequiredString(value: unknown, ctx: string, key: string): string {
  if (typeof value !== 'string' || value.trim().length === 0) {
    throw new Error(`Invalid registry entry: expected non-empty string at ${describePath(ctx, key)}`);
  }
  return value.trim();
}

function readOptionalString(value: unknown, ctx: string, key: string): string | undefined {
  if (value === undefined) {
    return undefined;
  }
  if (typeof value !== 'string' || value.trim().length === 0) {
    throw new Error(`Invalid registry entry: expected non-empty string at ${describePath(ctx, key)}`);
  }
  return value.trim();
}

function parseSkillEntry(raw: unknown, index: number): SkillRegistryEntry {
  const ctx = `skills[${index}]`;
  if (!raw || typeof raw !== 'object') {
    throw new Error(`Invalid registry entry: expected object at ${ctx}`);
  }

  const obj = raw as Record<string, unknown>;
  const id = readRequiredString(obj.id, ctx, 'id');
  if (!SKILL_ID_RE.test(id)) {
    throw new Error(`Invalid registry entry: ${ctx}.id must match ${SKILL_ID_RE.source}`);
  }

  const source = readRequiredString(obj.source, ctx, 'source');
  if (!source.includes('/') && !source.includes(':') && !source.startsWith('.')) {
    throw new Error(`Invalid registry entry: ${ctx}.source must be a path or URL`);
  }

  return {
    id,
    name: readRequiredString(obj.name, ctx, 'name'),
    version: readRequiredString(obj.version, ctx, 'version'),
    source,
    summary: readRequiredString(obj.summary, ctx, 'summary'),
    publisher: readOptionalString(obj.publisher, ctx, 'publisher'),
    homepage: readOptionalString(obj.homepage, ctx, 'homepage'),
    sha256: readOptionalString(obj.sha256, ctx, 'sha256'),
  };
}

export function parseSkillRegistryCatalog(raw: unknown): SkillRegistryCatalog {
  if (!raw || typeof raw !== 'object') {
    throw new Error('Invalid registry catalog: expected top-level object');
  }

  const obj = raw as Record<string, unknown>;
  if (!Array.isArray(obj.skills)) {
    throw new Error('Invalid registry catalog: expected skills array');
  }

  return {
    skills: obj.skills.map((entry, index) => parseSkillEntry(entry, index)),
  };
}

async function fetchRegistryJson(url: string, opts: SkillRegistryLoadOptions): Promise<unknown> {
  if (!url.startsWith('https://')) {
    throw new Error(`Registry URL must use https:// (${url})`);
  }

  const fetchImpl = opts.fetchImpl ?? fetch;
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
  const signal = AbortSignal.timeout(timeoutMs);
  const response = await fetchImpl(url, { signal });

  if (!response.ok) {
    throw new Error(`Registry fetch failed: HTTP ${response.status}`);
  }

  return await response.json();
}

export async function loadSkillRegistryCatalog(
  source: SkillRegistrySource,
  opts: SkillRegistryLoadOptions = {},
): Promise<SkillRegistryCatalog> {
  if (source.type === 'file') {
    const absPath = resolve(source.path);
    const raw = readFileSync(absPath, 'utf8');
    return parseSkillRegistryCatalog(JSON.parse(raw) as unknown);
  }

  const json = await fetchRegistryJson(source.url, opts);
  return parseSkillRegistryCatalog(json);
}
