import * as fs from 'node:fs';
import * as path from 'node:path';
import {
  normalizeMountedInstanceSpec,
  type IMountedInstanceMetadataInput,
  type IMountedInstanceSecretRefInput,
} from '@open-matrix/core/runtime/MountedInstanceMetadata.js';

type JsonRecord = Record<string, unknown>;

export interface IMatrixServiceSecretRef extends IMountedInstanceSecretRefInput {}

export interface IMatrixServiceInstanceManifest extends IMountedInstanceMetadataInput {
  readonly secretRefs?: ReadonlyArray<IMatrixServiceSecretRef>;
}

export interface IMatrixServiceBootstrapManifest {
  readonly overrides?: Record<string, unknown>;
  readonly root?: string;
  readonly transport?: Record<string, unknown>;
  readonly auth?: Record<string, unknown>;
  readonly http?: Record<string, unknown>;
}

export interface IMatrixRuntimeManifest {
  readonly language?: string;
  readonly entry?: string;
  readonly factory?: IMatrixServiceManifest;
}

export interface IMatrixServiceManifest {
  readonly kind?: 'factory';
  readonly entry?: string;
  readonly export?: string;
  /** Standalone-only bootstrap envelope. */
  readonly bootstrap?: IMatrixServiceBootstrapManifest;
  readonly overrides?: Record<string, unknown>;
  readonly root?: string;
  readonly mount?: string;
  readonly config?: {
    readonly file?: string;
  };
  readonly secrets?: {
    readonly file?: string;
  };
  readonly transport?: Record<string, unknown>;
  readonly auth?: Record<string, unknown>;
  readonly http?: Record<string, unknown>;
  readonly dependsOn?: ReadonlyArray<string>;
  readonly policyRef?: string;
  readonly instance?: IMatrixServiceInstanceManifest;
}

export interface IServiceInstanceBootstrap {
  readonly id: string;
  readonly packageName: string;
  readonly packageRevision?: string;
  readonly class: string;
  readonly mount?: string;
  readonly rootKind: string;
  readonly componentType?: string;
  readonly configRef?: string;
  readonly secretRefs?: ReadonlyArray<{
    readonly name: string;
    readonly provider: string;
    readonly ref: string;
    readonly required?: boolean;
  }>;
  readonly dependsOn?: ReadonlyArray<string>;
  readonly policyRef?: string;
  readonly autoStart: boolean;
  readonly registrationSource: 'matrix-service';
}

export interface ILoadedMatrixBindingDeclaration {
  readonly type?: string;
  readonly mount: string;
  readonly accepts: readonly string[];
}

export interface ILoadedMatrixServiceManifest {
  readonly packageDir: string;
  readonly packageName: string;
  readonly displayName?: string;
  readonly packageNamespace?: string;
  readonly manifestPath: string;
  readonly entryPath: string;
  readonly exportName: string;
  readonly kind: 'factory';
  readonly overrides: Record<string, unknown>;
  readonly root?: string;
  readonly mount?: string;
  readonly configRef?: string;
  readonly transport?: Record<string, unknown>;
  readonly auth?: Record<string, unknown>;
  readonly http?: Record<string, unknown>;
  readonly packageRoot?: ILoadedMatrixBindingDeclaration;
  readonly packageComponents: ReadonlyArray<ILoadedMatrixBindingDeclaration>;
  readonly serviceInstance: IServiceInstanceBootstrap;
}

export function packageDeclaresStandaloneFactory(packageDir: string): boolean {
  const resolvedPackageDir = path.resolve(packageDir);
  const matrixManifestPath = path.join(resolvedPackageDir, 'matrix.json');
  if (!fs.existsSync(matrixManifestPath)) {
    return false;
  }
  const matrixManifest = readOptionalJsonObject(matrixManifestPath, 'matrix.json');
  const runtime = asRecord(matrixManifest?.runtime);
  return isRecord(runtime?.factory);
}

