import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
import http from "node:http";
import path from "node:path";
import { once } from "node:events";
import { fileURLToPath } from "node:url";
import { AppConfigService } from "../../apps/desktop/src/main/services/app-config.js";
import { ProjectBundleService } from "../../apps/desktop/src/main/services/project-bundle.js";
import { ProjectStoreService } from "../../apps/desktop/src/main/services/project-store.js";
import type { RemoteSkillAsset } from "../../apps/desktop/src/main/services/skill-store.js";

function assert(condition: unknown, message: string): asserts condition {
  if (!condition) {
    throw new Error(message);
  }
}

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

async function readJsonFile<T>(filePath: string): Promise<T | null> {
  try {
    const raw = await readFile(filePath, "utf8");
    return JSON.parse(raw) as T;
  } catch {
    return null;
  }
}

async function main(): Promise<void> {
  const __filename = fileURLToPath(import.meta.url);
  const __dirname = path.dirname(__filename);
  const repoRoot = path.resolve(__dirname, "..", "..");
  const resultPath = path.resolve(process.argv[2] ?? path.join(repoRoot, ".tmp", "project-bundle-reconcile-smoke", "result.json"));
  const bundleZipPath = path.resolve(process.argv[3] ?? path.join(path.dirname(resultPath), "bundle.zip"));
  const tempRoot = path.dirname(resultPath);
  const userDataPath = path.join(tempRoot, "user-data");
  const projectId = "bundle-reconcile-smoke";
  const skillEntryName = "bundle-reconcile-skill";
  const cronEntryName = "bundle-reconcile-task.txt";

  assert(await pathExists(bundleZipPath), `Bundle zip fixture was not found: ${bundleZipPath}`);
  await rm(userDataPath, { recursive: true, force: true });
  await mkdir(userDataPath, { recursive: true });

  const server = http.createServer(async (request, response) => {
    try {
      if (request.url !== "/bundle.zip") {
        response.statusCode = 404;
        response.end("not found");
        return;
      }
      const payload = await readFile(bundleZipPath);
      response.statusCode = 200;
      response.setHeader("Content-Type", "application/zip");
      response.end(payload);
    } catch (error) {
      response.statusCode = 500;
      response.end(error instanceof Error ? error.message : String(error));
    }
  });

  server.listen(0, "127.0.0.1");
  await once(server, "listening");
  const address = server.address();
  assert(address && typeof address === "object", "Failed to start local bundle smoke server.");
  const downloadUrl = `http://127.0.0.1:${address.port}/bundle.zip`;

  try {
    const configService = new AppConfigService(userDataPath);
    await configService.load();

    const projectStore = new ProjectStoreService(configService);
    await projectStore.initialize();
    const projectBundleService = new ProjectBundleService(configService, projectStore);

    const remoteAsset: RemoteSkillAsset = {
      bindingId: "bundle-reconcile-binding",
      skillId: "bundle-reconcile-skill-asset",
      name: "Bundle Reconcile Smoke Asset",
      description: "Remote bundle smoke asset for reconciliation.",
      category: "project",
      fileName: path.basename(bundleZipPath),
      downloadUrl
    };

    await projectBundleService.syncRemoteBundles([remoteAsset], "config-v1", "sync");

    const workspaceRoot = await projectStore.getWorkspaceRoot();
    const projectPath = path.join(workspaceRoot, "projects", projectId);
    const skillPath = path.join(workspaceRoot, "skills", skillEntryName);
    const cronPath = path.join(workspaceRoot, "cron", cronEntryName);
    const manifestPath = path.join(workspaceRoot, "manifests", "project-bundles.json");

    assert(await pathExists(projectPath), "Bundle project was not materialized before reconciliation cleanup.");
    assert(await pathExists(skillPath), "Bundle skill entry was not materialized before reconciliation cleanup.");
    assert(await pathExists(cronPath), "Bundle cron entry was not materialized before reconciliation cleanup.");

    const manifestBefore = (await readJsonFile<Record<string, {
      sharedSkillEntries?: string[];
      sharedCronEntries?: string[];
      projectId: string;
      sourceUrl: string;
    }>>(manifestPath)) ?? {};
    const manifestRecord = manifestBefore[projectId];
    assert(manifestRecord, "Bundle manifest record was not persisted.");
    assert(manifestRecord.projectId === projectId, "Bundle manifest record projectId did not match.");
    assert(manifestRecord.sourceUrl === downloadUrl, "Bundle manifest record sourceUrl did not match.");
    assert(manifestRecord.sharedSkillEntries?.includes(skillEntryName), "Bundle manifest record did not track shared skill ownership.");
    assert(manifestRecord.sharedCronEntries?.includes(cronEntryName), "Bundle manifest record did not track cron ownership.");

    const activeProjectAfterSync = await projectStore.getActiveProject();
    assert(activeProjectAfterSync.id === projectId, "Bundle project did not become the active project after sync.");

    await projectBundleService.syncRemoteBundles([], "config-v2", "sync");

    assert(!(await pathExists(projectPath)), "Stale bundle project directory was not removed during reconciliation.");
    assert(!(await pathExists(skillPath)), "Stale shared skill entry was not removed during reconciliation.");
    assert(!(await pathExists(cronPath)), "Stale cron entry was not removed during reconciliation.");

    const manifestAfter = (await readJsonFile<Record<string, unknown>>(manifestPath)) ?? {};
    assert(Object.keys(manifestAfter).length === 0, "Bundle manifest was not pruned after reconciliation cleanup.");

    const projectsAfterCleanup = await projectStore.listProjects();
    assert(!projectsAfterCleanup.some((project) => project.id === projectId), "Removed bundle project still exists in project list after cleanup.");
    assert(projectsAfterCleanup.length === 0, "Project list should be empty after removing the only bundle-backed project.");

    const activeProjectFailure = await projectStore.getActiveProject()
      .then(() => null)
      .catch((error) => error instanceof Error ? error.message : String(error));
    assert(activeProjectFailure === "No available projects.", "Active project lookup did not fail with the expected empty-inventory error.");

    const summary = {
      ok: true,
      userDataPath,
      workspaceRoot,
      bundleZipPath,
      downloadUrl,
      projectId,
      skillEntryName,
      cronEntryName,
      activeProjectAfterSync: activeProjectAfterSync.id,
      activeProjectAfterCleanup: null,
      activeProjectFailure,
      projectsAfterCleanup: projectsAfterCleanup.map((project) => project.id),
      manifestKeysAfterCleanup: Object.keys(manifestAfter)
    };

    await mkdir(path.dirname(resultPath), { recursive: true });
    await writeFile(resultPath, JSON.stringify(summary, null, 2), "utf8");
    console.log(JSON.stringify(summary, null, 2));
  } finally {
    await new Promise<void>((resolve) => {
      server.close(() => resolve());
    });
  }
}

main().catch(async (error) => {
  const __filename = fileURLToPath(import.meta.url);
  const __dirname = path.dirname(__filename);
  const repoRoot = path.resolve(__dirname, "..", "..");
  const resultPath = path.resolve(process.argv[2] ?? path.join(repoRoot, ".tmp", "project-bundle-reconcile-smoke", "result.json"));
  const failure = {
    ok: false,
    error: error instanceof Error ? error.stack ?? error.message : String(error)
  };

  await mkdir(path.dirname(resultPath), { recursive: true });
  await writeFile(resultPath, JSON.stringify(failure, null, 2), "utf8");
  console.error(failure.error);
  process.exitCode = 1;
});
