import { existsSync, readFileSync } from "node:fs";
import { isAbsolute, resolve } from "node:path";

export interface ProviderAppsConfig {
  readonly configPath: string;
  readonly target: { id: string; route: string; workspace: string };
  readonly runtime: {
    bindHost: string;
    probeHost: string;
    port: number;
    stateDir: string;
    pidPath: string;
    logPath: string;
    maxLogTailBytes: number;
  };
  readonly release: {
    root: string;
    manifestPath: string;
    providerPath: string;
    routes: { manifest: string; provider: string; health: string };
  };
  readonly install: {
    root: string;
    indexPath: string;
    stylePath: string;
    scriptPath: string;
    installerPath: string;
    startCmdPath: string;
    bootstrapPath: string;
    linuxInstallerPath: string;
    faviconPath: string;
    panelImagePath: string;
    routes: {
      index: string;
      style: string;
      script: string;
      installer: string;
      startCmd: string;
      bootstrap: string;
      linuxInstaller: string;
      contract: string;
      favicon: string;
      panelImage: string;
    };
    defaults: {
      server: string;
      credentialFile: string;
      autoStart: boolean;
      pythonRuntime: "auto" | "system" | "private";
    };
    runtime: {
      root: string;
      connectTimeoutSeconds: number;
      progressIntervalSeconds: number;
      windows7: PythonRuntime;
      windowsModern: PythonRuntime;
    };
    linux: LinuxInstallConfig;
  };
  readonly publicExposure: PublicExposure;
}

export interface LinuxInstallTarget {
  bootstrap: {
    mode: "windowsSsh";
    route: string;
    guest: string;
    identityFile: string;
    stagingDirectory: string;
  };
  providerId: string;
  server: string;
  user: string;
  home: string;
  systemdUnit: string;
  network: {
    bypassKubernetesServiceNat: boolean;
    systemdUnit: string;
  };
  logsTailLines: number;
  credential: {
    sourceRef: { path: string; key: string };
    targetFile: string;
  };
  benchmark: {
    concurrency: number;
    poolProbeConcurrency: number;
    commandRounds: number;
    transferBytes: number;
    transferRounds: number;
    transferTimeoutSeconds: number;
    readyTimeoutSeconds: number;
  };
}

export interface LinuxInstallConfig {
  minimumPythonVersion: string;
  privateRuntimeDirectory: string;
  systemdRestartSeconds: number;
  pipSources: Array<{ id: string; indexUrl: string }>;
  targets: Record<string, LinuxInstallTarget>;
}

export interface PythonRuntime {
  version: string;
  minimumVersion: string;
  directory: string;
  fileName: string;
  bytes: number;
  sha256: string;
  sources: Array<{ id: string; url: string; pipIndexUrl: string }>;
}

export interface PublicExposure {
  enabled: boolean;
  publicBaseUrl: string;
  hostname: string;
  expectedA: string;
  upstream: string;
  healthPath: string;
}

function record(value: unknown, path: string): Record<string, unknown> {
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be a mapping`);
  return value as Record<string, unknown>;
}

function string(value: unknown, path: string): string {
  if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path} must be a non-empty string`);
  return value.trim();
}

function integer(value: unknown, path: string): number {
  if (!Number.isInteger(value) || Number(value) <= 0) throw new Error(`${path} must be a positive integer`);
  return Number(value);
}

function choice<T extends string>(value: unknown, path: string, allowed: readonly T[]): T {
  const result = string(value, path);
  if (!allowed.includes(result as T)) throw new Error(`${path} must be one of ${allowed.join(", ")}`);
  return result as T;
}

function relativePath(value: unknown, path: string): string {
  const result = string(value, path);
  if (isAbsolute(result) || result.split(/[\\/]/u).includes("..")) throw new Error(`${path} must stay inside the repository`);
  return result;
}

function absolutePosixPath(value: unknown, path: string): string {
  const result = string(value, path);
  if (!result.startsWith("/") || result.split("/").includes("..")) throw new Error(`${path} must be an absolute POSIX path`);
  return result;
}

function httpPath(value: unknown, path: string): string {
  const result = string(value, path);
  if (!result.startsWith("/") || result.includes("..") || result.includes("?") || result.includes("#")) {
    throw new Error(`${path} must be an absolute HTTP path`);
  }
  return result;
}

function url(value: unknown, path: string, protocols: string[]): string {
  const result = string(value, path);
  const parsed = new URL(result);
  if (!protocols.includes(parsed.protocol)) throw new Error(`${path} must use ${protocols.join(" or ")}`);
  return result;
}