export function loadMatrixServiceManifest(packageDir: string): ILoadedMatrixServiceManifest {
  const resolvedPackageDir = path.resolve(packageDir);
  const matrixManifestPath = path.join(resolvedPackageDir, 'matrix.json');

  const matrixManifest = readJsonObject(matrixManifestPath, 'matrix.json');
  const packageName = optionalTrimmedString(matrixManifest.name) ?? path.basename(resolvedPackageDir);
  const displayName = optionalTrimmedString(matrixManifest.displayName);
  const packageVersion = optionalTrimmedString(matrixManifest.version) ?? undefined;
  const runtime = asRecord(matrixManifest.runtime) as IMatrixRuntimeManifest | null;
  const factorySource = readFactorySource(runtime?.factory, matrixManifestPath);
  const serviceManifest = factorySource.manifest;
  const packageNamespace = optionalTrimmedString(matrixManifest.namespace);
  const runtimeEntry = optionalTrimmedString(runtime?.entry);
  const components = Array.isArray(matrixManifest.components)
    ? matrixManifest.components.filter((entry): entry is Record<string, unknown> => isRecord(entry))
    : [];
  const packageRoot = readBindingDeclaration(asRecord(matrixManifest.root));
  const packageComponents = components
    .map((entry) => readBindingDeclaration(entry))
    .filter((entry): entry is ILoadedMatrixBindingDeclaration => entry !== null);
  const kind = optionalTrimmedString(serviceManifest.kind) ?? 'factory';
  if (kind !== 'factory') {
    throw new Error(`${factorySource.label} kind must be "factory", got "${kind}"`);
  }

  const entry = optionalTrimmedString(serviceManifest.entry) ?? runtimeEntry;
  if (!entry) {
    throw new Error(`${factorySource.label} requires entry or matrix.json runtime.entry`);
  }

  const exportName = optionalTrimmedString(serviceManifest.export);
  if (!exportName) {
    throw new Error(`${factorySource.label} export must be a non-empty string`);
  }

  const bootstrap = asRecord(serviceManifest.bootstrap);
  assertUnsupportedStandaloneRecordRelocation({
    sourceLabel: factorySource.label,
    oldPath: 'overrides',
    oldValue: serviceManifest.overrides,
    canonicalPath: 'bootstrap.overrides',
  });
  const overrides = asRecord(bootstrap?.overrides) ?? {};
  assertUnsupportedStandaloneStringRelocation({
    sourceLabel: factorySource.label,
    oldPath: 'root',
    oldValue: serviceManifest.root,
    canonicalPath: 'bootstrap.root',
  });
  const root = optionalTrimmedString(bootstrap?.root) ?? undefined;
  assertUnsupportedStandaloneRecordRelocation({
    sourceLabel: factorySource.label,
    oldPath: 'transport',
    oldValue: serviceManifest.transport,
    canonicalPath: 'bootstrap.transport',
  });
  const transport = asRecord(bootstrap?.transport);
  assertUnsupportedStandaloneRecordRelocation({
    sourceLabel: factorySource.label,
    oldPath: 'auth',
    oldValue: serviceManifest.auth,
    canonicalPath: 'bootstrap.auth',
  });
  const auth = asRecord(bootstrap?.auth);
  assertUnsupportedStandaloneRecordRelocation({
    sourceLabel: factorySource.label,
    oldPath: 'http',
    oldValue: serviceManifest.http,
    canonicalPath: 'bootstrap.http',
  });
  const http = asRecord(bootstrap?.http);
  const instance = asRecord(serviceManifest.instance);
  const instancePackageName = optionalTrimmedString(instance?.packageName) ?? packageName;
  const declaredPackageRevision = optionalTrimmedString(instance?.packageRevision);
  const instancePackageRevision = declaredPackageRevision ?? packageVersion;
  if (optionalTrimmedString(instance?.packageName) && instancePackageName !== packageName) {
    throw new Error(`${factorySource.label} instance.packageName "${instancePackageName}" does not match matrix.json name "${packageName}"`);
  }
  if (declaredPackageRevision && packageVersion && declaredPackageRevision !== packageVersion) {
    throw new Error(`${factorySource.label} instance.packageRevision "${declaredPackageRevision}" does not match matrix.json version "${packageVersion}"`);
  }
  const rootKind = optionalTrimmedString(instance?.rootKind) ?? 'package-root';
  const componentType = optionalTrimmedString(instance?.componentType);
  const instanceMount = optionalTrimmedString(instance?.mount);
  assertUnsupportedStandaloneStringRelocation({
    sourceLabel: factorySource.label,
    oldPath: 'mount',
    oldValue: serviceManifest.mount,
    canonicalPath: 'instance.mount',
  });
  const mount = instanceMount
    ?? (rootKind === 'component'
      ? resolveComponentRootMount(factorySource.label, components, componentType)
      : undefined);
  const instanceConfigRef = optionalTrimmedString(instance?.configRef);
  assertUnsupportedStandaloneStringRelocation({
    sourceLabel: factorySource.label,
    oldPath: 'config.file',
    oldValue: asRecord(serviceManifest.config)?.file,
    canonicalPath: 'instance.configRef',
  });
  const configRef = instanceConfigRef;
  assertUnsupportedStandaloneStringRelocation({
    sourceLabel: factorySource.label,
    oldPath: 'secrets.file',
    oldValue: asRecord(serviceManifest.secrets)?.file,
    canonicalPath: 'instance.secretRefs or CLI --secrets',
  });
  const serviceClass = optionalTrimmedString(instance?.class) ?? 'service';
  const instanceDependsOn = normalizeStringArray(instance?.dependsOn);
  const instancePolicyRef = optionalTrimmedString(instance?.policyRef);
  assertUnsupportedStandaloneArrayRelocation({
    sourceLabel: factorySource.label,
    oldPath: 'dependsOn',
    oldValue: serviceManifest.dependsOn,
    canonicalPath: 'instance.dependsOn',
  });
  assertUnsupportedStandaloneStringRelocation({
    sourceLabel: factorySource.label,
    oldPath: 'policyRef',
    oldValue: serviceManifest.policyRef,
    canonicalPath: 'instance.policyRef',
  });
  const serviceInstance = normalizeMountedInstanceSpec({
    id: optionalTrimmedString(instance?.id) ?? buildServiceInstanceId(instancePackageName, mount),
    packageName: instancePackageName,
    packageRevision: instancePackageRevision,
    class: serviceClass,
    mount,
    rootKind,
    componentType,
    configRef,
    secretRefs: Array.isArray(instance?.secretRefs) ? instance.secretRefs : undefined,
    dependsOn: instanceDependsOn.length > 0 ? instanceDependsOn : undefined,
    policyRef: instancePolicyRef,
    autoStart: instance?.autoStart === true,
  }, {
    registrationSource: 'matrix-service',
  }) as IServiceInstanceBootstrap;
  const entryPath = path.resolve(resolvedPackageDir, entry);
  if (!fs.existsSync(entryPath)) {
    throw new Error(`${factorySource.label} entry does not exist: ${entry}`);
  }

  return {
    packageDir: resolvedPackageDir,
    packageName,
    ...(displayName ? { displayName } : {}),
    ...(packageNamespace ? { packageNamespace } : {}),
    manifestPath: factorySource.path,
    entryPath,
    exportName,
    kind: 'factory',
    overrides: structuredClone(overrides),
    ...(root ? { root } : {}),
    ...(mount ? { mount } : {}),
    ...(configRef ? { configRef } : {}),
    ...(transport ? { transport: structuredClone(transport) } : {}),
    ...(auth ? { auth: structuredClone(auth) } : {}),
    ...(http ? { http: structuredClone(http) } : {}),
    ...(packageRoot ? { packageRoot } : {}),
    packageComponents,
    serviceInstance,
  };
}

