/**
 * OAuth session storage helpers.
 *
 * Centralizes all sessionStorage keys used during the OAuth flow so they
 * aren't duplicated as string literals across InternalMCPCatalog,
 * manage-users-dialog, and the oauth-callback page.
 *
 * Security note: Some values stored here (e.g. environment variables) may
 * contain sensitive data. sessionStorage is scoped to the browser tab and
 * cleared on tab close, and the OAuth redirect flow requires state to survive
 * a full-page navigation. All stored values are cleaned up promptly via
 * clearInstallContext() after the callback completes.
 */

// ─── Key constants ───────────────────────────────────────────────────
const OAUTH_STATE = "oauth_state";
const OAUTH_CATALOG_ID = "oauth_catalog_id";
const OAUTH_TEAM_ID = "oauth_team_id";
const OAUTH_SCOPE = "oauth_scope";
const OAUTH_IS_FIRST_INSTALLATION = "oauth_is_first_installation";
const OAUTH_MCP_SERVER_ID = "oauth_mcp_server_id";
const OAUTH_INSTALLATION_COMPLETE_CATALOG_ID =
  "oauth_installation_complete_catalog_id";
const OAUTH_SERVER_TYPE = "oauth_server_type";
const OAUTH_ENVIRONMENT_VALUES = "oauth_environment_values";
const OAUTH_USER_CONFIG_VALUES = "oauth_user_config_values";
const OAUTH_PENDING_AFTER_ENV_VARS = "oauth_pending_after_env_vars";
const OAUTH_RETURN_URL = "oauth_return_url";
const OAUTH_CHAT_RESUME = "oauth_chat_resume";

// Dynamic key prefix (combined with code + state to deduplicate callbacks)
const OAUTH_PROCESSING_PREFIX = "oauth_processing_";

// ─── Helpers ─────────────────────────────────────────────────────────

/** Store the OAuth PKCE state value so the callback can verify it. */
export function setOAuthState(state: string) {
  sessionStorage.setItem(OAUTH_STATE, state);
}

/** Store the catalog ID that initiated the OAuth flow. */
export function setOAuthCatalogId(catalogId: string) {
  sessionStorage.setItem(OAUTH_CATALOG_ID, catalogId);
}

/** Store the team ID to associate with the installed server after OAuth. */
export function setOAuthTeamId(teamId: string | null) {
  if (teamId) {
    sessionStorage.setItem(OAUTH_TEAM_ID, teamId);
  } else {
    sessionStorage.removeItem(OAUTH_TEAM_ID);
  }
}

export type OAuthScope = "personal" | "team" | "org";

/** Store the install scope selected before the OAuth redirect. */
export function setOAuthScope(scope: OAuthScope | null) {
  if (scope) {
    sessionStorage.setItem(OAUTH_SCOPE, scope);
  } else {
    sessionStorage.removeItem(OAUTH_SCOPE);
  }
}

export function getOAuthScope(): OAuthScope | null {
  const value = sessionStorage.getItem(OAUTH_SCOPE);
  if (value === "personal" || value === "team" || value === "org") {
    return value;
  }
  return null;
}

/** Mark whether this is the first installation (for auto-opening assignments dialog). */
export function setOAuthIsFirstInstallation(isFirst: boolean) {
  if (isFirst) {
    sessionStorage.setItem(OAUTH_IS_FIRST_INSTALLATION, "true");
  } else {
    sessionStorage.removeItem(OAUTH_IS_FIRST_INSTALLATION);
  }
}

/** Store the MCP server ID for re-authentication flows. */
export function setOAuthMcpServerId(serverId: string | null) {
  if (serverId) {
    sessionStorage.setItem(OAUTH_MCP_SERVER_ID, serverId);
  } else {
    sessionStorage.removeItem(OAUTH_MCP_SERVER_ID);
  }
}

/** Store a catalog ID so the catalog page opens the assignments dialog after redirect. */
export function setOAuthInstallationCompleteCatalogId(catalogId: string) {
  sessionStorage.setItem(OAUTH_INSTALLATION_COMPLETE_CATALOG_ID, catalogId);
}

/** Store the server type (e.g. "local") so the callback knows the install context. */
export function setOAuthServerType(serverType: string) {
  sessionStorage.setItem(OAUTH_SERVER_TYPE, serverType);
}

