import { createHash } from "node:crypto";
import { cp, mkdir, readdir, stat } from "node:fs/promises";
import path from "node:path";
import type { AppConfigService } from "./app-config.js";
import type { StartupLogger } from "./startup-logger.js";

async function pathExists(targetPath: string): Promise<boolean> {
  try {
    await stat(targetPath);
    return true;
  } catch {
    return false;
  }
}

function buildApiKeyFingerprint(apiKey: string): string {
  return createHash("sha256").update(apiKey).digest("hex");
}

interface PackagedBootstrapServiceOptions {
  bootstrapRoot: string;
  startupLogger?: StartupLogger;
}

export class PackagedBootstrapService {
  private readonly configService: AppConfigService;
  private readonly bootstrapRoot: string;
  private readonly startupLogger?: StartupLogger;
  private readonly materializedFingerprints = new Set<string>();

  constructor(configService: AppConfigService, options: PackagedBootstrapServiceOptions) {
    this.configService = configService;
    this.bootstrapRoot = options.bootstrapRoot;
    this.startupLogger = options.startupLogger;
  }

  async materializeForApiKey(apiKey: string): Promise<boolean> {
    const normalizedApiKey = apiKey.trim();
    if (!normalizedApiKey) {
      return false;
    }

    const keyFingerprint = buildApiKeyFingerprint(normalizedApiKey);
    if (this.materializedFingerprints.has(keyFingerprint)) {
      return true;
    }

    const seedRoot = path.join(this.bootstrapRoot, "employee-key", keyFingerprint);
    if (!(await pathExists(seedRoot))) {
      return false;
    }

    const cacheSourcePath = path.join(seedRoot, "config", "runtime-cloud-cache.json");
    if (!(await pathExists(cacheSourcePath))) {
      return false;
    }

    const config = await this.configService.load();
    const workspaceRoot = config.workspacePath.trim() || this.configService.getDataPath("workspace");
    const cacheTargetPath = this.configService.getDataPath("config", "runtime-cloud-cache.json");

    await this.copyIfMissing(cacheSourcePath, cacheTargetPath);
    await this.copyWorkspaceSeedsIfMissing(path.join(seedRoot, "workspace"), workspaceRoot);

    this.materializedFingerprints.add(keyFingerprint);
    await this.startupLogger?.info("runtime-cloud", "bootstrap.materialized", "Packaged runtime cloud bootstrap was materialized.", {
      keyFingerprint: keyFingerprint.slice(0, 12),
      seedRoot,
      workspaceRoot
    });
    return true;
  }

  private async copyWorkspaceSeedsIfMissing(sourceRoot: string, targetRoot: string): Promise<void> {
    if (!(await pathExists(sourceRoot))) {
      return;
    }

    await this.copyChildrenIfMissing(path.join(sourceRoot, "projects"), path.join(targetRoot, "projects"));
    await this.copyChildrenIfMissing(path.join(sourceRoot, "skills"), path.join(targetRoot, "skills"));
    await this.copyChildrenIfMissing(path.join(sourceRoot, "cron"), path.join(targetRoot, "cron"));
    await this.copyIfMissing(
      path.join(sourceRoot, "manifests", "project-bundles.json"),
      path.join(targetRoot, "manifests", "project-bundles.json")
    );
    await this.copyIfMissing(
      path.join(sourceRoot, "manifests", "active-project.json"),
      path.join(targetRoot, "manifests", "active-project.json")
    );
  }

  private async copyIfMissing(sourcePath: string, targetPath: string): Promise<void> {
    if (!(await pathExists(sourcePath)) || (await pathExists(targetPath))) {
      return;
    }

    await mkdir(path.dirname(targetPath), { recursive: true });
    await cp(sourcePath, targetPath, {
      recursive: true,
      force: false,
      errorOnExist: false
    });
  }

  private async copyChildrenIfMissing(sourceDir: string, targetDir: string): Promise<void> {
    if (!(await pathExists(sourceDir))) {
      return;
    }

    await mkdir(targetDir, { recursive: true });
    const entries = await readdir(sourceDir, { withFileTypes: true });
    for (const entry of entries) {
      await this.copyIfMissing(path.join(sourceDir, entry.name), path.join(targetDir, entry.name));
    }
  }
}