function readFactorySource(factory: unknown, matrixManifestPath: string): {
  readonly label: string;
  readonly path: string;
  readonly manifest: IMatrixServiceManifest;
} {
  if (isRecord(factory)) {
    return {
      label: 'matrix.json runtime.factory',
      path: matrixManifestPath,
      manifest: factory as IMatrixServiceManifest,
    };
  }
  throw new Error('matrix.json runtime.factory must be a JSON object');
}

function readJsonObject(filePath: string, label: string): JsonRecord {
  const parsed = readJsonValue(filePath, label);
  if (!isRecord(parsed)) {
    throw new Error(`${label} must contain a JSON object`);
  }
  return parsed;
}

function readOptionalJsonObject(filePath: string, label: string): JsonRecord | null {
  const parsed = readJsonValue(filePath, label);
  if (!isRecord(parsed)) {
    return null;
  }
  return parsed;
}

function readJsonValue(filePath: string, label: string): unknown {
  if (!fs.existsSync(filePath)) {
    throw new Error(`${label} not found at ${filePath}`);
  }
  try {
    const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, ''));
    return parsed;
  } catch (err) {
    throw new Error(`Failed to parse ${label}: ${err instanceof Error ? err.message : String(err)}`);
  }
}

function optionalTrimmedString(value: unknown): string | undefined {
  if (typeof value !== 'string') return undefined;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : undefined;
}