/** Store environment values collected before OAuth redirect (for local servers). */
export function setOAuthEnvironmentValues(values: Record<string, string>) {
  sessionStorage.setItem(OAUTH_ENVIRONMENT_VALUES, JSON.stringify(values));
}

type OAuthUserConfigField = {
  sensitive?: boolean;
};

/**
 * Store promptable user-config values collected before OAuth redirect.
 *
 * In non-BYOS mode we only persist non-sensitive values across the redirect.
 * Sensitive values must not be written into sessionStorage; they are handled
 * server-side or re-prompted after the callback. In BYOS mode, values are
 * vault references rather than raw secrets, so they are safe to persist.
 */
export function setOAuthUserConfigValues(params: {
  values: Record<string, string>;
  userConfig: Record<string, OAuthUserConfigField> | null | undefined;
  isByosVault?: boolean;
}) {
  if (!params.userConfig) {
    sessionStorage.removeItem(OAUTH_USER_CONFIG_VALUES);
    return;
  }

  const valuesToPersist = Object.fromEntries(
    Object.entries(params.values).filter(([fieldName]) => {
      const fieldConfig = params.userConfig?.[fieldName];
      if (!fieldConfig) {
        return false;
      }

      return params.isByosVault ? true : fieldConfig.sensitive !== true;
    }),
  );

  if (Object.keys(valuesToPersist).length === 0) {
    sessionStorage.removeItem(OAUTH_USER_CONFIG_VALUES);
    return;
  }

  sessionStorage.setItem(
    OAUTH_USER_CONFIG_VALUES,
    JSON.stringify(valuesToPersist),
  );
}

/** Flag that OAuth is pending after env vars collection. */
export function setOAuthPendingAfterEnvVars(pending: boolean) {
  if (pending) {
    sessionStorage.setItem(OAUTH_PENDING_AFTER_ENV_VARS, "true");
  } else {
    sessionStorage.removeItem(OAUTH_PENDING_AFTER_ENV_VARS);
  }
}

// ─── Processing guard (prevents duplicate callback processing) ───────

export function getProcessingKey(code: string, state: string) {
  return `${OAUTH_PROCESSING_PREFIX}${code}_${state}`;
}

export function isCallbackProcessed(code: string, state: string): boolean {
  return !!sessionStorage.getItem(getProcessingKey(code, state));
}

export function markCallbackProcessing(code: string, state: string) {
  sessionStorage.setItem(getProcessingKey(code, state), "true");
}

export function clearCallbackProcessing(code: string, state: string) {
  sessionStorage.removeItem(getProcessingKey(code, state));
}

// ─── Getters ─────────────────────────────────────────────────────────

export function getOAuthMcpServerId(): string | null {
  return sessionStorage.getItem(OAUTH_MCP_SERVER_ID);
}

export function getOAuthTeamId(): string | null {
  return sessionStorage.getItem(OAUTH_TEAM_ID);
}

export function getOAuthServerType(): string | null {
  return sessionStorage.getItem(OAUTH_SERVER_TYPE);
}

export function getOAuthEnvironmentValues(): Record<string, string> | null {
  const json = sessionStorage.getItem(OAUTH_ENVIRONMENT_VALUES);
  if (!json) return null;
  try {
    return JSON.parse(json) as Record<string, string>;
  } catch {
    return null;
  }
}

export function getOAuthUserConfigValues(): Record<string, string> | null {
  const json = sessionStorage.getItem(OAUTH_USER_CONFIG_VALUES);
  if (!json) return null;
  try {
    return JSON.parse(json) as Record<string, string>;
  } catch {
    return null;
  }
}

export function getOAuthIsFirstInstallation(): boolean {
  return sessionStorage.getItem(OAUTH_IS_FIRST_INSTALLATION) === "true";
}

export function getOAuthInstallationCompleteCatalogId(): string | null {
  return sessionStorage.getItem(OAUTH_INSTALLATION_COMPLETE_CATALOG_ID);
}

export function getOAuthPendingAfterEnvVars(): boolean {
  return sessionStorage.getItem(OAUTH_PENDING_AFTER_ENV_VARS) === "true";
}