function pythonRuntime(value: unknown, path: string): PythonRuntime {
  const raw = record(value, path);
  const sources = raw.sources;
  if (!Array.isArray(sources) || sources.length === 0) throw new Error(`${path}.sources must be a non-empty list`);
  const result = {
    version: string(raw.version, `${path}.version`),
    minimumVersion: string(raw.minimumVersion, `${path}.minimumVersion`),
    directory: relativePath(raw.directory, `${path}.directory`),
    fileName: string(raw.fileName, `${path}.fileName`),
    bytes: integer(raw.bytes, `${path}.bytes`),
    sha256: string(raw.sha256, `${path}.sha256`).toLowerCase(),
    sources: sources.map((source, index) => {
      const item = record(source, `${path}.sources[${index}]`);
      const sha = string(item.id, `${path}.sources[${index}].id`);
      if (!/^[a-z0-9-]+$/u.test(sha)) throw new Error(`${path}.sources[${index}].id is invalid`);
      return {
        id: sha,
        url: url(item.url, `${path}.sources[${index}].url`, ["https:"]),
        pipIndexUrl: url(item.pipIndexUrl, `${path}.sources[${index}].pipIndexUrl`, ["https:"]),
      };
    }),
  };
  if (!/^[0-9a-f]{64}$/u.test(result.sha256)) throw new Error(`${path}.sha256 must be a SHA-256`);
  return result;
}

function linuxInstall(value: unknown, path: string, defaultServer: string): LinuxInstallConfig {
  const raw = record(value, path);
  const pipSources = raw.pipSources;
  if (!Array.isArray(pipSources) || pipSources.length === 0) throw new Error(`${path}.pipSources must be a non-empty list`);
  const rawTargets = record(raw.targets, `${path}.targets`);
  const targets: Record<string, LinuxInstallTarget> = {};
  for (const [targetId, targetValue] of Object.entries(rawTargets)) {
    if (!/^[A-Za-z0-9_.-]{1,64}$/u.test(targetId)) throw new Error(`${path}.targets contains an invalid target id`);
    const target = record(targetValue, `${path}.targets.${targetId}`);
    const credential = record(target.credential, `${path}.targets.${targetId}.credential`);
    const sourceRef = record(credential.sourceRef, `${path}.targets.${targetId}.credential.sourceRef`);
    const benchmark = record(target.benchmark, `${path}.targets.${targetId}.benchmark`);
    const providerId = string(target.providerId, `${path}.targets.${targetId}.providerId`);
    if (providerId !== targetId) throw new Error(`${path}.targets.${targetId}.providerId must match its target key`);
    const systemdUnit = string(target.systemdUnit, `${path}.targets.${targetId}.systemdUnit`);
    if (!/^[A-Za-z0-9_.@-]+\.service$/u.test(systemdUnit)) throw new Error(`${path}.targets.${targetId}.systemdUnit is invalid`);
    const network = record(target.network, `${path}.targets.${targetId}.network`);
    if (typeof network.bypassKubernetesServiceNat !== "boolean") {
      throw new Error(`${path}.targets.${targetId}.network.bypassKubernetesServiceNat must be boolean`);
    }
    const networkSystemdUnit = string(network.systemdUnit, `${path}.targets.${targetId}.network.systemdUnit`);
    if (!/^[A-Za-z0-9_.@-]+\.service$/u.test(networkSystemdUnit)) {
      throw new Error(`${path}.targets.${targetId}.network.systemdUnit is invalid`);
    }
    const bootstrap = record(target.bootstrap, `${path}.targets.${targetId}.bootstrap`);
    const bootstrapMode = choice(bootstrap.mode, `${path}.targets.${targetId}.bootstrap.mode`, ["windowsSsh"] as const);
    const guest = string(bootstrap.guest, `${path}.targets.${targetId}.bootstrap.guest`);
    if (!/^[A-Za-z0-9_.-]+@[A-Za-z0-9_.:-]+$/u.test(guest)) throw new Error(`${path}.targets.${targetId}.bootstrap.guest is invalid`);
    targets[targetId] = {
      bootstrap: {
        mode: bootstrapMode,
        route: string(bootstrap.route, `${path}.targets.${targetId}.bootstrap.route`),
        guest,
        identityFile: string(bootstrap.identityFile, `${path}.targets.${targetId}.bootstrap.identityFile`),
        stagingDirectory: string(bootstrap.stagingDirectory, `${path}.targets.${targetId}.bootstrap.stagingDirectory`),
      },
      providerId,
      server: defaultServer,
      user: string(target.user, `${path}.targets.${targetId}.user`),
      home: absolutePosixPath(target.home, `${path}.targets.${targetId}.home`),
      systemdUnit,
      network: {
        bypassKubernetesServiceNat: network.bypassKubernetesServiceNat,
        systemdUnit: networkSystemdUnit,
      },
      logsTailLines: integer(target.logsTailLines, `${path}.targets.${targetId}.logsTailLines`),
      credential: {
        sourceRef: {
          path: absolutePosixPath(sourceRef.path, `${path}.targets.${targetId}.credential.sourceRef.path`),
          key: string(sourceRef.key, `${path}.targets.${targetId}.credential.sourceRef.key`),
        },
        targetFile: string(credential.targetFile, `${path}.targets.${targetId}.credential.targetFile`),
      },
      benchmark: {
        concurrency: integer(benchmark.concurrency, `${path}.targets.${targetId}.benchmark.concurrency`),
        poolProbeConcurrency: integer(benchmark.poolProbeConcurrency, `${path}.targets.${targetId}.benchmark.poolProbeConcurrency`),
        commandRounds: integer(benchmark.commandRounds, `${path}.targets.${targetId}.benchmark.commandRounds`),
        transferBytes: integer(benchmark.transferBytes, `${path}.targets.${targetId}.benchmark.transferBytes`),
        transferRounds: integer(benchmark.transferRounds, `${path}.targets.${targetId}.benchmark.transferRounds`),
        transferTimeoutSeconds: integer(benchmark.transferTimeoutSeconds, `${path}.targets.${targetId}.benchmark.transferTimeoutSeconds`),
        readyTimeoutSeconds: integer(benchmark.readyTimeoutSeconds, `${path}.targets.${targetId}.benchmark.readyTimeoutSeconds`),
      },
    };
  }
  if (Object.keys(targets).length === 0) throw new Error(`${path}.targets must not be empty`);
  return {
    minimumPythonVersion: string(raw.minimumPythonVersion, `${path}.minimumPythonVersion`),
    privateRuntimeDirectory: relativePath(raw.privateRuntimeDirectory, `${path}.privateRuntimeDirectory`),
    systemdRestartSeconds: integer(raw.systemdRestartSeconds, `${path}.systemdRestartSeconds`),
    pipSources: pipSources.map((source, index) => {
      const item = record(source, `${path}.pipSources[${index}]`);
      return {
        id: string(item.id, `${path}.pipSources[${index}].id`),
        indexUrl: url(item.indexUrl, `${path}.pipSources[${index}].indexUrl`, ["https:"]),
      };
    }),
    targets,
  };
}