function asRecord(value: unknown): Record<string, unknown> | null {
  if (!isRecord(value)) return null;
  return value;
}

function resolveComponentRootMount(
  sourceLabel: string,
  components: ReadonlyArray<Record<string, unknown>>,
  componentType: string | undefined,
): string | undefined {
  if (components.length === 0) {
    throw new Error(`${sourceLabel} declares instance.rootKind="component", but matrix.json does not declare any components`);
  }

  if (componentType) {
    const matching = components.find((entry) => optionalTrimmedString(entry.type) === componentType);
    if (!matching) {
      throw new Error(`${sourceLabel} instance.componentType "${componentType}" was not found in matrix.json components`);
    }
    const mount = optionalTrimmedString(matching.mount);
    if (!mount) {
      throw new Error(`${sourceLabel} instance.componentType "${componentType}" does not declare a component mount in matrix.json`);
    }
    return mount;
  }

  if (components.length === 1) {
    const mount = optionalTrimmedString(components[0]?.mount);
    if (!mount) {
      throw new Error(`${sourceLabel} component-root bootstrap requires an explicit mount because the only matrix.json component does not declare one`);
    }
    return mount;
  }

  throw new Error(`${sourceLabel} component-root bootstrap is ambiguous; declare instance.componentType or mount`);
}

function readBindingDeclaration(
  value: Record<string, unknown> | null | undefined,
): ILoadedMatrixBindingDeclaration | null {
  if (!value) {
    return null;
  }
  const mount = optionalTrimmedString(value.mount);
  if (!mount) {
    return null;
  }
  return {
    ...(optionalTrimmedString(value.type) ? { type: optionalTrimmedString(value.type) } : {}),
    mount,
    accepts: normalizeAccepts(value.accepts),
  };
}

function normalizeAccepts(value: unknown): readonly string[] {
  if (!Array.isArray(value)) {
    return [];
  }
  const accepts = value
    .filter((entry): entry is string => typeof entry === 'string')
    .map((entry) => entry.trim())
    .filter((entry) => entry.length > 0);
  return [...new Set(accepts)];
}

function buildServiceInstanceId(packageName: string, mount: string | undefined): string {
  return mount ? `${packageName}:${mount}` : packageName;
}

function assertUnsupportedStandaloneStringRelocation(options: {
  readonly sourceLabel: string;
  readonly oldPath: string;
  readonly oldValue: unknown;
  readonly canonicalPath: string;
}): void {
  if (optionalTrimmedString(options.oldValue) === undefined) return;
  throw new Error(
    `${options.sourceLabel} top-level "${options.oldPath}" is no longer supported; use "${options.canonicalPath}"`,
  );
}

function assertUnsupportedStandaloneArrayRelocation(options: {
  readonly sourceLabel: string;
  readonly oldPath: string;
  readonly oldValue: unknown;
  readonly canonicalPath: string;
}): void {
  if (normalizeStringArray(options.oldValue).length === 0) return;
  throw new Error(
    `${options.sourceLabel} top-level "${options.oldPath}" is no longer supported; use "${options.canonicalPath}"`,
  );
}

function assertUnsupportedStandaloneRecordRelocation(options: {
  readonly sourceLabel: string;
  readonly oldPath: string;
  readonly oldValue: unknown;
  readonly canonicalPath: string;
}): void {
  if (!isRecord(options.oldValue)) return;
  throw new Error(
    `${options.sourceLabel} top-level "${options.oldPath}" is no longer supported; use "${options.canonicalPath}"`,
  );
}

function normalizeStringArray(value: unknown): string[] {
  if (!Array.isArray(value)) return [];
  const seen = new Set<string>();
  const normalized: string[] = [];
  for (const entry of value) {
    const trimmed = optionalTrimmedString(entry);
    if (!trimmed || seen.has(trimmed)) continue;
    seen.add(trimmed);
    normalized.push(trimmed);
  }
  return normalized;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}