/** Store the URL to return to after OAuth re-authentication (e.g. chat page). */
export function setOAuthReturnUrl(url: string) {
  sessionStorage.setItem(OAUTH_RETURN_URL, url);
}

export function getOAuthReturnUrl(): string | null {
  return sessionStorage.getItem(OAUTH_RETURN_URL);
}

export function clearOAuthReturnUrl() {
  sessionStorage.removeItem(OAUTH_RETURN_URL);
}

/** Queue a chat resume after re-authenticating an existing connection. */
export function setOAuthReauthChatResume(params: {
  returnUrl: string;
  serverName: string;
}): string | null {
  return writeChatResume(
    params.returnUrl,
    `I re-authenticated the "${params.serverName}" connection. Please retry the last failed tool call and continue from where we left off.`,
  );
}

/** Queue a chat resume after connecting a new integration mid-conversation. */
export function setOAuthInstallChatResume(params: {
  returnUrl: string;
  serverName: string;
}): string | null {
  return writeChatResume(
    params.returnUrl,
    `I connected the "${params.serverName}" integration. Please retry what I asked and continue from where we left off.`,
  );
}

export function getOAuthPendingChatResume(): {
  conversationId: string;
  message: string;
} | null {
  const json = sessionStorage.getItem(OAUTH_CHAT_RESUME);
  if (!json) {
    return null;
  }

  try {
    const parsed = JSON.parse(json) as {
      conversationId?: unknown;
      message?: unknown;
    };
    if (
      typeof parsed.conversationId !== "string" ||
      typeof parsed.message !== "string"
    ) {
      return null;
    }

    return {
      conversationId: parsed.conversationId,
      message: parsed.message,
    };
  } catch {
    return null;
  }
}

export function clearOAuthPendingChatResume() {
  sessionStorage.removeItem(OAUTH_CHAT_RESUME);
}

// ─── Cleanup ─────────────────────────────────────────────────────────

/** Remove re-authentication context. */
export function clearReauthContext() {
  sessionStorage.removeItem(OAUTH_MCP_SERVER_ID);
  sessionStorage.removeItem(OAUTH_RETURN_URL);
}

/** Remove all install-flow context stored before the OAuth redirect. */
export function clearInstallContext() {
  sessionStorage.removeItem(OAUTH_STATE);
  sessionStorage.removeItem(OAUTH_CATALOG_ID);
  sessionStorage.removeItem(OAUTH_TEAM_ID);
  sessionStorage.removeItem(OAUTH_SCOPE);
  sessionStorage.removeItem(OAUTH_IS_FIRST_INSTALLATION);
  sessionStorage.removeItem(OAUTH_SERVER_TYPE);
  sessionStorage.removeItem(OAUTH_ENVIRONMENT_VALUES);
  sessionStorage.removeItem(OAUTH_USER_CONFIG_VALUES);
}

/** Remove the assignments-dialog flag. */
export function clearInstallationCompleteCatalogId() {
  sessionStorage.removeItem(OAUTH_INSTALLATION_COMPLETE_CATALOG_ID);
}

/** Remove the "pending after env vars" flag. */
export function clearPendingAfterEnvVars() {
  sessionStorage.removeItem(OAUTH_PENDING_AFTER_ENV_VARS);
}

/**
 * Persist a message to auto-send once the user lands back on a chat
 * conversation after an OAuth redirect. Only applies when `returnUrl` points at
 * a chat conversation (`/chat/:id`); returns the conversation id when a resume
 * was stored, or null otherwise (e.g. flows started from the MCP registry).
 */
function writeChatResume(returnUrl: string, message: string): string | null {
  const conversationId = extractConversationIdFromChatUrl(returnUrl);
  if (!conversationId) {
    return null;
  }

  sessionStorage.setItem(
    OAUTH_CHAT_RESUME,
    JSON.stringify({ conversationId, message }),
  );
  return conversationId;
}

function extractConversationIdFromChatUrl(url: string): string | null {
  try {
    const parsedUrl = new URL(url, window.location.origin);
    const match = parsedUrl.pathname.match(/^\/chat\/([^/]+)$/);
    return match?.[1] ?? null;
  } catch {
    return null;
  }
}
