import { mkdir, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { AppConfigService } from "../../apps/desktop/src/main/services/app-config.js";
import {
  refreshProjectContextAfterExecution,
  shouldRefreshProjectContextAfterExecution
} from "../../apps/desktop/src/main/services/project-context-lifecycle.js";
import { ProjectContextService } from "../../apps/desktop/src/main/services/project-context.js";
import { ProjectExecutionRouter } from "../../apps/desktop/src/main/services/project-execution-router.js";
import { ProjectStoreService } from "../../apps/desktop/src/main/services/project-store.js";
import type { SystemSummary } from "../../packages/shared-types/src/index.js";

function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

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

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", "default-chat-smoke", "context-result.json"));
  const tempRoot = path.dirname(resultPath);
  const userDataPath = path.join(tempRoot, "context-user-data");
  const readmeMarkerBefore = "README marker before default chat refresh smoke";
  const readmeMarkerAfter = "README marker after default chat refresh smoke";
  const promptBefore = "Repeat the README marker and confirm the current project root.";
  const promptAfter = "Repeat the updated README marker and confirm the current project root again.";

  await rm(tempRoot, { recursive: true, force: true });
  await mkdir(userDataPath, { recursive: true });

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

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

  const project = await projectStore.upsertProject({
    id: "default-chat-smoke",
    name: "Default Chat Smoke",
    description: "Verifies chat-fallback project context binding and post-execution refresh.",
    ready: true,
    boundSkillIds: []
  });
  await projectStore.setActiveProject(project.id);

  const projectRoot = await projectStore.getProjectRoot(project.id);
  const readmePath = path.join(projectRoot, "README.md");
  await writeFile(readmePath, `# Default Chat Smoke\n\n${readmeMarkerBefore}\n`, "utf8");

  const projectContextService = new ProjectContextService(projectStore);
  const systemSummary: SystemSummary = {
    appName: "千匠问天",
    appVersion: "0.1.0-smoke",
    isPackaged: false,
    platform: process.platform,
    arch: process.arch,
    appPath: path.join(repoRoot, "apps", "desktop"),
    resourcesPath: path.join(repoRoot, "apps", "desktop"),
    userDataPath,
    logsPath: path.join(tempRoot, "logs")
  };
  const projectExecutionRouter = new ProjectExecutionRouter(systemSummary);
  const session = await projectStore.createSession("Default Chat Smoke", project.id);

  async function prepare(prompt: string) {
    const sessionStateBefore = await projectStore.getSessionState(session.id);
    const snapshot = await projectContextService.getSnapshot(project.id);
    if (sessionStateBefore.contextSnapshotId !== snapshot.snapshotId) {
      await projectStore.bindSessionContextSnapshot(session.id, snapshot.snapshotId);
    }

    const decision = await projectExecutionRouter.decide({
      sessionId: session.id,
      projectId: project.id,
      projectRoot,
      userPrompt: prompt,
      context: snapshot,
      selectedSkillId: null
    });
    const sessionStateAfter = await projectStore.getSessionState(session.id);
    return {
      sessionStateBefore,
      sessionStateAfter,
      snapshot,
      decision
    };
  }

  const firstPreparation = await prepare(promptBefore);
  assert(firstPreparation.decision.kind === "chat-fallback", "Expected the first default chat preparation to route to chat-fallback.");
  assert(firstPreparation.sessionStateAfter.contextSnapshotId === firstPreparation.snapshot.snapshotId, "Initial default chat preparation did not bind session.contextSnapshotId.");
  assert(shouldRefreshProjectContextAfterExecution(firstPreparation.decision), "chat-fallback should schedule post-execution project context refresh.");
  assert(firstPreparation.decision.preparedPrompt.includes(readmeMarkerBefore), "Initial default chat prepared prompt did not include the original README marker.");
  assert(firstPreparation.decision.preparedPrompt.includes(`Current project: ${project.name} (${project.id})`), "Initial default chat prepared prompt did not include the project identity.");
  assert(firstPreparation.decision.preparedPrompt.includes(`Project root: ${projectRoot}`), "Initial default chat prepared prompt did not include the project root.");
  assert(firstPreparation.decision.preparedPrompt.includes("Keep project context isolated to this project and session."), "Initial default chat prepared prompt did not include the isolation instruction.");

  await delay(25);
  await writeFile(readmePath, `# Default Chat Smoke\n\n${readmeMarkerAfter}\n`, "utf8");

  const staleSessionState = await projectStore.getSessionState(session.id);
  assert(staleSessionState.contextSnapshotId === firstPreparation.snapshot.snapshotId, "Session snapshot changed before the post-execution refresh step ran.");

  await refreshProjectContextAfterExecution({
    sessionId: session.id,
    projectId: project.id,
    projectContextService,
    projectStore
  });

  const refreshedSnapshot = await projectContextService.getSnapshot(project.id);
  const reboundSessionState = await projectStore.getSessionState(session.id);
  assert(refreshedSnapshot.snapshotId !== firstPreparation.snapshot.snapshotId, "Post-execution default chat refresh did not produce a new snapshotId.");
  assert(refreshedSnapshot.readme?.includes(readmeMarkerAfter), "Post-execution default chat refresh did not include the updated README marker.");
  assert(reboundSessionState.contextSnapshotId === refreshedSnapshot.snapshotId, "Post-execution default chat refresh did not rebind session.contextSnapshotId.");

  const secondPreparation = await prepare(promptAfter);
  assert(secondPreparation.decision.kind === "chat-fallback", "Expected the second default chat preparation to route to chat-fallback.");
  assert(secondPreparation.snapshot.snapshotId === refreshedSnapshot.snapshotId, "Second default chat preparation did not reuse the refreshed snapshotId.");
  assert(secondPreparation.sessionStateAfter.contextSnapshotId === secondPreparation.snapshot.snapshotId, "Second default chat preparation did not preserve the rebound session.contextSnapshotId.");
  assert(secondPreparation.decision.preparedPrompt.includes(readmeMarkerAfter), "Second default chat prepared prompt did not include the updated README marker.");
  assert(secondPreparation.decision.preparedPrompt.includes(promptAfter), "Second default chat prepared prompt did not preserve the user prompt.");

  const cachedAfterRefresh = await projectContextService.getSnapshot(project.id);
  assert(cachedAfterRefresh.snapshotId === refreshedSnapshot.snapshotId, "Default chat snapshot cache was not stable after post-execution refresh.");

  const summary = {
    ok: true,
    userDataPath,
    projectId: project.id,
    sessionId: session.id,
    projectRoot,
    readmePath,
    initialSnapshotId: firstPreparation.snapshot.snapshotId,
    refreshedSnapshotId: refreshedSnapshot.snapshotId,
    finalSnapshotId: cachedAfterRefresh.snapshotId,
    initialSessionSnapshotId: firstPreparation.sessionStateAfter.contextSnapshotId,
    staleSessionSnapshotId: staleSessionState.contextSnapshotId,
    reboundSessionSnapshotId: reboundSessionState.contextSnapshotId,
    firstDecisionKind: firstPreparation.decision.kind,
    secondDecisionKind: secondPreparation.decision.kind,
    postExecutionRefreshEnabled: shouldRefreshProjectContextAfterExecution(firstPreparation.decision),
    firstPreparedPromptIncludesInitialReadme: firstPreparation.decision.preparedPrompt.includes(readmeMarkerBefore),
    secondPreparedPromptIncludesUpdatedReadme: secondPreparation.decision.preparedPrompt.includes(readmeMarkerAfter),
    sessionReboundAfterRefresh: reboundSessionState.contextSnapshotId === refreshedSnapshot.snapshotId,
    secondPreparationReusedRefreshedSnapshot: secondPreparation.snapshot.snapshotId === refreshedSnapshot.snapshotId,
    snapshotChangedAfterExecution: refreshedSnapshot.snapshotId !== firstPreparation.snapshot.snapshotId
  };

  await mkdir(path.dirname(resultPath), { recursive: true });
  await writeFile(resultPath, JSON.stringify(summary, null, 2), "utf8");
  console.log(JSON.stringify(summary, null, 2));
}

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", "default-chat-smoke", "context-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;
});