function publicExposure(value: unknown, path: string): PublicExposure {
  const raw = record(value, path);
  if (typeof raw.enabled !== "boolean") throw new Error(`${path}.enabled must be boolean`);
  const result = {
    enabled: raw.enabled,
    publicBaseUrl: string(raw.publicBaseUrl, `${path}.publicBaseUrl`),
    hostname: string(raw.hostname, `${path}.hostname`),
    expectedA: string(raw.expectedA, `${path}.expectedA`),
    upstream: string(raw.upstream, `${path}.upstream`),
    healthPath: httpPath(raw.healthPath, `${path}.healthPath`),
  };
  const origin = new URL(result.publicBaseUrl);
  if (origin.protocol !== "https:" || origin.hostname !== result.hostname || origin.pathname !== "/") {
    throw new Error(`${path}.publicBaseUrl must be the declared hostname HTTPS origin`);
  }
  return result;
}

export function readProviderAppsConfig(repositoryRoot: string): ProviderAppsConfig {
  const configPath = resolve(repositoryRoot, "config/platform-infra/apps.yaml");
  if (!existsSync(configPath)) throw new Error(`Provider Apps owning YAML not found: ${configPath}`);
  const root = record(Bun.YAML.parse(readFileSync(configPath, "utf8")), "provider-apps");
  if (root.version !== 1 || root.kind !== "platform-infra-provider-apps") {
    throw new Error("provider-apps YAML version/kind mismatch");
  }
  const target = record(root.target, "target");
  const runtime = record(root.runtime, "runtime");
  const release = record(root.release, "release");
  const routes = record(release.routes, "release.routes");
  const install = record(root.install, "install");
  const installRoutes = record(install.routes, "install.routes");
  const installDefaults = record(install.defaults, "install.defaults");
  const installRuntime = record(install.runtime, "install.runtime");
  const defaultServer = url(installDefaults.server, "install.defaults.server", ["ws:", "wss:"]);
  if (typeof installDefaults.autoStart !== "boolean") throw new Error("install.defaults.autoStart must be boolean");
  const result: ProviderAppsConfig = {
    configPath,
    target: {
      id: string(target.id, "target.id"),
      route: string(target.route, "target.route"),
      workspace: string(target.workspace, "target.workspace"),
    },
    runtime: {
      bindHost: string(runtime.bindHost, "runtime.bindHost"),
      probeHost: string(runtime.probeHost, "runtime.probeHost"),
      port: integer(runtime.port, "runtime.port"),
      stateDir: relativePath(runtime.stateDir, "runtime.stateDir"),
      pidPath: relativePath(runtime.pidPath, "runtime.pidPath"),
      logPath: relativePath(runtime.logPath, "runtime.logPath"),
      maxLogTailBytes: integer(runtime.maxLogTailBytes, "runtime.maxLogTailBytes"),
    },
    release: {
      root: relativePath(release.root, "release.root"),
      manifestPath: relativePath(release.manifestPath, "release.manifestPath"),
      providerPath: relativePath(release.providerPath, "release.providerPath"),
      routes: {
        manifest: httpPath(routes.manifest, "release.routes.manifest"),
        provider: httpPath(routes.provider, "release.routes.provider"),
        health: httpPath(routes.health, "release.routes.health"),
      },
    },
    install: {
      root: relativePath(install.root, "install.root"),
      indexPath: relativePath(install.indexPath, "install.indexPath"),
      stylePath: relativePath(install.stylePath, "install.stylePath"),
      scriptPath: relativePath(install.scriptPath, "install.scriptPath"),
      installerPath: relativePath(install.installerPath, "install.installerPath"),
      startCmdPath: relativePath(install.startCmdPath, "install.startCmdPath"),
      bootstrapPath: relativePath(install.bootstrapPath, "install.bootstrapPath"),
      linuxInstallerPath: relativePath(install.linuxInstallerPath, "install.linuxInstallerPath"),
      faviconPath: relativePath(install.faviconPath, "install.faviconPath"),
      panelImagePath: relativePath(install.panelImagePath, "install.panelImagePath"),
      routes: {
        index: httpPath(installRoutes.index, "install.routes.index"),
        style: httpPath(installRoutes.style, "install.routes.style"),
        script: httpPath(installRoutes.script, "install.routes.script"),
        installer: httpPath(installRoutes.installer, "install.routes.installer"),
        startCmd: httpPath(installRoutes.startCmd, "install.routes.startCmd"),
        bootstrap: httpPath(installRoutes.bootstrap, "install.routes.bootstrap"),
        linuxInstaller: httpPath(installRoutes.linuxInstaller, "install.routes.linuxInstaller"),
        contract: httpPath(installRoutes.contract, "install.routes.contract"),
        favicon: httpPath(installRoutes.favicon, "install.routes.favicon"),
        panelImage: httpPath(installRoutes.panelImage, "install.routes.panelImage"),
      },
      defaults: {
        server: defaultServer,
        credentialFile: string(installDefaults.credentialFile, "install.defaults.credentialFile"),
        autoStart: installDefaults.autoStart,
        pythonRuntime: choice(
          installDefaults.pythonRuntime,
          "install.defaults.pythonRuntime",
          ["auto", "system", "private"] as const,
        ),
      },
      runtime: {
        root: relativePath(installRuntime.root, "install.runtime.root"),
        connectTimeoutSeconds: integer(
          installRuntime.connectTimeoutSeconds,
          "install.runtime.connectTimeoutSeconds",
        ),
        progressIntervalSeconds: integer(
          installRuntime.progressIntervalSeconds,
          "install.runtime.progressIntervalSeconds",
        ),
        windows7: pythonRuntime(installRuntime.windows7, "install.runtime.windows7"),
        windowsModern: pythonRuntime(installRuntime.windowsModern, "install.runtime.windowsModern"),
      },
      linux: linuxInstall(install.linux, "install.linux", defaultServer),
    },
    publicExposure: publicExposure(root.publicExposure, "publicExposure"),
  };
  if (result.publicExposure.healthPath !== result.release.routes.health) {
    throw new Error("publicExposure.healthPath must match release.routes.health");
  }
  const server = new URL(result.install.defaults.server);
  if (!["ws:", "wss:"].includes(server.protocol)) throw new Error("install.defaults.server must use ws or wss");
  return result;
}
