import * as fs from 'node:fs';
import * as path from 'node:path';

type JsonRecord = Record<string, unknown>;

export interface IMatrixPackageEnvironment {
  readonly name: string;
  readonly nats?: {
    readonly mode?: string;
    readonly port?: number;
    readonly wsPort?: number;
    readonly url?: string;
    readonly wsUrl?: string;
    readonly credentialsRef?: string;
    readonly dataDir?: string;
    readonly pidFile?: string;
    readonly binaryPath?: string;
  };
  readonly runtime: {
    readonly root: string;
    readonly runtimeId?: string;
    readonly runtimeMount?: string;
    readonly controlMount?: string;
  };
  readonly package?: {
    readonly publicRoot?: string;
  };
  readonly http?: {
    readonly enabled?: boolean;
    readonly port?: number;
  };
  readonly host?: {
    readonly matrixDir?: string;
  };
  readonly topology?: Record<string, unknown>;
  readonly authorityLease?: Record<string, unknown> | null;
}

export interface ILoadedMatrixPackageEnvironment {
  readonly packageDir: string;
  readonly envName: string;
  readonly environmentPath: string;
  readonly environment: IMatrixPackageEnvironment;
}

export function loadPackageEnvironment(packageDir: string, envName: string): ILoadedMatrixPackageEnvironment {
  const resolvedPackageDir = path.resolve(packageDir);
  const cleanEnvName = envName.trim();
  if (cleanEnvName.length === 0) {
    throw new Error('Package environment name must be a non-empty string');
  }

  const environmentPath = path.join(resolvedPackageDir, '.matrix', `${cleanEnvName}.environment.json`);
  if (!fs.existsSync(environmentPath)) {
    throw new Error(`Package environment "${cleanEnvName}" not found at ${environmentPath}`);
  }

  return loadPackageEnvironmentFromPath(resolvedPackageDir, environmentPath, cleanEnvName);
}

export function loadPackageEnvironmentFromPath(
  packageDir: string,
  environmentPath: string,
  envName?: string,
): ILoadedMatrixPackageEnvironment {
  const resolvedPackageDir = path.resolve(packageDir);
  const resolvedEnvironmentPath = path.resolve(environmentPath);
  if (!fs.existsSync(resolvedEnvironmentPath)) {
    throw new Error(`Package environment file not found at ${resolvedEnvironmentPath}`);
  }

  const label = path.basename(resolvedEnvironmentPath);
  const parsed = readJsonObject(resolvedEnvironmentPath, label);
  const name = readRequiredString(parsed.name, `Package environment "${envName ?? label}" requires name`);
  const cleanEnvName = readOptionalString(envName) ?? name;
  const runtime = asRecord(parsed.runtime);
  const root = readRequiredString(runtime?.root, `Package environment "${cleanEnvName}" requires runtime.root`);

  const environment: IMatrixPackageEnvironment = {
    name,
    runtime: {
      root,
      ...(readOptionalString(runtime?.runtimeId) ? { runtimeId: readOptionalString(runtime?.runtimeId) } : {}),
      ...(readOptionalString(runtime?.runtimeMount) ? { runtimeMount: readOptionalString(runtime?.runtimeMount) } : {}),
      ...(readOptionalString(runtime?.controlMount) ? { controlMount: readOptionalString(runtime?.controlMount) } : {}),
    },
    ...(asRecord(parsed.nats) ? { nats: asRecord(parsed.nats) as IMatrixPackageEnvironment['nats'] } : {}),
    ...(asRecord(parsed.package) ? { package: asRecord(parsed.package) as IMatrixPackageEnvironment['package'] } : {}),
    ...(asRecord(parsed.http) ? { http: asRecord(parsed.http) as IMatrixPackageEnvironment['http'] } : {}),
    ...(asRecord(parsed.host) ? { host: asRecord(parsed.host) as IMatrixPackageEnvironment['host'] } : {}),
    ...(asRecord(parsed.topology) ? { topology: asRecord(parsed.topology) } : {}),
    ...(parsed.authorityLease === null || asRecord(parsed.authorityLease)
      ? { authorityLease: parsed.authorityLease === null ? null : asRecord(parsed.authorityLease) }
      : {}),
  };

  return {
    packageDir: resolvedPackageDir,
    envName: cleanEnvName,
    environmentPath: resolvedEnvironmentPath,
    environment,
  };
}

function readJsonObject(filePath: string, label: string): JsonRecord {
  try {
    const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, ''));
    if (!isRecord(parsed)) {
      throw new Error(`${label} must contain a JSON object`);
    }
    return parsed;
  } catch (err) {
    throw new Error(`Failed to parse ${label}: ${err instanceof Error ? err.message : String(err)}`);
  }
}

function readRequiredString(value: unknown, message: string): string {
  const resolved = readOptionalString(value);
  if (!resolved) {
    throw new Error(message);
  }
  return resolved;
}

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

function isRecord(value: unknown): value is JsonRecord {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function asRecord(value: unknown): JsonRecord | undefined {
  return isRecord(value) ? value : undefined;
}
