import path from 'node:path';
import { developerToolsFromPolicy } from './capabilities.mjs';
import { buildSessionMemoryEntries } from './user-memory-profile.mjs';
import { buildSandboxSessionConstraints } from './user-publish.mjs';

function extensionName(config) {
  return config?.name ?? null;
}

export function allowedExtensionNames(extensionOverrides) {
  if (!extensionOverrides) return null;
  return new Set(extensionOverrides.map((item) => item.name).filter(Boolean));
}

export function extensionToolsKey(config) {
  const tools = config?.available_tools ?? config?.availableTools ?? [];
  return [...tools].sort().join('\0');
}

export function extensionConfigsMatch(sessionExt, desiredConfig) {
  if (extensionName(sessionExt) !== extensionName(desiredConfig)) return false;
  return extensionToolsKey(sessionExt) === extensionToolsKey(desiredConfig);
}

export function extensionsNeedingRefresh(currentExtensions, desiredExtensions) {
  const current = currentExtensions ?? [];
  const desired = desiredExtensions ?? [];
  const toRemove = [];
  const toAdd = [];

  for (const ext of current) {
    const name = extensionName(ext);
    if (!name) continue;
    const wanted = desired.find((item) => extensionName(item) === name);
    if (!wanted) {
      toRemove.push(name);
      continue;
    }
    if (!extensionConfigsMatch(ext, wanted)) {
      toRemove.push(name);
      toAdd.push(wanted);
    }
  }

  for (const config of desired) {
    const name = extensionName(config);
    if (!name) continue;
    const exists = current.some((ext) => extensionName(ext) === name);
    if (!exists) {
      toAdd.push(config);
    }
  }

  return { toRemove, toAdd };
}

function samePath(left, right) {
  if (!left || !right) return false;
  return path.resolve(left) === path.resolve(right);
}

async function readJson(upstream) {
  const text = await upstream.text();
  if (!upstream.ok) {
    throw new Error(text || `upstream ${upstream.status}`);
  }
  return text ? JSON.parse(text) : null;
}

function isInvalidDirectoryPathError(err) {
  return err instanceof Error && /Invalid directory path/i.test(err.message);
}

/**
 * Re-apply H5 session policy after resume so stale extension sets cannot bypass capability limits.
 */
export async function reconcileAgentSession(
  apiFetch,
  sessionId,
  {
    workingDir,
    sessionPolicy,
    sandboxConstraints = null,
    userContext = null,
    tolerateInvalidWorkingDir = false,
  },
) {
  if (sessionPolicy?.unrestricted) return;

  const session = await readJson(await apiFetch(`/sessions/${sessionId}`));
  let needsRestart = false;
  if (workingDir && !samePath(session?.working_dir, workingDir)) {
    try {
      await readJson(
        await apiFetch('/agent/update_working_dir', {
          method: 'POST',
          body: JSON.stringify({ session_id: sessionId, working_dir: workingDir }),
        }),
      );
    } catch (err) {
      if (!(tolerateInvalidWorkingDir && isInvalidDirectoryPathError(err))) {
        throw err;
      }
      console.warn(
        `[session-reconcile] skip invalid working_dir during resume for session ${sessionId}: ${
          err.message
        }`,
      );
    }
  }

  if (sessionPolicy?.gooseMode && session?.goose_mode !== sessionPolicy.gooseMode) {
    await readJson(
      await apiFetch('/agent/update_session', {
        method: 'POST',
        body: JSON.stringify({
          session_id: sessionId,
          goose_mode: sessionPolicy.gooseMode,
        }),
      }),
    );
    needsRestart = true;
  }

  const desired = sessionPolicy?.extensionOverrides ?? [];
  const allowed = allowedExtensionNames(desired);
  const currentPayload = await readJson(await apiFetch(`/sessions/${sessionId}/extensions`));
  const current = currentPayload?.extensions ?? [];
  let removedAny = false;

  for (const ext of current) {
    const name = extensionName(ext);
    if (!name || allowed.has(name)) continue;
    await readJson(
      await apiFetch('/agent/remove_extension', {
        method: 'POST',
        body: JSON.stringify({ session_id: sessionId, name }),
      }),
    );
    removedAny = true;
  }

  const refreshed = removedAny
    ? ((await readJson(await apiFetch(`/sessions/${sessionId}/extensions`)))?.extensions ?? [])
    : current;
  const { toRemove, toAdd } = extensionsNeedingRefresh(refreshed, desired);

  for (const name of toRemove) {
    await readJson(
      await apiFetch('/agent/remove_extension', {
        method: 'POST',
        body: JSON.stringify({ session_id: sessionId, name }),
      }),
    );
    needsRestart = true;
  }

  for (const config of toAdd) {
    await readJson(
      await apiFetch('/agent/add_extension', {
        method: 'POST',
        body: JSON.stringify({ session_id: sessionId, config }),
      }),
    );
    needsRestart = true;
  }

  if (needsRestart) {
    await readJson(
      await apiFetch('/agent/restart', {
        method: 'POST',
        body: JSON.stringify({ session_id: sessionId }),
      }),
    );
  }

  const sandboxText = sandboxConstraints?.trim()
    ? buildSandboxSessionConstraints({
        baseConstraints: sandboxConstraints,
        developerTools: developerToolsFromPolicy(sessionPolicy),
      })
    : null;

  const memoryEntries = buildSessionMemoryEntries({
    workingDir,
    sessionPolicy,
    sandboxConstraints: sandboxText,
    userContext,
  });

  if (memoryEntries.length > 0) {
    for (const entry of memoryEntries) {
      if (!entry.content?.trim()) continue;
      await apiFetch('/agent/harness_remember', {
        method: 'POST',
        body: JSON.stringify({
          sessionId,
          content: entry.content,
          title: entry.title,
        }),
      });
    }
    await apiFetch('/agent/harness_bootstrap', {
      method: 'POST',
      body: JSON.stringify({
        sessionId,
        force: true,
      }),
    });
  }
}
