import { Container } from '@cloudflare/containers';
import { DISABLE_SESSION_TOKEN } from '@repo/shared/internal';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { RuntimeIdentityInactiveError } from '../src/current-runtime-identity';
import {
  InvalidBackupConfigError,
  PortNotExposedError,
  ProcessNotFoundError
} from '../src/errors';
import { connect, Sandbox } from '../src/sandbox';

// Mock dependencies before imports
vi.mock('./interpreter', () => ({
  CodeInterpreter: vi.fn().mockImplementation(() => ({}))
}));

vi.mock('@cloudflare/containers', () => {
  const mockSwitchPort = vi.fn((request: Request, port: number) => {
    // Create a new request with the port in the URL path
    const url = new URL(request.url);
    url.pathname = `/proxy/${port}${url.pathname}`;
    return new Request(url, request);
  });

  const MockContainer = class Container {
    ctx: any;
    env: any;
    sleepAfter: string | number = '10m';
    constructor(ctx: any, env: any) {
      this.ctx = ctx;
      this.env = env;
    }
    async fetch(request: Request): Promise<Response> {
      // Mock implementation - will be spied on in tests
      const upgradeHeader = request.headers.get('Upgrade');
      if (upgradeHeader?.toLowerCase() === 'websocket') {
        return new Response('WebSocket Upgraded', {
          status: 200,
          headers: {
            'X-WebSocket-Upgraded': 'true',
            Upgrade: 'websocket',
            Connection: 'Upgrade'
          }
        });
      }
      return new Response('Mock Container fetch');
    }
    async containerFetch(request: Request, port: number): Promise<Response> {
      // Mock implementation for HTTP path
      return new Response('Mock Container HTTP fetch');
    }
    async startAndWaitForPorts(): Promise<void> {
      // No-op: real container startup is not needed in tests.
    }
    async destroy(): Promise<void> {
      // No-op: real container destroy is not needed in tests; individual
      // tests that want to simulate destroy behavior use vi.spyOn.
    }
    async stop(): Promise<void> {
      // No-op: real container stop is not needed in tests.
    }
    async getState() {
      // Mock implementation - return healthy state
      return { status: 'healthy' };
    }
    renewActivityTimeout() {
      // Mock implementation - reschedules activity timeout
    }
  };

  const MockContainerProxy = class ContainerProxy {
    ctx: any;
    env: any;
    constructor(ctx: any, env: any) {
      this.ctx = ctx;
      this.env = env;
    }
    async fetch(request: Request): Promise<Response> {
      return new Response('Mock ContainerProxy fetch');
    }
  };

  return {
    Container: MockContainer,
    ContainerProxy: MockContainerProxy,
    getContainer: vi.fn(),
    switchPort: mockSwitchPort
  };
});

interface MockStorage {
  get: ReturnType<typeof vi.fn>;
  put: ReturnType<typeof vi.fn>;
  delete: ReturnType<typeof vi.fn>;
  list: ReturnType<typeof vi.fn>;
  transaction: ReturnType<typeof vi.fn>;
}

interface MockCtx {
  storage: MockStorage;
  blockConcurrencyWhile: ReturnType<typeof vi.fn>;
  waitUntil: ReturnType<typeof vi.fn>;
  container: {
    running: boolean;
    getTcpPort?: ReturnType<typeof vi.fn>;
    start?: ReturnType<typeof vi.fn>;
  };
  id: {
    toString: () => string;
    equals: ReturnType<typeof vi.fn>;
    name: string;
  };
}

const PREVIEW_TEST_PORT = 8080;
const PREVIEW_TEST_TOKEN = 'token12345678901';
const PREVIEW_TEST_RUNTIME_ID = 'runtime-1';

function activePreviewStorageState({
  port = PREVIEW_TEST_PORT,
  token = PREVIEW_TEST_TOKEN,
  runtimeIdentityID = PREVIEW_TEST_RUNTIME_ID
}: {
  port?: number;
  token?: string;
  runtimeIdentityID?: string;
} = {}) {
  return {
    portTokens: {
      [port.toString()]: { token }
    },
    currentRuntimeIdentity: {
      id: runtimeIdentityID
    },
    activePreviewPorts: {
      [port.toString()]: {
        runtimeIdentityID,
        token
      }
    }
  };
}

function mockPreviewStorageGet(
  mockCtx: MockCtx,
  state: Partial<ReturnType<typeof activePreviewStorageState>>
): void {
  vi.mocked(mockCtx.storage.get).mockImplementation(
    async (key) => state[key as keyof typeof state] ?? null
  );
}

function createPreviewProxyRequest(path = '/api'): Request {
  return new Request(
    `https://8080-test-sandbox-token12345678901.example.com${path}`,
    {
      headers: {
        'x-sandbox-preview-proxy': '1',
        'x-sandbox-preview-port': '8080',
        'x-sandbox-preview-token': 'token12345678901',
        'x-sandbox-preview-sandbox-id': 'test-sandbox'
      }
    }
  );
}

function createPreviewWebSocketRequest(): Request {
  return new Request(
    'https://8080-test-sandbox-token12345678901.example.com/ws',
    {
      headers: {
        Upgrade: 'websocket',
        Connection: 'Upgrade',
        'Sec-WebSocket-Key': 'test-key-123',
        'Sec-WebSocket-Version': '13',
        'x-sandbox-preview-proxy': '1',
        'x-sandbox-preview-port': '8080',
        'x-sandbox-preview-token': 'token12345678901',
        'x-sandbox-preview-sandbox-id': 'test-sandbox'
      }
    }
  );
}

describe('Sandbox - Automatic Session Management', () => {
  let sandbox: Sandbox;
  let mockCtx: MockCtx;
  let mockEnv: Record<string, unknown>;

  beforeEach(async () => {
    vi.clearAllMocks();

    const storageState = new Map<string, unknown>();

    const storage = {
      get: vi.fn(async (key: string) => storageState.get(key) ?? null),
      put: vi.fn(async (key: string, value: unknown) => {
        storageState.set(key, value);
      }),
      delete: vi.fn(async (key: string) => {
        storageState.delete(key);
      }),
      list: vi.fn().mockResolvedValue(new Map()),
      transaction: vi.fn(async (callback) => callback(storage))
    };

    // Mock DurableObjectState
    mockCtx = {
      storage: storage as any,
      blockConcurrencyWhile: vi
        .fn()
        .mockImplementation(
          <T>(callback: () => Promise<T>): Promise<T> => callback()
        ),
      waitUntil: vi.fn(),
      container: { running: true, start: vi.fn() },
      id: {
        toString: () => 'test-sandbox-id',
        equals: vi.fn(),
        name: 'test-sandbox'
      } as any
    };

    mockEnv = {};

    // Create Sandbox instance - SandboxClient is created internally
    const stub = new Sandbox(
      mockCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
      mockEnv
    );

    // Wait for blockConcurrencyWhile to complete
    await vi.waitFor(() => {
      expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
    });
    // Await the restore callback so tests observe a fully rehydrated instance.
    await Promise.all(
      (mockCtx.blockConcurrencyWhile as any).mock.results.map(
        (r: { value: unknown }) => r.value
      )
    );

    sandbox = Object.assign(stub, {
      wsConnect: connect(stub)
    });

    // Now spy on the client methods that we need for testing
    vi.spyOn(sandbox.client.utils, 'createSession').mockResolvedValue({
      success: true,
      id: 'sandbox-default',
      message: 'Created'
    } as any);

    vi.spyOn(sandbox.client.commands, 'execute').mockResolvedValue({
      success: true,
      stdout: '',
      stderr: '',
      exitCode: 0,
      command: '',
      timestamp: new Date().toISOString()
    } as any);

    vi.spyOn(sandbox.client.files, 'writeFile').mockResolvedValue({
      success: true,
      path: '/test.txt',
      timestamp: new Date().toISOString()
    } as any);

    vi.spyOn(sandbox.client.watch, 'checkChanges').mockResolvedValue({
      success: true,
      status: 'unchanged',
      version: 'watch-1:0',
      timestamp: new Date().toISOString()
    } as any);
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('default session management', () => {
    it('should create default session on first operation', async () => {
      vi.mocked(sandbox.client.commands.execute).mockResolvedValueOnce({
        success: true,
        stdout: 'test output',
        stderr: '',
        exitCode: 0,
        command: 'echo test',
        timestamp: new Date().toISOString()
      } as any);

      await sandbox.exec('echo test');

      expect(sandbox.client.utils.createSession).toHaveBeenCalledTimes(1);
      expect(sandbox.client.utils.createSession).toHaveBeenCalledWith(
        expect.objectContaining({
          id: expect.stringMatching(/^sandbox-/),
          cwd: '/workspace'
        })
      );

      expect(sandbox.client.commands.execute).toHaveBeenCalledWith(
        'echo test',
        expect.stringMatching(/^sandbox-/),
        undefined
      );
    });

    it('does not read enableDefaultSession from storage', async () => {
      const nullStorageCtx: MockCtx = {
        storage: {
          get: vi.fn().mockImplementation(async (key: string) => {
            return key === 'enableDefaultSession' ? null : undefined;
          }),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          ),
        waitUntil: vi.fn(),
        container: { running: true, start: vi.fn() },
        id: {
          toString: () => 'null-enable-default-session-sandbox',
          equals: vi.fn(),
          name: 'null-enable-default-session'
        } as any
      };

      const freshStub = new Sandbox(
        nullStorageCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        mockEnv
      );

      await vi.waitFor(() => {
        expect(nullStorageCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });
      await Promise.all(
        (nullStorageCtx.blockConcurrencyWhile as any).mock.results.map(
          (r: { value: unknown }) => r.value
        )
      );

      const freshSandbox = Object.assign(freshStub, {
        wsConnect: connect(freshStub)
      });

      vi.spyOn(freshSandbox.client.utils, 'createSession').mockResolvedValue({
        success: true,
        id: 'sandbox-default',
        message: 'Created'
      } as any);

      vi.spyOn(freshSandbox.client.commands, 'execute').mockResolvedValue({
        success: true,
        stdout: 'test output',
        stderr: '',
        exitCode: 0,
        command: 'echo test',
        timestamp: new Date().toISOString()
      } as any);

      await freshSandbox.exec('echo test');

      expect(nullStorageCtx.storage.get).not.toHaveBeenCalledWith(
        'enableDefaultSession'
      );
      expect(freshSandbox.client.utils.createSession).toHaveBeenCalledTimes(1);
      expect(freshSandbox.client.commands.execute).toHaveBeenCalledWith(
        'echo test',
        expect.stringMatching(/^sandbox-/),
        undefined
      );
    });

    it('should forward exec options to the command client', async () => {
      await sandbox.exec('echo $OPTION', {
        env: { OPTION: 'value' },
        cwd: '/workspace/project',
        timeout: 5000
      });

      expect(sandbox.client.commands.execute).toHaveBeenCalledWith(
        'echo $OPTION',
        expect.stringMatching(/^sandbox-/),
        {
          timeoutMs: 5000,
          env: { OPTION: 'value' },
          cwd: '/workspace/project'
        }
      );
    });

    it('should forward checkChanges options to the watch client', async () => {
      await sandbox.checkChanges('/workspace/test', {
        since: 'watch-1:0',
        recursive: false
      });

      expect(sandbox.client.watch.checkChanges).toHaveBeenCalledWith({
        path: '/workspace/test',
        recursive: false,
        include: undefined,
        exclude: undefined,
        since: 'watch-1:0',
        sessionId: expect.stringMatching(/^sandbox-/)
      });
    });

    it('should forward explicit sessionId for execStream and listFiles', async () => {
      vi.spyOn(sandbox.client.commands, 'executeStream').mockResolvedValue(
        new ReadableStream()
      );
      vi.spyOn(sandbox.client.files, 'listFiles').mockResolvedValue({
        success: true,
        path: '/workspace',
        files: [],
        count: 0,
        timestamp: new Date().toISOString()
      });

      await sandbox.execStream('echo streamed', {
        sessionId: 'explicit-session',
        cwd: '/workspace/project'
      });
      await sandbox.listFiles('/workspace', {
        recursive: true,
        sessionId: 'explicit-session'
      });

      expect(sandbox.client.commands.executeStream).toHaveBeenCalledWith(
        'echo streamed',
        'explicit-session',
        {
          cwd: '/workspace/project'
        }
      );
      expect(sandbox.client.files.listFiles).toHaveBeenCalledWith(
        '/workspace',
        'explicit-session',
        {
          recursive: true,
          sessionId: 'explicit-session'
        }
      );
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });

    it('should allow explicit session IDs on top-level methods', async () => {
      vi.spyOn(sandbox.client.commands, 'executeStream').mockResolvedValue(
        new ReadableStream()
      );
      vi.spyOn(sandbox.client.files, 'listFiles').mockResolvedValue({
        success: true,
        path: '/workspace',
        files: [],
        count: 0,
        timestamp: new Date().toISOString()
      });
      vi.mocked(sandbox.client.utils.createSession).mockClear();

      await sandbox.execStream('echo streamed', {
        sessionId: 'explicit-session',
        cwd: '/workspace/project'
      });
      await sandbox.listFiles('/workspace', {
        includeHidden: true,
        sessionId: 'explicit-session'
      });

      expect(sandbox.client.commands.executeStream).toHaveBeenCalledWith(
        'echo streamed',
        'explicit-session',
        {
          cwd: '/workspace/project'
        }
      );
      expect(sandbox.client.files.listFiles).toHaveBeenCalledWith(
        '/workspace',
        'explicit-session',
        {
          includeHidden: true,
          sessionId: 'explicit-session'
        }
      );

      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });

    it('should reject empty explicit session IDs', async () => {
      await expect(
        sandbox.execStream('echo bad', { sessionId: '   ' })
      ).rejects.toThrow('sessionId must not be empty or whitespace');

      await expect(
        sandbox.listFiles('/workspace', { sessionId: '' })
      ).rejects.toThrow('sessionId must not be empty or whitespace');

      await expect(sandbox.listProcesses('')).rejects.toThrow(
        'sessionId must not be empty or whitespace'
      );
    });

    it('should not expose the sessionless token on exec results', async () => {
      vi.mocked(sandbox.client.commands.execute).mockResolvedValueOnce({
        success: true,
        stdout: 'sessionless',
        stderr: '',
        exitCode: 0,
        command: 'printf sessionless',
        timestamp: new Date().toISOString()
      } as any);

      const result = await sandbox.execWithSessionToken(
        'printf sessionless',
        DISABLE_SESSION_TOKEN
      );

      expect(sandbox.client.commands.execute).toHaveBeenCalledWith(
        'printf sessionless',
        DISABLE_SESSION_TOKEN,
        undefined
      );
      expect(result.sessionId).toBeUndefined();
    });

    it('should not expose the sessionless token on streaming exec results', async () => {
      vi.spyOn(sandbox.client.commands, 'executeStream').mockResolvedValue(
        new ReadableStream({
          start(controller) {
            controller.enqueue(
              new TextEncoder().encode(
                'data: {"type":"complete","exitCode":0}\n\n'
              )
            );
            controller.close();
          }
        })
      );
      await sandbox.setEnvVars({ SANDBOX_LEVEL_ENV: 'from-sandbox' });

      const result = await sandbox.execWithSessionToken(
        'printf sessionless',
        DISABLE_SESSION_TOKEN,
        {
          stream: true,
          onOutput: vi.fn(),
          env: { CALL_LEVEL_ENV: 'from-call' }
        }
      );

      expect(sandbox.client.commands.executeStream).toHaveBeenCalledWith(
        'printf sessionless',
        DISABLE_SESSION_TOKEN,
        {
          env: {
            SANDBOX_LEVEL_ENV: 'from-sandbox',
            CALL_LEVEL_ENV: 'from-call'
          }
        }
      );
      expect(result.sessionId).toBeUndefined();
    });

    it('should reuse default session across multiple operations', async () => {
      await sandbox.exec('echo test1');
      await sandbox.writeFile('/test.txt', 'content');
      await sandbox.exec('echo test2');

      expect(sandbox.client.utils.createSession).toHaveBeenCalledTimes(1);

      const firstSessionId = vi.mocked(sandbox.client.commands.execute).mock
        .calls[0][1];
      const fileSessionId = vi.mocked(sandbox.client.files.writeFile).mock
        .calls[0][2];
      const secondSessionId = vi.mocked(sandbox.client.commands.execute).mock
        .calls[1][1];

      expect(firstSessionId).toBe(fileSessionId);
      expect(firstSessionId).toBe(secondSessionId);
    });

    it('should use default session for process management', async () => {
      vi.spyOn(sandbox.client.processes, 'startProcess').mockResolvedValue({
        success: true,
        processId: 'proc-1',
        pid: 1234,
        command: 'sleep 10',
        timestamp: new Date().toISOString()
      } as any);

      vi.spyOn(sandbox.client.processes, 'listProcesses').mockResolvedValue({
        success: true,
        processes: [
          {
            id: 'proc-1',
            pid: 1234,
            command: 'sleep 10',
            status: 'running',
            startTime: new Date().toISOString()
          }
        ],
        timestamp: new Date().toISOString()
      } as any);

      const process = await sandbox.startProcess('sleep 10');
      const processes = await sandbox.listProcesses();

      expect(sandbox.client.utils.createSession).toHaveBeenCalledTimes(1);

      // startProcess uses sessionId (to start process in that session)
      const startSessionId = vi.mocked(sandbox.client.processes.startProcess)
        .mock.calls[0][1];
      expect(startSessionId).toMatch(/^sandbox-/);

      // listProcesses is sandbox-scoped - no sessionId parameter
      const listProcessesCall = vi.mocked(
        sandbox.client.processes.listProcesses
      ).mock.calls[0];
      expect(listProcessesCall).toEqual([]);

      // Verify the started process appears in the list with session annotation
      expect(process.id).toBe('proc-1');
      expect(process.sessionId).toMatch(/^sandbox-/);
      expect(processes).toHaveLength(1);
      expect(processes[0].id).toBe('proc-1');
      expect(processes[0].sessionId).toMatch(/^sandbox-/);
      expect(process.sessionId).toBe(processes[0].sessionId);
    });

    it('should preserve the default session on process objects', async () => {
      vi.spyOn(sandbox.client.processes, 'startProcess').mockResolvedValue({
        success: true,
        processId: 'proc-none',
        pid: 4321,
        command: 'sleep 10',
        timestamp: new Date().toISOString()
      } as any);

      vi.spyOn(sandbox.client.processes, 'listProcesses').mockResolvedValue({
        success: true,
        processes: [
          {
            id: 'proc-none',
            pid: 4321,
            command: 'sleep 10',
            status: 'running',
            startTime: new Date().toISOString()
          }
        ],
        timestamp: new Date().toISOString()
      } as any);

      vi.spyOn(sandbox.client.processes, 'getProcess').mockResolvedValue({
        success: true,
        process: {
          id: 'proc-none',
          pid: 4321,
          command: 'sleep 10',
          status: 'running',
          startTime: new Date().toISOString()
        },
        timestamp: new Date().toISOString()
      } as any);

      const started = await sandbox.startProcess('sleep 10');
      const listed = await sandbox.listProcesses();
      const fetched = await sandbox.getProcess('proc-none');

      expect(
        vi.mocked(sandbox.client.processes.startProcess).mock.calls[0][1]
      ).toMatch(/^sandbox-/);
      expect(
        vi.mocked(sandbox.client.processes.listProcesses).mock.calls[0]
      ).toEqual([]);
      expect(
        vi.mocked(sandbox.client.processes.getProcess).mock.calls[0]
      ).toEqual(['proc-none']);
      expect(started.sessionId).toMatch(/^sandbox-/);
      expect(listed).toHaveLength(1);
      expect(listed[0].sessionId).toMatch(/^sandbox-/);
      expect(fetched?.sessionId).toMatch(/^sandbox-/);
      expect(started.sessionId).toBe(listed[0].sessionId);
      expect(started.sessionId).toBe(fetched?.sessionId);
      expect(sandbox.client.utils.createSession).toHaveBeenCalledOnce();
    });

    it('should not expose the sessionless token on process objects', async () => {
      vi.spyOn(sandbox.client.processes, 'startProcess').mockResolvedValue({
        success: true,
        processId: 'proc-sessionless',
        pid: 4321,
        command: 'sleep 10',
        timestamp: new Date().toISOString()
      } as any);
      vi.spyOn(sandbox.client.processes, 'listProcesses').mockResolvedValue({
        success: true,
        processes: [
          {
            id: 'proc-sessionless',
            pid: 4321,
            command: 'sleep 10',
            status: 'running',
            startTime: new Date().toISOString()
          }
        ],
        timestamp: new Date().toISOString()
      } as any);
      vi.spyOn(sandbox.client.processes, 'getProcess').mockResolvedValue({
        success: true,
        process: {
          id: 'proc-sessionless',
          pid: 4321,
          command: 'sleep 10',
          status: 'running',
          startTime: new Date().toISOString()
        },
        timestamp: new Date().toISOString()
      } as any);

      const process = await sandbox.startProcess(
        'sleep 10',
        undefined,
        DISABLE_SESSION_TOKEN
      );
      const processes = await sandbox.listProcesses(DISABLE_SESSION_TOKEN);
      const fetched = await sandbox.getProcess(
        'proc-sessionless',
        DISABLE_SESSION_TOKEN
      );

      expect(
        vi.mocked(sandbox.client.processes.startProcess).mock.calls[0][1]
      ).toBe(DISABLE_SESSION_TOKEN);
      expect(process.sessionId).toBeUndefined();
      expect(processes[0].sessionId).toBeUndefined();
      expect(fetched?.sessionId).toBeUndefined();
    });

    it('should use default session for git operations', async () => {
      vi.spyOn(sandbox.client.git, 'checkout').mockResolvedValue({
        success: true,
        stdout: 'Cloned successfully',
        stderr: '',
        branch: 'main',
        targetDir: '/workspace/repo',
        timestamp: new Date().toISOString()
      } as any);

      await sandbox.gitCheckout('https://github.com/test/repo.git', {
        branch: 'main',
        cloneTimeoutMs: 90_000
      });

      expect(sandbox.client.utils.createSession).toHaveBeenCalledTimes(1);
      expect(sandbox.client.git.checkout).toHaveBeenCalledWith(
        'https://github.com/test/repo.git',
        expect.stringMatching(/^sandbox-/),
        {
          branch: 'main',
          targetDir: undefined,
          depth: undefined,
          timeoutMs: 90_000
        }
      );
    });

    it('should initialize session with sandbox name when available', async () => {
      await sandbox.setSandboxName('my-sandbox');

      await sandbox.exec('pwd');

      expect(sandbox.client.utils.createSession).toHaveBeenCalledWith(
        expect.objectContaining({
          id: 'sandbox-my-sandbox',
          cwd: '/workspace'
        })
      );
    });

    it('coalesces concurrent callers onto one createSession RPC', async () => {
      let resolveCreate!: (value: unknown) => void;
      vi.mocked(sandbox.client.utils.createSession).mockReturnValueOnce(
        new Promise((resolve) => {
          resolveCreate = resolve;
        }) as any
      );

      const first = sandbox.exec('echo one');
      const second = sandbox.exec('echo two');

      resolveCreate({ success: true, id: 'sandbox-default', message: 'ok' });
      await Promise.all([first, second]);

      expect(sandbox.client.utils.createSession).toHaveBeenCalledTimes(1);
    });

    it('retries createSession after a failed initialization', async () => {
      vi.mocked(sandbox.client.utils.createSession)
        .mockRejectedValueOnce(new Error('boom'))
        .mockResolvedValueOnce({
          success: true,
          id: 'sandbox-default',
          message: 'ok'
        } as any);

      await expect(sandbox.exec('echo one')).rejects.toThrow('boom');
      await sandbox.exec('echo two');

      expect(sandbox.client.utils.createSession).toHaveBeenCalledTimes(2);
    });

    it('does not cache the session id in memory if persistence fails', async () => {
      vi.mocked(mockCtx.storage.put).mockImplementation(async (key) => {
        if (key === 'defaultSession') throw new Error('storage down');
      });

      await expect(sandbox.exec('echo one')).rejects.toThrow('storage down');

      vi.mocked(mockCtx.storage.put).mockResolvedValue(undefined);
      await sandbox.exec('echo two');

      expect(sandbox.client.utils.createSession).toHaveBeenCalledTimes(2);
    });

    it('does not share an in-flight init across different session ids', async () => {
      let resolveFirst!: (value: unknown) => void;
      let resolveSecond!: (value: unknown) => void;
      vi.mocked(sandbox.client.utils.createSession)
        .mockReturnValueOnce(
          new Promise((resolve) => {
            resolveFirst = resolve;
          }) as any
        )
        .mockReturnValueOnce(
          new Promise((resolve) => {
            resolveSecond = resolve;
          }) as any
        );

      const first = sandbox.exec('echo one');
      await sandbox.setSandboxName('renamed');
      const second = sandbox.exec('echo two');
      const third = sandbox.exec('echo three');

      resolveFirst({ success: true, id: 'sandbox-default', message: 'ok' });
      resolveSecond({ success: true, id: 'sandbox-renamed', message: 'ok' });
      await Promise.all([first, second, third]);

      expect(sandbox.client.utils.createSession).toHaveBeenCalledTimes(2);
      const calls = vi.mocked(sandbox.client.commands.execute).mock.calls;
      expect(calls[0][1]).toBe('sandbox-default');
      expect(calls[1][1]).toBe('sandbox-renamed');
      expect(calls[2][1]).toBe('sandbox-renamed');
    });

    it('invalidates an in-flight init when onStop runs mid-flight', async () => {
      let resolveCreate!: (value: unknown) => void;
      vi.mocked(sandbox.client.utils.createSession).mockReturnValueOnce(
        new Promise((resolve) => {
          resolveCreate = resolve;
        }) as any
      );

      const inflight = sandbox.exec('echo one');
      await (sandbox as any).onStop();

      resolveCreate({ success: true, id: 'sandbox-default', message: 'ok' });
      await expect(inflight).rejects.toThrow();

      const defaultSessionPuts = vi
        .mocked(mockCtx.storage.put)
        .mock.calls.filter((call) => call[0] === 'defaultSession');
      expect(defaultSessionPuts).toHaveLength(0);
    });

    it('does not join a stale init promise after onStop clears it', async () => {
      let resolveFirst!: (value: unknown) => void;
      vi.mocked(sandbox.client.utils.createSession)
        .mockReturnValueOnce(
          new Promise((resolve) => {
            resolveFirst = resolve;
          }) as any
        )
        .mockResolvedValueOnce({
          success: true,
          id: 'sandbox-default',
          message: 'ok'
        } as any);

      const first = sandbox.exec('echo one');
      await (sandbox as any).onStop();
      const second = sandbox.exec('echo two');

      resolveFirst({ success: true, id: 'sandbox-default', message: 'ok' });
      await expect(first).rejects.toThrow();
      await second;

      expect(sandbox.client.utils.createSession).toHaveBeenCalledTimes(2);
    });

    it('keeps default shell state independent of sessionless proxy configuration', async () => {
      vi.spyOn(sandbox.client.utils, 'deleteSession').mockResolvedValue({
        success: true,
        sessionId: 'sandbox-default',
        timestamp: new Date().toISOString()
      } as never);

      (sandbox as unknown as { defaultSession: string }).defaultSession =
        'sandbox-default';

      vi.mocked(sandbox.client.commands.execute).mockResolvedValueOnce({
        success: true,
        stdout: 'sessionless',
        stderr: '',
        exitCode: 0,
        command: 'printf sessionless',
        timestamp: new Date().toISOString()
      } as never);

      const result = await sandbox.exec('printf sessionless');

      expect(result.stdout).toBe('sessionless');
      expect(sandbox.client.utils.deleteSession).not.toHaveBeenCalled();
      expect(mockCtx.storage.delete).not.toHaveBeenCalledWith('defaultSession');
      expect(mockCtx.storage.put).not.toHaveBeenCalledWith(
        'enableDefaultSession',
        false
      );
    });
  });

  describe('getProcess', () => {
    it('returns null when the process does not exist', async () => {
      vi.spyOn(sandbox.client.processes, 'getProcess').mockRejectedValue(
        new ProcessNotFoundError({
          error: 'Process nonexistent-process-id-12345 not found',
          code: 'PROCESS_NOT_FOUND',
          details: { processId: 'nonexistent-process-id-12345' }
        } as any)
      );

      const process = await sandbox.getProcess('nonexistent-process-id-12345');

      expect(process).toBeNull();
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });
  });

  describe('explicit session creation', () => {
    it('should reject the internal sentinel as a session ID', async () => {
      await expect(
        sandbox.createSession({ id: DISABLE_SESSION_TOKEN })
      ).rejects.toThrow('reserved for internal use');
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });

    it('should create isolated execution session', async () => {
      vi.mocked(sandbox.client.utils.createSession).mockResolvedValueOnce({
        success: true,
        id: 'custom-session-123',
        message: 'Created'
      } as any);

      const session = await sandbox.createSession({
        id: 'custom-session-123',
        env: { NODE_ENV: 'test' },
        cwd: '/test'
      });

      expect(sandbox.client.utils.createSession).toHaveBeenCalledWith({
        id: 'custom-session-123',
        env: { NODE_ENV: 'test' },
        cwd: '/test'
      });

      expect(session.id).toBe('custom-session-123');
      expect(session.exec).toBeInstanceOf(Function);
      expect(session.startProcess).toBeInstanceOf(Function);
      expect(session.writeFile).toBeInstanceOf(Function);
      expect(session.gitCheckout).toBeInstanceOf(Function);
    });

    it('should execute operations in specific session context', async () => {
      vi.mocked(sandbox.client.utils.createSession).mockResolvedValueOnce({
        success: true,
        id: 'isolated-session',
        message: 'Created'
      } as any);

      const session = await sandbox.createSession({ id: 'isolated-session' });

      await session.exec('echo test');

      expect(sandbox.client.commands.execute).toHaveBeenCalledWith(
        'echo test',
        'isolated-session',
        undefined
      );
    });

    it('should isolate multiple explicit sessions', async () => {
      vi.mocked(sandbox.client.utils.createSession)
        .mockResolvedValueOnce({
          success: true,
          id: 'session-1',
          message: 'Created'
        } as any)
        .mockResolvedValueOnce({
          success: true,
          id: 'session-2',
          message: 'Created'
        } as any);

      const session1 = await sandbox.createSession({ id: 'session-1' });
      const session2 = await sandbox.createSession({ id: 'session-2' });

      await session1.exec('echo build');
      await session2.exec('echo test');

      const session1Id = vi.mocked(sandbox.client.commands.execute).mock
        .calls[0][1];
      const session2Id = vi.mocked(sandbox.client.commands.execute).mock
        .calls[1][1];

      expect(session1Id).toBe('session-1');
      expect(session2Id).toBe('session-2');
      expect(session1Id).not.toBe(session2Id);
    });

    it('should not interfere with default session', async () => {
      vi.mocked(sandbox.client.utils.createSession)
        .mockResolvedValueOnce({
          success: true,
          id: 'sandbox-default',
          message: 'Created'
        } as any)
        .mockResolvedValueOnce({
          success: true,
          id: 'explicit-session',
          message: 'Created'
        } as any);

      await sandbox.exec('echo default');

      const explicitSession = await sandbox.createSession({
        id: 'explicit-session'
      });
      await explicitSession.exec('echo explicit');

      await sandbox.exec('echo default-again');

      const defaultSessionId1 = vi.mocked(sandbox.client.commands.execute).mock
        .calls[0][1];
      const explicitSessionId = vi.mocked(sandbox.client.commands.execute).mock
        .calls[1][1];
      const defaultSessionId2 = vi.mocked(sandbox.client.commands.execute).mock
        .calls[2][1];

      expect(defaultSessionId1).toBe('sandbox-default');
      expect(explicitSessionId).toBe('explicit-session');
      expect(defaultSessionId2).toBe('sandbox-default');
      expect(defaultSessionId1).toBe(defaultSessionId2);
      expect(explicitSessionId).not.toBe(defaultSessionId1);
    });

    it('should generate session ID if not provided', async () => {
      vi.mocked(sandbox.client.utils.createSession).mockResolvedValueOnce({
        success: true,
        id: 'session-generated-123',
        message: 'Created'
      } as any);

      await sandbox.createSession();

      expect(sandbox.client.utils.createSession).toHaveBeenCalledWith(
        expect.objectContaining({
          id: expect.stringMatching(/^session-/)
        })
      );
    });
  });

  describe('placement id capture', () => {
    it('should store containerPlacementId from session-create response', async () => {
      vi.mocked(sandbox.client.utils.createSession).mockResolvedValueOnce({
        success: true,
        id: 'sandbox-default',
        message: 'Created',
        containerPlacementId: 'placement-abc-123'
      } as any);

      await sandbox.exec('echo hi');

      expect(mockCtx.storage.put).toHaveBeenCalledWith(
        'containerPlacementId',
        'placement-abc-123'
      );
    });

    it('should store null when container reports containerPlacementId as null', async () => {
      vi.mocked(sandbox.client.utils.createSession).mockResolvedValueOnce({
        success: true,
        id: 'sandbox-default',
        message: 'Created',
        containerPlacementId: null
      } as any);

      await sandbox.exec('echo hi');

      expect(mockCtx.storage.put).toHaveBeenCalledWith(
        'containerPlacementId',
        null
      );
    });

    it('should not touch containerPlacementId storage when response omits the field', async () => {
      vi.mocked(sandbox.client.utils.createSession).mockResolvedValueOnce({
        success: true,
        id: 'sandbox-default',
        message: 'Created'
      } as any);

      await sandbox.exec('echo hi');

      const placementCalls = mockCtx.storage.put.mock.calls.filter(
        (call: unknown[]) => call[0] === 'containerPlacementId'
      );
      expect(placementCalls).toHaveLength(0);
    });

    it('getContainerPlacementId returns stored value', async () => {
      mockCtx.storage.get.mockImplementation(async (key: string) => {
        if (key === 'containerPlacementId') return 'placement-stored-xyz';
        return null;
      });

      await expect(sandbox.getContainerPlacementId()).resolves.toBe(
        'placement-stored-xyz'
      );
    });

    it('getContainerPlacementId returns undefined when no handshake has occurred', async () => {
      mockCtx.storage.get.mockResolvedValue(undefined);

      await expect(sandbox.getContainerPlacementId()).resolves.toBeUndefined();
    });
  });

  describe('ExecutionSession operations', () => {
    let session: any;

    beforeEach(async () => {
      vi.mocked(sandbox.client.utils.createSession).mockResolvedValueOnce({
        success: true,
        id: 'test-session',
        message: 'Created'
      } as any);

      session = await sandbox.createSession({ id: 'test-session' });
    });

    it('should execute command with session context', async () => {
      await session.exec('pwd');
      expect(sandbox.client.commands.execute).toHaveBeenCalledWith(
        'pwd',
        'test-session',
        undefined
      );
    });

    it('should start process with session context', async () => {
      vi.spyOn(sandbox.client.processes, 'startProcess').mockResolvedValue({
        success: true,
        process: {
          id: 'proc-1',
          pid: 1234,
          command: 'sleep 10',
          status: 'running',
          startTime: new Date().toISOString()
        }
      } as any);

      await session.startProcess('sleep 10');

      expect(sandbox.client.processes.startProcess).toHaveBeenCalledWith(
        'sleep 10',
        'test-session',
        {}
      );
    });

    it('should write file with session context', async () => {
      vi.spyOn(sandbox.client.files, 'writeFile').mockResolvedValue({
        success: true,
        path: '/test.txt',
        timestamp: new Date().toISOString()
      } as any);

      await session.writeFile('/test.txt', 'content');

      expect(sandbox.client.files.writeFile).toHaveBeenCalledWith(
        '/test.txt',
        'content',
        'test-session',
        { encoding: undefined }
      );
    });

    it('should perform git checkout with session context', async () => {
      vi.spyOn(sandbox.client.git, 'checkout').mockResolvedValue({
        success: true,
        stdout: 'Cloned',
        stderr: '',
        branch: 'main',
        targetDir: '/workspace/repo',
        timestamp: new Date().toISOString()
      } as any);

      await session.gitCheckout('https://github.com/test/repo.git', {
        depth: 1,
        cloneTimeoutMs: 90_000
      });

      expect(sandbox.client.git.checkout).toHaveBeenCalledWith(
        'https://github.com/test/repo.git',
        'test-session',
        {
          branch: undefined,
          targetDir: undefined,
          depth: 1,
          timeoutMs: 90_000
        }
      );
    });
  });

  describe('edge cases and error handling', () => {
    it('should handle session creation errors gracefully', async () => {
      vi.mocked(sandbox.client.utils.createSession).mockRejectedValueOnce(
        new Error('Session creation failed')
      );

      await expect(sandbox.exec('echo test')).rejects.toThrow(
        'Session creation failed'
      );
    });

    it('should initialize with empty environment when not set', async () => {
      await sandbox.exec('pwd');

      expect(sandbox.client.utils.createSession).toHaveBeenCalledWith(
        expect.objectContaining({
          id: expect.any(String),
          cwd: '/workspace'
        })
      );
    });

    it('should use updated environment after setEnvVars', async () => {
      await sandbox.setEnvVars({ NODE_ENV: 'production', DEBUG: 'true' });

      await sandbox.exec('env');

      expect(sandbox.client.utils.createSession).toHaveBeenCalledWith({
        id: expect.any(String),
        env: { NODE_ENV: 'production', DEBUG: 'true' },
        cwd: '/workspace'
      });
    });
  });

  describe('port exposure - workers.dev detection', () => {
    beforeEach(async () => {
      await sandbox.setSandboxName('test-sandbox');
    });

    it('should reject workers.dev domains with CustomDomainRequiredError', async () => {
      const hostnames = [
        'my-worker.workers.dev',
        'my-worker.my-account.workers.dev'
      ];

      for (const hostname of hostnames) {
        try {
          await sandbox.exposePort(8080, { name: 'test', hostname });
          // Should not reach here
          expect.fail('Should have thrown CustomDomainRequiredError');
        } catch (error: any) {
          expect(error.name).toBe('CustomDomainRequiredError');
          expect(error.code).toBe('CUSTOM_DOMAIN_REQUIRED');
          expect(error.message).toContain('workers.dev');
          expect(error.message).toContain('custom domain');
        }
      }
    });

    it('should accept custom domains and subdomains', async () => {
      const testCases = [
        { hostname: 'example.com', description: 'apex domain' },
        { hostname: 'sandbox.example.com', description: 'subdomain' }
      ];

      for (const { hostname } of testCases) {
        const result = await sandbox.exposePort(8080, {
          name: 'test',
          hostname
        });
        expect(result.url).toContain(hostname);
        expect(result.port).toBe(8080);
      }
    });

    it('should accept localhost for local development', async () => {
      const result = await sandbox.exposePort(8080, {
        name: 'test',
        hostname: 'localhost:8787'
      });

      expect(result.url).toContain('localhost');
      expect(sandbox.client.utils.createSession).toHaveBeenCalled();
    });
  });

  describe('fetch() override - WebSocket detection', () => {
    let superFetchSpy: any;

    beforeEach(async () => {
      await sandbox.setSandboxName('test-sandbox');

      // Spy on Container.prototype.fetch to verify WebSocket routing
      superFetchSpy = vi
        .spyOn(Container.prototype, 'fetch')
        .mockResolvedValue(new Response('WebSocket response'));
    });

    afterEach(() => {
      superFetchSpy?.mockRestore();
    });

    it('should detect WebSocket upgrade header and route to super.fetch', async () => {
      const request = new Request('https://example.com/ws', {
        headers: {
          Upgrade: 'websocket',
          Connection: 'Upgrade'
        }
      });

      const response = await sandbox.fetch(request);

      // Should route through super.fetch() for WebSocket
      expect(superFetchSpy).toHaveBeenCalledTimes(1);
      expect(await response.text()).toBe('WebSocket response');
    });

    it('should route non-WebSocket requests through containerFetch', async () => {
      // GET request
      const getRequest = new Request('https://example.com/api/data');
      await sandbox.fetch(getRequest);
      expect(superFetchSpy).not.toHaveBeenCalled();

      vi.clearAllMocks();

      // POST request
      const postRequest = new Request('https://example.com/api/data', {
        method: 'POST',
        body: JSON.stringify({ data: 'test' }),
        headers: { 'Content-Type': 'application/json' }
      });
      await sandbox.fetch(postRequest);
      expect(superFetchSpy).not.toHaveBeenCalled();

      vi.clearAllMocks();

      // SSE request (should not be detected as WebSocket)
      const sseRequest = new Request('https://example.com/events', {
        headers: { Accept: 'text/event-stream' }
      });
      await sandbox.fetch(sseRequest);
      expect(superFetchSpy).not.toHaveBeenCalled();
    });

    it('should preserve WebSocket request unchanged when calling super.fetch()', async () => {
      const request = new Request('https://example.com/ws', {
        headers: {
          Upgrade: 'websocket',
          Connection: 'Upgrade',
          'Sec-WebSocket-Key': 'test-key-123',
          'Sec-WebSocket-Version': '13'
        }
      });

      await sandbox.fetch(request);

      expect(superFetchSpy).toHaveBeenCalledTimes(1);
      const passedRequest = superFetchSpy.mock.calls[0][0] as Request;
      expect(passedRequest.headers.get('Upgrade')).toBe('websocket');
      expect(passedRequest.headers.get('Connection')).toBe('Upgrade');
      expect(passedRequest.headers.get('Sec-WebSocket-Key')).toBe(
        'test-key-123'
      );
      expect(passedRequest.headers.get('Sec-WebSocket-Version')).toBe('13');
    });

    it('routes active preview proxy requests through the TCP port without starting', async () => {
      const tcpFetch = vi.fn().mockResolvedValue(new Response('preview ok'));
      mockCtx.container.running = true;
      mockCtx.container.getTcpPort = vi
        .fn()
        .mockReturnValue({ fetch: tcpFetch });
      mockPreviewStorageGet(mockCtx, activePreviewStorageState());
      const containerFetchSpy = vi.spyOn(sandbox, 'containerFetch');
      const startAndWaitSpy = vi.spyOn(sandbox, 'startAndWaitForPorts');

      const response = await sandbox.fetch(
        createPreviewProxyRequest('/hello?x=1')
      );

      expect(await response.text()).toBe('preview ok');
      expect(containerFetchSpy).not.toHaveBeenCalled();
      expect(startAndWaitSpy).not.toHaveBeenCalled();
      expect(mockCtx.container.start).not.toHaveBeenCalled();
      expect(mockCtx.container.getTcpPort).toHaveBeenCalledWith(8080);
      expect(tcpFetch).toHaveBeenCalledWith(
        'http://localhost:8080/hello?x=1',
        expect.any(Request)
      );
      const forwardedRequest = tcpFetch.mock.calls[0][1] as Request;
      expect(forwardedRequest.headers.get('X-Sandbox-Name')).toBe(
        'test-sandbox'
      );
    });

    it('preserves WebSocket preview proxy requests when forwarding', async () => {
      const tcpFetch = vi
        .fn()
        .mockResolvedValue(new Response('preview websocket ok'));
      mockCtx.container.running = true;
      mockCtx.container.getTcpPort = vi
        .fn()
        .mockReturnValue({ fetch: tcpFetch });
      mockPreviewStorageGet(mockCtx, activePreviewStorageState());

      const request = createPreviewWebSocketRequest();

      await sandbox.fetch(request);

      expect(tcpFetch).toHaveBeenCalledTimes(1);
      const forwardedRequest = tcpFetch.mock.calls[0][1] as Request;
      expect(forwardedRequest.url).toBe(request.url);
      expect(forwardedRequest.headers.get('Upgrade')).toBe('websocket');
      expect(forwardedRequest.headers.get('Connection')).toBe('Upgrade');
      expect(forwardedRequest.headers.get('Sec-WebSocket-Key')).toBe(
        'test-key-123'
      );
      expect(forwardedRequest.headers.get('Sec-WebSocket-Version')).toBe('13');
      expect(forwardedRequest.headers.has('x-sandbox-preview-proxy')).toBe(
        false
      );
    });

    it('returns user 503 responses when the runtime remains active', async () => {
      const tcpFetch = vi
        .fn()
        .mockResolvedValue(
          new Response('service temporarily unavailable', { status: 503 })
        );
      mockCtx.container.running = true;
      mockCtx.container.getTcpPort = vi
        .fn()
        .mockReturnValue({ fetch: tcpFetch });
      mockPreviewStorageGet(mockCtx, activePreviewStorageState());

      const response = await sandbox.fetch(createPreviewProxyRequest());

      expect(response.status).toBe(503);
      expect(await response.text()).toBe('service temporarily unavailable');
    });

    it('returns stale without forwarding when the container is stopped', async () => {
      mockCtx.container.running = false;
      mockCtx.container.getTcpPort = vi.fn();
      mockPreviewStorageGet(mockCtx, activePreviewStorageState());
      const containerFetchSpy = vi.spyOn(sandbox, 'containerFetch');
      const startAndWaitSpy = vi.spyOn(sandbox, 'startAndWaitForPorts');

      const response = await sandbox.fetch(createPreviewProxyRequest());

      expect(response.status).toBe(410);
      expect(await response.json()).toMatchObject({
        code: 'STALE_PREVIEW_URL'
      });
      expect(mockCtx.container.getTcpPort).not.toHaveBeenCalled();
      expect(containerFetchSpy).not.toHaveBeenCalled();
      expect(startAndWaitSpy).not.toHaveBeenCalled();
      expect(mockCtx.container.start).not.toHaveBeenCalled();
    });

    it('returns stale when the runtime goes inactive during network loss', async () => {
      mockCtx.container.running = true;
      let runtimeActive = true;
      vi.mocked(mockCtx.storage!.get).mockImplementation(async (key) => {
        const state = activePreviewStorageState();
        if (key === 'currentRuntimeIdentity') {
          return runtimeActive ? state.currentRuntimeIdentity : null;
        }
        return state[key as keyof typeof state] ?? null;
      });
      const tcpFetch = vi.fn().mockImplementation(async () => {
        runtimeActive = false;
        throw new Error('Network connection lost.');
      });
      mockCtx.container.getTcpPort = vi
        .fn()
        .mockReturnValue({ fetch: tcpFetch });

      const response = await sandbox.fetch(createPreviewProxyRequest());

      expect(response.status).toBe(410);
      expect(await response.json()).toMatchObject({
        code: 'STALE_PREVIEW_URL'
      });
    });

    it('returns controlled disconnect response when network loss keeps the runtime active', async () => {
      mockCtx.container.running = true;
      mockPreviewStorageGet(mockCtx, activePreviewStorageState());
      const tcpFetch = vi
        .fn()
        .mockRejectedValue(new Error('Network connection lost.'));
      mockCtx.container.getTcpPort = vi
        .fn()
        .mockReturnValue({ fetch: tcpFetch });

      const response = await sandbox.fetch(createPreviewProxyRequest());

      expect(response.status).toBe(500);
      expect(await response.text()).toBe(
        'Container suddenly disconnected, try again'
      );
    });

    it('rejects preview proxy requests without durable authorization', async () => {
      mockCtx.container.running = true;
      vi.mocked(mockCtx.storage!.get).mockImplementation(async (key) =>
        key === 'portTokens' ? {} : null
      );
      const containerFetchSpy = vi.spyOn(sandbox, 'containerFetch');

      const response = await sandbox.fetch(
        new Request('https://8080-test-sandbox-badtoken.example.com/api', {
          headers: {
            'x-sandbox-preview-proxy': '1',
            'x-sandbox-preview-port': '8080',
            'x-sandbox-preview-token': 'badtoken',
            'x-sandbox-preview-sandbox-id': 'test-sandbox'
          }
        })
      );

      expect(response.status).toBe(404);
      expect(await response.json()).toMatchObject({
        code: 'INVALID_TOKEN'
      });
      expect(containerFetchSpy).not.toHaveBeenCalled();
    });

    it('rejects preview proxy requests without current-runtime activation', async () => {
      mockCtx.container.running = true;
      vi.mocked(mockCtx.storage!.get).mockImplementation(async (key) => {
        if (key === 'portTokens') {
          return { '8080': { token: 'token12345678901' } };
        }
        if (key === 'currentRuntimeIdentity') {
          return { id: 'runtime-1' };
        }
        if (key === 'activePreviewPorts') {
          return {};
        }
        return null;
      });
      const containerFetchSpy = vi.spyOn(sandbox, 'containerFetch');

      const response = await sandbox.fetch(createPreviewProxyRequest());

      expect(response.status).toBe(410);
      expect(await response.json()).toMatchObject({
        code: 'STALE_PREVIEW_URL'
      });
      expect(containerFetchSpy).not.toHaveBeenCalled();
    });

    it('rejects persisted preview auth without runtime identity or activation', async () => {
      mockCtx.container.running = true;
      vi.mocked(mockCtx.storage!.get).mockImplementation(async (key) => {
        if (key === 'portTokens') {
          return { '8080': { token: 'token12345678901' } };
        }
        if (key === 'currentRuntimeIdentity') {
          return null;
        }
        if (key === 'activePreviewPorts') {
          return null;
        }
        return null;
      });
      const containerFetchSpy = vi.spyOn(sandbox, 'containerFetch');

      const response = await sandbox.fetch(createPreviewProxyRequest());

      expect(response.status).toBe(410);
      expect(await response.json()).toMatchObject({
        code: 'STALE_PREVIEW_URL'
      });
      expect(containerFetchSpy).not.toHaveBeenCalled();
    });
  });

  describe('wsConnect() method', () => {
    it('should route WebSocket request through switchPort to sandbox.fetch', async () => {
      const { switchPort } = await import('@cloudflare/containers');
      const switchPortMock = vi.mocked(switchPort);

      const request = new Request('http://localhost/ws/echo', {
        headers: {
          Upgrade: 'websocket',
          Connection: 'Upgrade'
        }
      });

      const fetchSpy = vi.spyOn(sandbox, 'fetch');
      const response = await sandbox.wsConnect(request, 8080);

      // Verify switchPort was called with correct port
      expect(switchPortMock).toHaveBeenCalledWith(request, 8080);

      // Verify fetch was called with the switched request
      expect(fetchSpy).toHaveBeenCalledOnce();

      // Verify response indicates WebSocket upgrade
      expect(response.status).toBe(200);
      expect(response.headers.get('X-WebSocket-Upgraded')).toBe('true');
    });

    it('should reject invalid ports with SecurityError', async () => {
      const request = new Request('http://localhost/ws/test', {
        headers: { Upgrade: 'websocket', Connection: 'Upgrade' }
      });

      // Invalid port values
      await expect(sandbox.wsConnect(request, -1)).rejects.toThrow(
        'Invalid port number'
      );
      await expect(sandbox.wsConnect(request, 0)).rejects.toThrow(
        'Invalid port number'
      );
      await expect(sandbox.wsConnect(request, 70000)).rejects.toThrow(
        'Invalid port number'
      );

      // Privileged ports
      await expect(sandbox.wsConnect(request, 80)).rejects.toThrow(
        'Invalid port number'
      );
      await expect(sandbox.wsConnect(request, 443)).rejects.toThrow(
        'Invalid port number'
      );
    });

    it('should preserve request properties through routing', async () => {
      const request = new Request(
        'http://localhost/ws/test?token=abc&room=lobby',
        {
          headers: {
            Upgrade: 'websocket',
            Connection: 'Upgrade',
            'X-Custom-Header': 'custom-value'
          }
        }
      );

      const fetchSpy = vi.spyOn(sandbox, 'fetch');
      await sandbox.wsConnect(request, 8080);

      const calledRequest = fetchSpy.mock.calls[0][0];

      // Verify headers are preserved
      expect(calledRequest.headers.get('Upgrade')).toBe('websocket');
      expect(calledRequest.headers.get('X-Custom-Header')).toBe('custom-value');

      // Verify query parameters are preserved
      const url = new URL(calledRequest.url);
      expect(url.searchParams.get('token')).toBe('abc');
      expect(url.searchParams.get('room')).toBe('lobby');
    });
  });

  describe('deleteSession', () => {
    it('should prevent deletion of default session', async () => {
      // Trigger creation of default session
      await sandbox.exec('echo "test"');

      // Verify default session exists
      expect((sandbox as any).defaultSession).toBeTruthy();
      const defaultSessionId = (sandbox as any).defaultSession;

      // Attempt to delete default session should throw
      await expect(sandbox.deleteSession(defaultSessionId)).rejects.toThrow(
        `Cannot delete default session '${defaultSessionId}'. Use sandbox.destroy() to terminate the sandbox.`
      );
    });

    it('should allow deletion of non-default sessions', async () => {
      // Mock the deleteSession API response
      vi.spyOn(sandbox.client.utils, 'deleteSession').mockResolvedValue({
        success: true,
        sessionId: 'custom-session',
        timestamp: new Date().toISOString()
      });

      // Create a custom session
      await sandbox.createSession({ id: 'custom-session' });

      // Should successfully delete non-default session
      const result = await sandbox.deleteSession('custom-session');
      expect(result.success).toBe(true);
      expect(result.sessionId).toBe('custom-session');
    });
  });

  describe('constructPreviewUrl validation', () => {
    it('should throw clear error for ID with uppercase letters without normalizeId', async () => {
      await sandbox.setSandboxName('MyProject-123', false);
      await expect(
        sandbox.exposePort(8080, { hostname: 'example.com' })
      ).rejects.toThrow(/Preview URLs require lowercase sandbox IDs/);
    });

    it('should construct valid URL for lowercase ID', async () => {
      await sandbox.setSandboxName('my-project', false);
      const result = await sandbox.exposePort(8080, {
        hostname: 'example.com'
      });

      expect(result.url).toMatch(
        /^https:\/\/8080-my-project-[a-z0-9_]{16}\.example\.com\/?$/
      );
      expect(result.port).toBe(8080);
    });

    it('should construct valid URL with normalized ID', async () => {
      await sandbox.setSandboxName('myproject-123', true);
      const result = await sandbox.exposePort(4000, { hostname: 'my-app.dev' });

      expect(result.url).toMatch(
        /^https:\/\/4000-myproject-123-[a-z0-9_]{16}\.my-app\.dev\/?$/
      );
      expect(result.port).toBe(4000);
    });

    it('should construct valid localhost URL', async () => {
      await sandbox.setSandboxName('test-sandbox', false);
      const result = await sandbox.exposePort(8080, {
        hostname: 'localhost:3000'
      });

      expect(result.url).toMatch(
        /^http:\/\/8080-test-sandbox-[a-z0-9_]{16}\.localhost:3000\/?$/
      );
    });

    it('should include helpful guidance in error message', async () => {
      await sandbox.setSandboxName('MyProject-ABC', false);
      await expect(
        sandbox.exposePort(8080, { hostname: 'example.com' })
      ).rejects.toThrow(
        /getSandbox\(ns, "MyProject-ABC", \{ normalizeId: true \}\)/
      );
    });
  });

  describe('timeout configuration validation', () => {
    it('should reject invalid timeout values', async () => {
      // NaN, Infinity, and out-of-range values should all be rejected
      await expect(
        sandbox.setContainerTimeouts({ instanceGetTimeoutMS: NaN })
      ).rejects.toThrow();

      await expect(
        sandbox.setContainerTimeouts({ portReadyTimeoutMS: Infinity })
      ).rejects.toThrow();

      await expect(
        sandbox.setContainerTimeouts({ instanceGetTimeoutMS: -1 })
      ).rejects.toThrow();

      await expect(
        sandbox.setContainerTimeouts({ waitIntervalMS: 999_999 })
      ).rejects.toThrow();
    });

    it('should accept valid timeout values', async () => {
      await expect(
        sandbox.setContainerTimeouts({
          instanceGetTimeoutMS: 30_000,
          portReadyTimeoutMS: 90_000,
          waitIntervalMS: 300
        })
      ).resolves.toBeUndefined();
    });
  });

  describe('custom token validation', () => {
    beforeEach(async () => {
      await sandbox.setSandboxName('test-sandbox', false);

      vi.mocked(mockCtx.storage!.get).mockResolvedValue({} as any);
      vi.mocked(mockCtx.storage!.put).mockResolvedValue(undefined);
    });

    it('should validate token format and length', async () => {
      const result = await sandbox.exposePort(8080, {
        hostname: 'example.com',
        token: 'abc_123_xyz'
      });
      expect(result.url).toContain('abc_123_xyz');

      await expect(
        sandbox.exposePort(8080, { hostname: 'example.com', token: '' })
      ).rejects.toThrow('Custom token cannot be empty');

      await expect(
        sandbox.exposePort(8080, {
          hostname: 'example.com',
          token: 'a1234567890123456'
        })
      ).rejects.toThrow('Maximum 16 characters');

      await expect(
        sandbox.exposePort(8080, { hostname: 'example.com', token: 'ABC123' })
      ).rejects.toThrow('lowercase letters');

      await expect(
        sandbox.exposePort(8080, { hostname: 'example.com', token: 'abc-123' })
      ).rejects.toThrow('underscores (_)');
    });

    it('should prevent token collision across different ports', async () => {
      await sandbox.exposePort(8080, {
        hostname: 'example.com',
        token: 'shared'
      });

      vi.mocked(mockCtx.storage!.get).mockResolvedValueOnce({
        '8080': 'shared'
      } as any);

      await expect(
        sandbox.exposePort(8081, { hostname: 'example.com', token: 'shared' })
      ).rejects.toThrow(/already in use by port 8080/);
    });

    it('should allow re-exposing same port with same token', async () => {
      await sandbox.exposePort(8080, {
        hostname: 'example.com',
        token: 'stable'
      });

      vi.mocked(mockCtx.storage!.get).mockResolvedValueOnce({
        '8080': 'stable'
      } as any);

      const result = await sandbox.exposePort(8080, {
        hostname: 'example.com',
        token: 'stable'
      });
      expect(result.url).toContain('stable');
    });
  });

  describe('preview URL runtime activation', () => {
    beforeEach(async () => {
      await sandbox.setSandboxName('test-sandbox', false);
    });

    it('onStart() marks a new current runtime without restoring saved ports', async () => {
      vi.mocked(mockCtx.storage!.get).mockImplementation(async (key) =>
        key === 'portTokens'
          ? {
              '8080': { token: 'tok8080', name: 'api' }
            }
          : null
      );

      await (sandbox as any).onStart();

      expect(mockCtx.storage.put).toHaveBeenCalledWith(
        'currentRuntimeIdentity',
        expect.objectContaining({
          id: expect.any(String)
        })
      );
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });

    it('onStop() preserves durable auth and clears runtime-scoped preview state', async () => {
      await (sandbox as any).onStop();

      const deletedKeys = vi
        .mocked(mockCtx.storage!.delete)
        .mock.calls.map((call) => call[0]);
      expect(deletedKeys).not.toContain('portTokens');
      expect(deletedKeys).toContain('activePreviewPorts');
      expect(deletedKeys).toContain('currentRuntimeIdentity');
      expect(deletedKeys).toContain('defaultSession');
    });

    it('stop() clears runtime-scoped preview state before signaling the container', async () => {
      const callOrder: string[] = [];
      vi.mocked(mockCtx.storage!.delete).mockImplementation(async (key) => {
        callOrder.push(`delete:${String(key)}`);
      });
      vi.spyOn(Container.prototype, 'stop').mockImplementation(async () => {
        callOrder.push('super.stop');
      });

      await sandbox.stop();

      expect(callOrder.indexOf('delete:activePreviewPorts')).toBeLessThan(
        callOrder.indexOf('super.stop')
      );
      expect(callOrder.indexOf('delete:currentRuntimeIdentity')).toBeLessThan(
        callOrder.indexOf('super.stop')
      );
      expect(callOrder).not.toContain('delete:portTokens');
    });

    it('destroy() clears preview auth and runtime-scoped state before calling super.destroy()', async () => {
      const callOrder: string[] = [];

      vi.mocked(mockCtx.storage!.delete).mockImplementation(async (key) => {
        callOrder.push(`delete:${String(key)}`);
      });

      vi.spyOn(Container.prototype, 'destroy').mockImplementation(async () => {
        callOrder.push('super.destroy');
      });

      await sandbox.destroy();

      const superIdx = callOrder.indexOf('super.destroy');
      for (const key of [
        'portTokens',
        'activePreviewPorts',
        'currentRuntimeIdentity'
      ]) {
        const deleteIdx = callOrder.indexOf(`delete:${key}`);
        expect(deleteIdx).toBeGreaterThanOrEqual(0);
        expect(deleteIdx).toBeLessThan(superIdx);
      }
    });

    it('exposePort() persists durable auth and current-runtime activation', async () => {
      vi.mocked(mockCtx.storage!.get).mockImplementation(async (key) => {
        if (key === 'portTokens') {
          return {};
        }
        if (key === 'currentRuntimeIdentity') {
          return { id: 'runtime-1' };
        }
        if (key === 'activePreviewPorts') {
          return {};
        }
        return null;
      });
      const putSpy = vi.mocked(mockCtx.storage!.put);

      await sandbox.exposePort(8080, {
        hostname: 'example.com',
        token: 'friendlytok',
        name: 'my-api'
      });

      expect(putSpy).toHaveBeenCalledWith('portTokens', {
        '8080': { token: 'friendlytok', name: 'my-api' }
      });
      expect(putSpy).toHaveBeenCalledWith('activePreviewPorts', {
        '8080': {
          runtimeIdentityID: 'runtime-1',
          token: 'friendlytok'
        }
      });
    });

    it('exposePort() does not write preview state when runtime identity changes before storage writes', async () => {
      let runtimeIdentityReads = 0;
      vi.mocked(mockCtx.storage!.get).mockImplementation(async (key) => {
        if (key === 'portTokens') {
          return {};
        }
        if (key === 'currentRuntimeIdentity') {
          runtimeIdentityReads++;
          return {
            id: runtimeIdentityReads === 1 ? 'runtime-1' : 'runtime-2'
          };
        }
        if (key === 'activePreviewPorts') {
          return {};
        }
        return null;
      });
      vi.mocked(mockCtx.storage!.put).mockClear();

      await expect(
        sandbox.exposePort(8080, {
          hostname: 'example.com',
          token: 'friendlytok'
        })
      ).rejects.toBeInstanceOf(RuntimeIdentityInactiveError);

      expect(mockCtx.storage.put).not.toHaveBeenCalledWith(
        'portTokens',
        expect.anything()
      );
      expect(mockCtx.storage.put).not.toHaveBeenCalledWith(
        'activePreviewPorts',
        expect.anything()
      );
    });

    it('exposePort() rejects if runtime identity changes after preview state writes', async () => {
      let runtimeIdentityReads = 0;
      vi.mocked(mockCtx.storage!.get).mockImplementation(async (key) => {
        if (key === 'portTokens') {
          return {};
        }
        if (key === 'currentRuntimeIdentity') {
          runtimeIdentityReads++;
          return {
            id: runtimeIdentityReads <= 2 ? 'runtime-1' : 'runtime-2'
          };
        }
        if (key === 'activePreviewPorts') {
          return {};
        }
        return null;
      });
      vi.mocked(mockCtx.storage!.put).mockClear();

      await expect(
        sandbox.exposePort(8080, {
          hostname: 'example.com',
          token: 'friendlytok'
        })
      ).rejects.toBeInstanceOf(RuntimeIdentityInactiveError);

      expect(mockCtx.storage.put).toHaveBeenCalledWith('portTokens', {
        '8080': { token: 'friendlytok', name: undefined }
      });
      expect(mockCtx.storage.put).toHaveBeenCalledWith('activePreviewPorts', {
        '8080': {
          runtimeIdentityID: 'runtime-1',
          token: 'friendlytok'
        }
      });
    });

    it('exposePort() reuses the existing token when re-exposing the same port without a token', async () => {
      vi.mocked(mockCtx.storage!.get).mockImplementation(async (key) => {
        if (key === 'portTokens') {
          return { '8080': { token: 'stabletok' } };
        }
        if (key === 'currentRuntimeIdentity') {
          return { id: 'runtime-1' };
        }
        if (key === 'activePreviewPorts') {
          return {};
        }
        return null;
      });

      const result = await sandbox.exposePort(8080, {
        hostname: 'example.com'
      });

      expect(result.url).toContain('stabletok');
      expect(mockCtx.storage.put).toHaveBeenCalledWith(
        'activePreviewPorts',
        expect.objectContaining({
          '8080': expect.objectContaining({ token: 'stabletok' })
        })
      );
    });

    it('exposePort() does not restore a port revoked while the runtime starts', async () => {
      const storage = new Map<string, unknown>([
        ['portTokens', { '8080': { token: 'oldtoken' } }],
        ['currentRuntimeIdentity', { id: 'runtime-1' }],
        ['activePreviewPorts', {}]
      ]);
      mockCtx.storage.get.mockImplementation(
        async (key: string) => storage.get(key) ?? null
      );
      mockCtx.storage.put.mockImplementation(async (key: string, value) => {
        storage.set(key, value);
      });
      mockCtx.storage.delete.mockImplementation(async (key: string) => {
        storage.delete(key);
      });

      let releaseStartup!: () => void;
      const startupGate = new Promise<void>((resolve) => {
        releaseStartup = resolve;
      });
      const ensureDefaultSessionSpy = vi
        .spyOn(
          sandbox as unknown as { ensureDefaultSession: () => Promise<string> },
          'ensureDefaultSession'
        )
        .mockImplementation(async () => {
          await startupGate;
          return 'sandbox-default';
        });

      const exposePromise = sandbox.exposePort(9090, {
        hostname: 'example.com',
        token: 'newtoken'
      });
      await vi.waitFor(() =>
        expect(ensureDefaultSessionSpy).toHaveBeenCalled()
      );

      await sandbox.unexposePort(8080);
      expect(storage.get('portTokens')).toEqual({});

      releaseStartup();
      await exposePromise;

      expect(storage.get('portTokens')).toEqual({
        '9090': { token: 'newtoken', name: undefined }
      });
    });
  });

  describe('tunnels lifecycle storage', () => {
    function seedMixedTunnelStorage(): Array<{ key: string; value: unknown }> {
      const puts: Array<{ key: string; value: unknown }> = [];
      vi.mocked(mockCtx.storage!.get).mockImplementation(async (key) => {
        if (key === 'tunnels') {
          return {
            '8080': {
              id: 'quick-abc',
              port: 8080,
              url: 'https://x.trycloudflare.com',
              hostname: 'x.trycloudflare.com',
              createdAt: '2024-01-01T00:00:00.000Z'
            },
            '8081': {
              id: 'uuid-1',
              port: 8081,
              name: 'app',
              hostname: 'app.example.com',
              url: 'https://app.example.com',
              createdAt: '2024-01-01T00:00:00.000Z'
            }
          };
        }
        if (key === 'tunnels:meta') {
          return {
            '8080': { optionsHash: 'quick' },
            '8081': { optionsHash: 'named:app', dnsRecordId: 'rec-1' }
          };
        }
        return undefined as any;
      });
      vi.mocked(mockCtx.storage!.put).mockImplementation(
        async (key: string, value: unknown) => {
          puts.push({ key, value });
        }
      );
      (mockCtx.storage as unknown as { transaction: unknown }).transaction = vi
        .fn()
        .mockImplementation(
          async (closure: (txn: unknown) => Promise<unknown>) =>
            closure(mockCtx.storage)
        );
      return puts;
    }

    function expectOnlyNamedTunnelPreserved(
      puts: Array<{ key: string; value: unknown }>
    ): void {
      const nextTunnels = puts.find((p) => p.key === 'tunnels')
        ?.value as Record<string, { name?: string }>;
      const nextMeta = puts.find((p) => p.key === 'tunnels:meta')
        ?.value as Record<string, { needsRespawn?: boolean }>;

      expect(Object.keys(nextTunnels ?? {})).toEqual(['8081']);
      expect(nextMeta?.['8081']?.needsRespawn).toBe(true);
      expect(nextMeta?.['8080']).toBeUndefined();
    }

    it('onStart() preserves named-tunnel records across restart and drops quick ones', async () => {
      const puts = seedMixedTunnelStorage();

      await (sandbox as any).onStart();

      expectOnlyNamedTunnelPreserved(puts);
    });

    it('onStop() preserves named-tunnel records and drops quick ones', async () => {
      const puts = seedMixedTunnelStorage();

      await (sandbox as any).onStop();

      expectOnlyNamedTunnelPreserved(puts);
    });

    it('destroy() deletes the tunnels storage key', async () => {
      const deletedKeys: string[] = [];
      vi.mocked(mockCtx.storage!.delete).mockImplementation(async (key) => {
        deletedKeys.push(String(key));
        return true;
      });
      vi.spyOn(Container.prototype, 'destroy').mockImplementation(
        async () => {}
      );

      await sandbox.destroy();

      expect(deletedKeys).toContain('tunnels');
    });
  });

  describe('validatePortToken', () => {
    beforeEach(() => {
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) =>
        key === 'portTokens' ? { '8080': { token: 'correcttoken' } } : null
      );
    });

    it('returns true for a matching token without calling the container', async () => {
      const result = await sandbox.validatePortToken(8080, 'correcttoken');

      expect(result).toBe(true);
    });

    it('returns false for a mismatched token', async () => {
      const result = await sandbox.validatePortToken(8080, 'wrongtoken');

      expect(result).toBe(false);
    });

    it('returns false when no token is stored for the port', async () => {
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) =>
        key === 'portTokens' ? {} : null
      );

      const result = await sandbox.validatePortToken(8080, 'anytoken');

      expect(result).toBe(false);
    });

    it('accepts legacy string-valued tokens from storage', async () => {
      // readPortTokens normalizes the { port: string } storage shape
      // to { port: { token: string } }; legacy entries must still
      // authenticate.
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) =>
        key === 'portTokens' ? { '8080': 'legacytoken' } : null
      );

      const result = await sandbox.validatePortToken(8080, 'legacytoken');

      expect(result).toBe(true);
    });

    it('does not call isPortExposed', async () => {
      const spy = vi.spyOn(sandbox, 'isPortExposed');

      await sandbox.validatePortToken(8080, 'correcttoken');

      expect(spy).not.toHaveBeenCalled();
    });
  });

  describe('getExposedPorts Contract B', () => {
    beforeEach(async () => {
      await sandbox.setSandboxName('test-sandbox');
    });

    it('lists only ports activated for the current runtime without contacting the container', async () => {
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) => {
        if (key === 'currentRuntimeIdentity') {
          return { id: 'runtime-1' };
        }
        if (key === 'portTokens') {
          return {
            '8080': { token: 'tok8080', name: 'api' },
            '9090': { token: 'tok9090' }
          };
        }
        if (key === 'activePreviewPorts') {
          return {
            '8080': {
              runtimeIdentityID: 'runtime-1',
              token: 'tok8080'
            },
            '9090': {
              runtimeIdentityID: 'runtime-old',
              token: 'tok9090'
            }
          };
        }
        return null;
      });

      const result = await sandbox.getExposedPorts('example.com');

      expect(result).toEqual([
        {
          url: 'https://8080-test-sandbox-tok8080.example.com/',
          port: 8080,
          status: 'active'
        }
      ]);
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });

    it('returns an empty list when durable auth exists without a current runtime', async () => {
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) => {
        if (key === 'portTokens') {
          return { '8080': { token: 'tok8080' } };
        }
        if (key === 'activePreviewPorts') {
          return {
            '8080': {
              runtimeIdentityID: 'runtime-1',
              token: 'tok8080'
            }
          };
        }
        return null;
      });

      await expect(sandbox.getExposedPorts('example.com')).resolves.toEqual([]);
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });

    it('omits durable auth without matching current-runtime activation', async () => {
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) => {
        if (key === 'currentRuntimeIdentity') {
          return { id: 'runtime-1' };
        }
        if (key === 'portTokens') {
          return { '8080': { token: 'tok8080' } };
        }
        if (key === 'activePreviewPorts') {
          return {};
        }
        return null;
      });

      await expect(sandbox.getExposedPorts('example.com')).resolves.toEqual([]);
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });
  });

  describe('isPortExposed Contract B', () => {
    beforeEach(() => {});

    it('returns true only for durable auth activated in the current runtime', async () => {
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) => {
        if (key === 'currentRuntimeIdentity') {
          return { id: 'runtime-1' };
        }
        if (key === 'portTokens') {
          return { '8080': { token: 'tok8080' } };
        }
        if (key === 'activePreviewPorts') {
          return {
            '8080': {
              runtimeIdentityID: 'runtime-1',
              token: 'tok8080'
            }
          };
        }
        return null;
      });

      await expect(sandbox.isPortExposed(8080)).resolves.toBe(true);
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });

    it('returns false for durable auth without activation', async () => {
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) => {
        if (key === 'currentRuntimeIdentity') {
          return { id: 'runtime-1' };
        }
        if (key === 'portTokens') {
          return { '8080': { token: 'tok8080' } };
        }
        if (key === 'activePreviewPorts') {
          return {};
        }
        return null;
      });

      await expect(sandbox.isPortExposed(8080)).resolves.toBe(false);
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });

    it('returns false for activation from an old runtime', async () => {
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) => {
        if (key === 'currentRuntimeIdentity') {
          return { id: 'runtime-1' };
        }
        if (key === 'portTokens') {
          return { '8080': { token: 'tok8080' } };
        }
        if (key === 'activePreviewPorts') {
          return {
            '8080': {
              runtimeIdentityID: 'runtime-old',
              token: 'tok8080'
            }
          };
        }
        return null;
      });

      await expect(sandbox.isPortExposed(8080)).resolves.toBe(false);
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });
  });

  describe('unexposePort Contract B', () => {
    beforeEach(() => {});

    it('revokes auth and activation without waking when no current runtime is active', async () => {
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) => {
        if (key === 'portTokens') {
          return { '8080': { token: 'tok8080' } };
        }
        if (key === 'activePreviewPorts') {
          return {
            '8080': {
              runtimeIdentityID: 'runtime-1',
              token: 'tok8080'
            }
          };
        }
        return null;
      });

      await sandbox.unexposePort(8080);

      expect(mockCtx.storage.put).toHaveBeenCalledWith('portTokens', {});
      expect(mockCtx.storage.delete).toHaveBeenCalledWith('activePreviewPorts');
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });

    it('revokes auth and activation without touching the container registry when runtime is active', async () => {
      vi.mocked(mockCtx.storage.get).mockImplementation(async (key) => {
        if (key === 'currentRuntimeIdentity') {
          return { id: 'runtime-1' };
        }
        if (key === 'portTokens') {
          return { '8080': { token: 'tok8080' } };
        }
        if (key === 'activePreviewPorts') {
          return {
            '8080': {
              runtimeIdentityID: 'runtime-1',
              token: 'tok8080'
            }
          };
        }
        return null;
      });

      await sandbox.unexposePort(8080);

      expect(mockCtx.storage.put).toHaveBeenCalledWith('portTokens', {});
      expect(mockCtx.storage.delete).toHaveBeenCalledWith('activePreviewPorts');
      expect(sandbox.client.utils.createSession).not.toHaveBeenCalled();
    });
  });

  describe('sleepAfter configuration', () => {
    it('should call renewActivityTimeout when setSleepAfter is called', async () => {
      // Spy on renewActivityTimeout (inherited from Container)
      const renewSpy = vi.spyOn(sandbox as any, 'renewActivityTimeout');

      await sandbox.setSleepAfter('30m');

      // Verify sleepAfter was updated
      expect((sandbox as any).sleepAfter).toBe('30m');

      // Verify renewActivityTimeout was called to reschedule with new value
      expect(renewSpy).toHaveBeenCalled();
    });

    it('should accept numeric sleepAfter values', async () => {
      const renewSpy = vi.spyOn(sandbox as any, 'renewActivityTimeout');

      await sandbox.setSleepAfter(3600); // 1 hour in seconds

      expect((sandbox as any).sleepAfter).toBe(3600);
      expect(renewSpy).toHaveBeenCalled();
    });

    it('should persist sleepAfter to storage', async () => {
      await sandbox.setSleepAfter('30m');

      expect(mockCtx.storage.put).toHaveBeenCalledWith('sleepAfter', '30m');
    });

    it('should restore sleepAfter from storage on restart', async () => {
      const restartCtx = {
        ...mockCtx,
        storage: {
          ...mockCtx.storage,
          get: vi.fn().mockImplementation((key: string) => {
            if (key === 'sleepAfter') return Promise.resolve('30m');
            return Promise.resolve(null);
          }),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          )
      };

      const restored = new Sandbox(
        restartCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        mockEnv
      );

      await vi.waitFor(() => {
        expect((restored as any).sleepAfter).toBe('30m');
      });
    });

    it('is a no-op when sleepAfter matches current value', async () => {
      await sandbox.setSleepAfter('30m');
      const putCallsBefore = mockCtx.storage.put.mock.calls.length;
      const renewSpy = vi.spyOn(sandbox as any, 'renewActivityTimeout');

      await sandbox.setSleepAfter('30m');

      expect(mockCtx.storage.put.mock.calls.length).toBe(putCallsBefore);
      expect(renewSpy).not.toHaveBeenCalled();
    });

    it('leaves in-memory state unchanged when storage.put fails', async () => {
      const before = (sandbox as any).sleepAfter;
      vi.mocked(mockCtx.storage.put).mockRejectedValueOnce(
        new Error('simulated storage failure')
      );

      await expect(sandbox.setSleepAfter('45m')).rejects.toThrow(
        'simulated storage failure'
      );

      expect((sandbox as any).sleepAfter).toBe(before);
    });
  });

  describe('constructor - interceptHttps env injection', () => {
    it('injects SANDBOX_INTERCEPT_HTTPS into envVars when interceptHttps is true', async () => {
      class SandboxWithHttps extends Sandbox<Record<string, unknown>> {
        override interceptHttps = true;
      }

      const customCtx = {
        ...mockCtx,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          ),
        storage: {
          get: vi.fn().mockResolvedValue(null),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any
      };

      const instance = new SandboxWithHttps(
        customCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        mockEnv
      );

      await vi.waitFor(() => {
        expect((instance as any).envVars.SANDBOX_INTERCEPT_HTTPS).toBe('1');
      });
    });

    it('does not inject SANDBOX_INTERCEPT_HTTPS when interceptHttps is false', async () => {
      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      expect(sandbox.envVars.SANDBOX_INTERCEPT_HTTPS).toBeUndefined();
    });

    it('preserves existing envVars entries when injecting', async () => {
      class SandboxWithHttps extends Sandbox<Record<string, unknown>> {
        override interceptHttps = true;
        override envVars: Record<string, string> = { MY_KEY: 'my-value' };
      }

      const customCtx = {
        ...mockCtx,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          ),
        storage: {
          get: vi.fn().mockResolvedValue(null),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any
      };

      const instance = new SandboxWithHttps(
        customCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        mockEnv
      );

      await vi.waitFor(() => {
        expect((instance as any).envVars.SANDBOX_INTERCEPT_HTTPS).toBe('1');
      });

      expect((instance as any).envVars.MY_KEY).toBe('my-value');
    });
  });

  describe('keepAlive configuration', () => {
    it('should reschedule activity timeout when keepAlive is disabled', async () => {
      const renewSpy = vi.spyOn(sandbox as any, 'renewActivityTimeout');

      await sandbox.setKeepAlive(true);
      expect(renewSpy).not.toHaveBeenCalled();

      await sandbox.setKeepAlive(false);

      expect(mockCtx.storage.put).toHaveBeenNthCalledWith(
        2,
        'keepAliveEnabled',
        false
      );
      expect(renewSpy).toHaveBeenCalledTimes(1);
    });

    it('is a no-op when setKeepAlive(false) is called on an already-disabled sandbox', async () => {
      await sandbox.setKeepAlive(true);
      await sandbox.setKeepAlive(false);
      const putCallsBefore = mockCtx.storage.put.mock.calls.length;
      const renewSpy = vi.spyOn(sandbox as any, 'renewActivityTimeout');

      await sandbox.setKeepAlive(false);

      expect(mockCtx.storage.put.mock.calls.length).toBe(putCallsBefore);
      expect(renewSpy).not.toHaveBeenCalled();
    });
  });

  describe('containerTimeouts configuration', () => {
    // The in-memory defaults come from env vars with SDK fallbacks. A first
    // explicit call whose values happen to equal those defaults must still
    // persist so the user's intent is recorded independently of whatever the
    // env currently resolves to. A subsequent identical call is then a no-op.
    it('persists on first explicit call even when values match current in-memory defaults', async () => {
      const current = { ...(sandbox as any).containerTimeouts };

      await sandbox.setContainerTimeouts(current);

      expect(mockCtx.storage.put).toHaveBeenCalledWith(
        'containerTimeouts',
        expect.objectContaining(current)
      );

      const putCallsBefore = mockCtx.storage.put.mock.calls.length;
      const setRetrySpy = vi.spyOn(sandbox.client, 'setRetryTimeoutMs');
      await sandbox.setContainerTimeouts(current);
      expect(mockCtx.storage.put.mock.calls.length).toBe(putCallsBefore);
      expect(setRetrySpy).not.toHaveBeenCalled();
    });
  });

  describe('setSandboxName atomicity', () => {
    // sandboxName and normalizeId are written together; if the second write
    // rejects, in-memory state must match storage (both unchanged).
    it('leaves in-memory state unchanged when the second of the two writes fails', async () => {
      let callCount = 0;
      vi.mocked(mockCtx.storage.put).mockImplementation(async () => {
        callCount++;
        if (callCount === 2) throw new Error('simulated storage failure');
        return undefined;
      });

      const beforeSandboxName = (sandbox as any).sandboxName;
      const beforeNormalizeId = (sandbox as any).normalizeId;

      await expect(sandbox.setSandboxName('my-sandbox', true)).rejects.toThrow(
        'simulated storage failure'
      );

      expect((sandbox as any).sandboxName).toBe(beforeSandboxName);
      expect((sandbox as any).normalizeId).toBe(beforeNormalizeId);
    });
  });

  describe('configure() idempotency', () => {
    // getSandbox re-invokes configure() on every cold-isolate cache miss.
    // Identical reapply must be side-effect-free.
    it('does not renew activity timeout on a repeated identical configure call', async () => {
      const renewSpy = vi.spyOn(sandbox as any, 'renewActivityTimeout');

      await sandbox.configure({ sleepAfter: '3s' });
      const renewCallsAfterFirst = renewSpy.mock.calls.length;
      expect(renewCallsAfterFirst).toBeGreaterThan(0);

      await sandbox.configure({ sleepAfter: '3s' });

      expect(renewSpy.mock.calls.length).toBe(renewCallsAfterFirst);
    });
  });

  describe('backup path allowlist', () => {
    function createBackupBucket() {
      return {
        put: vi.fn().mockResolvedValue(undefined),
        get: vi.fn(),
        head: vi.fn(),
        delete: vi.fn().mockResolvedValue(undefined),
        list: vi.fn().mockResolvedValue({ objects: [], truncated: false })
      };
    }

    async function createBackupSandbox(
      bucket = createBackupBucket(),
      env: Record<string, unknown> = {}
    ) {
      const backupSandbox = new Sandbox(
        mockCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        {
          BACKUP_BUCKET: bucket,
          CLOUDFLARE_ACCOUNT_ID: 'test-account',
          R2_ACCESS_KEY_ID: 'test-key',
          R2_SECRET_ACCESS_KEY: 'test-secret',
          BACKUP_BUCKET_NAME: 'test-backups',
          ...env
        }
      );

      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      return { backupSandbox, bucket };
    }

    it('should build backup object URLs with the default R2 endpoint', async () => {
      const { backupSandbox } = await createBackupSandbox();

      const url = (
        backupSandbox as unknown as {
          getBackupObjectURL: (
            accountId: string,
            bucketName: string,
            r2Key: string
          ) => URL;
        }
      ).getBackupObjectURL(
        'test-account',
        'test-backups',
        'backups/id/data.sqsh'
      );

      expect(url.toString()).toBe(
        'https://test-account.r2.cloudflarestorage.com/test-backups/backups/id/data.sqsh'
      );
    });

    it('should build backup object URLs with a custom R2 endpoint', async () => {
      const { backupSandbox } = await createBackupSandbox(
        createBackupBucket(),
        {
          BACKUP_BUCKET_ENDPOINT:
            'https://test-account.eu.r2.cloudflarestorage.com/'
        }
      );

      const url = (
        backupSandbox as unknown as {
          getBackupObjectURL: (
            accountId: string,
            bucketName: string,
            r2Key: string
          ) => URL;
        }
      ).getBackupObjectURL(
        'test-account',
        'test-backups',
        'backups/id/data.sqsh'
      );

      expect(url.toString()).toBe(
        'https://test-account.eu.r2.cloudflarestorage.com/test-backups/backups/id/data.sqsh'
      );
    });

    it('should throw InvalidBackupConfigError for a malformed BACKUP_BUCKET_ENDPOINT', async () => {
      await expect(
        createBackupSandbox(createBackupBucket(), {
          BACKUP_BUCKET_ENDPOINT: 'not-a-url'
        })
      ).rejects.toThrow(InvalidBackupConfigError);
    });

    it('should throw InvalidBackupConfigError for an http BACKUP_BUCKET_ENDPOINT', async () => {
      await expect(
        createBackupSandbox(createBackupBucket(), {
          BACKUP_BUCKET_ENDPOINT:
            'http://test-account.eu.r2.cloudflarestorage.com'
        })
      ).rejects.toThrow(InvalidBackupConfigError);
    });

    it('should throw InvalidBackupConfigError for a BACKUP_BUCKET_ENDPOINT with a path', async () => {
      await expect(
        createBackupSandbox(createBackupBucket(), {
          BACKUP_BUCKET_ENDPOINT:
            'https://test-account.eu.r2.cloudflarestorage.com/some/prefix'
        })
      ).rejects.toThrow(InvalidBackupConfigError);
    });

    it('should throw InvalidBackupConfigError for a BACKUP_BUCKET_ENDPOINT with a query', async () => {
      await expect(
        createBackupSandbox(createBackupBucket(), {
          BACKUP_BUCKET_ENDPOINT:
            'https://test-account.eu.r2.cloudflarestorage.com?region=eu'
        })
      ).rejects.toThrow(InvalidBackupConfigError);
    });

    it('should throw InvalidBackupConfigError for a BACKUP_BUCKET_ENDPOINT with a fragment', async () => {
      await expect(
        createBackupSandbox(createBackupBucket(), {
          BACKUP_BUCKET_ENDPOINT:
            'https://test-account.eu.r2.cloudflarestorage.com#bucket'
        })
      ).rejects.toThrow(InvalidBackupConfigError);
    });

    it('should allow creating a backup from /app', async () => {
      const { backupSandbox, bucket } = await createBackupSandbox();

      vi.spyOn(backupSandbox.client.utils, 'createSession').mockResolvedValue({
        success: true,
        id: 'backup-session',
        message: 'Created'
      } as any);
      vi.spyOn(backupSandbox.client.utils, 'deleteSession').mockResolvedValue({
        success: true,
        id: 'backup-session',
        message: 'Deleted'
      } as any);
      const createArchiveSpy = vi
        .spyOn(backupSandbox.client.backup, 'createArchive')
        .mockResolvedValue({
          success: true,
          sizeBytes: 42,
          archivePath: '/var/backups/mock.sqsh'
        });
      vi.spyOn(backupSandbox as any, 'uploadBackupPresigned').mockResolvedValue(
        undefined
      );
      vi.spyOn(backupSandbox as any, 'execWithSession').mockResolvedValue({
        stdout: '',
        stderr: '',
        exitCode: 0
      });

      const backup = await backupSandbox.createBackup({ dir: '/app/project' });

      expect(backup.dir).toBe('/app/project');
      expect(createArchiveSpy).toHaveBeenCalledWith(
        '/app/project',
        expect.stringMatching(/^\/var\/backups\/.+\.sqsh$/),
        expect.stringMatching(/^__sandbox_backup_/),
        {
          gitignore: false,
          excludes: [],
          compression: {
            format: 'lz4',
            threads: 8
          }
        }
      );
      expect(bucket.put).toHaveBeenCalled();
    });

    it('should normalize globstar excludes before calling createArchive', async () => {
      const { backupSandbox } = await createBackupSandbox();

      vi.spyOn(backupSandbox.client.utils, 'createSession').mockResolvedValue({
        success: true,
        id: 'backup-session',
        message: 'Created'
      } as any);
      vi.spyOn(backupSandbox.client.utils, 'deleteSession').mockResolvedValue({
        success: true,
        id: 'backup-session',
        message: 'Deleted'
      } as any);
      const createArchiveSpy = vi
        .spyOn(backupSandbox.client.backup, 'createArchive')
        .mockResolvedValue({
          success: true,
          sizeBytes: 42,
          archivePath: '/var/backups/mock.sqsh'
        });
      vi.spyOn(backupSandbox as any, 'uploadBackupPresigned').mockResolvedValue(
        undefined
      );
      vi.spyOn(backupSandbox as any, 'execWithSession').mockResolvedValue({
        stdout: '',
        stderr: '',
        exitCode: 0
      });

      await backupSandbox.createBackup({
        dir: '/app/project',
        excludes: ['**/node_modules/.cache', '**/.next/cache', 'dist/**', '**']
      });

      expect(createArchiveSpy).toHaveBeenCalledWith(
        '/app/project',
        expect.stringMatching(/^\/var\/backups\/.+\.sqsh$/),
        expect.stringMatching(/^__sandbox_backup_/),
        {
          gitignore: false,
          excludes: ['node_modules/.cache', '.next/cache', 'dist'],
          compression: {
            format: 'lz4',
            threads: 8
          }
        }
      );
    });

    it('should reject unsupported backup compression before calling the container', async () => {
      const { backupSandbox } = await createBackupSandbox();
      const createArchiveSpy = vi.spyOn(
        backupSandbox.client.backup,
        'createArchive'
      );

      await expect(
        backupSandbox.createBackup({
          dir: '/app/project',
          compression: {
            format: 'brotli' as unknown as 'gzip'
          }
        })
      ).rejects.toThrow(
        /BackupOptions\.compression\.format must be one of: gzip, lz4, zstd/
      );

      expect(createArchiveSpy).not.toHaveBeenCalled();
    });

    it('should reject invalid backup compression thread count before calling the container', async () => {
      const { backupSandbox } = await createBackupSandbox();
      const createArchiveSpy = vi.spyOn(
        backupSandbox.client.backup,
        'createArchive'
      );

      await expect(
        backupSandbox.createBackup({
          dir: '/app/project',
          compression: {
            threads: 0
          }
        })
      ).rejects.toThrow(
        /BackupOptions\.compression\.threads must be a positive integer/
      );

      expect(createArchiveSpy).not.toHaveBeenCalled();
    });

    it('should allow restoring a backup into /app', async () => {
      const { backupSandbox, bucket } = await createBackupSandbox();
      const backupId = crypto.randomUUID();

      bucket.get.mockResolvedValue({
        json: vi.fn().mockResolvedValue({
          ttl: 259200,
          createdAt: new Date().toISOString(),
          dir: '/app/project'
        })
      });
      bucket.head.mockResolvedValue({ size: 42 });

      vi.spyOn(backupSandbox.client.utils, 'createSession').mockResolvedValue({
        success: true,
        id: 'backup-session',
        message: 'Created'
      } as any);
      vi.spyOn(backupSandbox.client.utils, 'deleteSession').mockResolvedValue({
        success: true,
        id: 'backup-session',
        message: 'Deleted'
      } as any);
      const restoreArchiveSpy = vi
        .spyOn(backupSandbox.client.backup, 'restoreArchive')
        .mockResolvedValue({ success: true, dir: '/app/project' });
      const downloadBackupParallelSpy = vi
        .spyOn(backupSandbox as any, 'downloadBackupParallel')
        .mockResolvedValue(undefined);
      vi.spyOn(backupSandbox as any, 'execWithSession').mockResolvedValue({
        stdout: '0',
        stderr: '',
        exitCode: 0
      });

      const result = await backupSandbox.restoreBackup({
        id: backupId,
        dir: '/app/project'
      });

      expect(result).toEqual({
        success: true,
        dir: '/app/project',
        id: backupId
      });
      expect(restoreArchiveSpy).toHaveBeenCalledWith(
        '/app/project',
        `/var/backups/${backupId}.sqsh`,
        expect.stringMatching(/^__sandbox_backup_/)
      );
      expect(downloadBackupParallelSpy).toHaveBeenCalledWith(
        `/var/backups/${backupId}.sqsh`,
        `backups/${backupId}/data.sqsh`,
        42,
        backupId,
        '/app/project',
        expect.stringMatching(/^__sandbox_backup_/)
      );
    });

    it('should write parallel restore ranges directly into the temp archive', async () => {
      const { backupSandbox } = await createBackupSandbox();
      const expectedSize = 16 * 1024 * 1024;
      const execWithSessionSpy = vi
        .spyOn(backupSandbox as any, 'execWithSession')
        .mockResolvedValueOnce({ stdout: '', stderr: '', exitCode: 0 })
        .mockResolvedValueOnce({ stdout: '', stderr: '', exitCode: 0 })
        .mockResolvedValueOnce({
          stdout: String(expectedSize),
          stderr: '',
          exitCode: 0
        })
        .mockResolvedValueOnce({ stdout: '', stderr: '', exitCode: 0 });
      vi.spyOn(
        backupSandbox as any,
        'generatePresignedGetURL'
      ).mockResolvedValue('https://example.com/archive');

      await (backupSandbox as any).downloadBackupParallel(
        '/var/backups/test.sqsh',
        'backups/test/data.sqsh',
        expectedSize,
        'test-backup-id',
        '/app/project',
        'backup-session'
      );

      const downloadCommand = execWithSessionSpy.mock.calls[1][0] as string;
      expect(downloadCommand).toContain(
        "truncate -s 16777216 '/var/backups/test.sqsh.tmp'"
      );
      expect(downloadCommand).toContain("of='/var/backups/test.sqsh.tmp'");
      expect(downloadCommand).toContain('oflag=seek_bytes');
      expect(downloadCommand).toContain('conv=notrunc');
      expect(downloadCommand).toContain('(set -o pipefail; curl -sSf');
      expect(downloadCommand).not.toContain('cat ');
      expect(downloadCommand).not.toContain('.part0.tmp');
    });

    it('should reject unsupported backup roots before calling the container', async () => {
      const { backupSandbox } = await createBackupSandbox();
      const createArchiveSpy = vi.spyOn(
        backupSandbox.client.backup,
        'createArchive'
      );

      await expect(
        backupSandbox.createBackup({ dir: '/opt/project' })
      ).rejects.toThrow(
        /BackupOptions\.dir must be inside one of the supported backup roots/
      );

      expect(createArchiveSpy).not.toHaveBeenCalled();
    });
  });

  describe('transport configuration', () => {
    it('defaults to http transport', async () => {
      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      expect((sandbox as any).transport).toBe('http');
      expect(sandbox.client.getTransportMode()).toBe('http');
    });

    it('reads websocket transport from SANDBOX_TRANSPORT env var', async () => {
      const wsCtx = {
        ...mockCtx,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          ),
        storage: {
          get: vi.fn().mockResolvedValue(null),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any
      };

      const instance = new Sandbox(
        wsCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        { SANDBOX_TRANSPORT: 'websocket' }
      );

      await vi.waitFor(() => {
        expect(wsCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      expect((instance as any).transport).toBe('websocket');
      expect(instance.client.getTransportMode()).toBe('websocket');
    });

    it('setTransport switches from http to websocket', async () => {
      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      expect((sandbox as any).transport).toBe('http');

      await sandbox.setTransport('websocket');

      expect((sandbox as any).transport).toBe('websocket');
      expect(sandbox.client.getTransportMode()).toBe('websocket');
    });

    it('setTransport switches from websocket to http', async () => {
      const wsCtx = {
        ...mockCtx,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          ),
        storage: {
          get: vi.fn().mockResolvedValue(null),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any
      };

      const instance = new Sandbox(
        wsCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        { SANDBOX_TRANSPORT: 'websocket' }
      );

      await vi.waitFor(() => {
        expect(wsCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      expect((instance as any).transport).toBe('websocket');

      await instance.setTransport('http');

      expect((instance as any).transport).toBe('http');
      expect(instance.client.getTransportMode()).toBe('http');
    });

    it('setTransport is a no-op when transport has been stored and value is unchanged', async () => {
      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      // First call persists (hasStoredTransport is false)
      await sandbox.setTransport('http');
      const putCallsAfterFirst = mockCtx.storage.put.mock.calls.length;
      const clientBefore = sandbox.client;

      // Second identical call is a no-op
      await sandbox.setTransport('http');

      expect(mockCtx.storage.put.mock.calls.length).toBe(putCallsAfterFirst);
      expect(sandbox.client).toBe(clientBefore);
    });

    it('setTransport recreates the client with new transport', async () => {
      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      const clientBefore = sandbox.client;

      await sandbox.setTransport('websocket');

      // Client should be a new instance
      expect(sandbox.client).not.toBe(clientBefore);
    });

    it('setTransport recreates the CodeInterpreter so it uses the new client', async () => {
      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      const interpreterBefore = (sandbox as any).codeInterpreter;

      await sandbox.setTransport('websocket');

      const interpreterAfter = (sandbox as any).codeInterpreter;
      expect(interpreterAfter).not.toBe(interpreterBefore);
    });

    it('setTransport disconnects the previous client', async () => {
      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      const previousClient = sandbox.client;
      const disconnectSpy = vi.spyOn(previousClient, 'disconnect');

      await sandbox.setTransport('websocket');

      expect(disconnectSpy).toHaveBeenCalledOnce();
    });

    it('persists transport to storage before updating in-memory state', async () => {
      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      await sandbox.setTransport('websocket');

      expect(mockCtx.storage.put).toHaveBeenCalledWith(
        'transport',
        'websocket'
      );
    });

    it('persists on first explicit call even when value matches env-derived default', async () => {
      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      // Default transport is 'http'; calling setTransport('http') must still persist
      await sandbox.setTransport('http');

      expect(mockCtx.storage.put).toHaveBeenCalledWith('transport', 'http');

      // Second identical call is a no-op
      const putCallsBefore = mockCtx.storage.put.mock.calls.length;
      await sandbox.setTransport('http');
      expect(mockCtx.storage.put.mock.calls.length).toBe(putCallsBefore);
    });

    it('restores transport from storage on cold start, overriding env var', async () => {
      const coldCtx = {
        ...mockCtx,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          ),
        storage: {
          get: vi.fn().mockImplementation(async (key: string) => {
            if (key === 'transport') return 'websocket';
            return null;
          }),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any
      };

      // Env says 'http' but storage says 'websocket'
      const instance = new Sandbox(
        coldCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        {}
      );

      await vi.waitFor(() => {
        expect(coldCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });
      await Promise.all(
        (coldCtx.blockConcurrencyWhile as any).mock.results.map(
          (r: { value: unknown }) => r.value
        )
      );

      expect((instance as any).transport).toBe('websocket');
      expect((instance as any).hasStoredTransport).toBe(true);
      expect(instance.client.getTransportMode()).toBe('websocket');
    });

    it('reads rpc transport from SANDBOX_TRANSPORT env var', async () => {
      const rpcCtx = {
        ...mockCtx,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          ),
        storage: {
          get: vi.fn().mockResolvedValue(null),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any
      };

      const instance = new Sandbox(
        rpcCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        { SANDBOX_TRANSPORT: 'rpc' }
      );

      await vi.waitFor(() => {
        expect(rpcCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      expect((instance as any).transport).toBe('rpc');
      expect(instance.client.getTransportMode()).toBe('rpc');
    });

    it('setTransport switches from http to rpc', async () => {
      await vi.waitFor(() => {
        expect(mockCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      expect((sandbox as any).transport).toBe('http');

      await sandbox.setTransport('rpc');

      expect((sandbox as any).transport).toBe('rpc');
      expect(sandbox.client.getTransportMode()).toBe('rpc');
    });

    it('setTransport switches from rpc to http', async () => {
      const rpcCtx = {
        ...mockCtx,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          ),
        storage: {
          get: vi.fn().mockResolvedValue(null),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any
      };

      const instance = new Sandbox(
        rpcCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        { SANDBOX_TRANSPORT: 'rpc' }
      );

      await vi.waitFor(() => {
        expect(rpcCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });

      expect((instance as any).transport).toBe('rpc');

      await instance.setTransport('http');

      expect((instance as any).transport).toBe('http');
      expect(instance.client.getTransportMode()).toBe('http');
    });

    it('restores rpc transport from storage on cold start', async () => {
      const coldCtx = {
        ...mockCtx,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          ),
        storage: {
          get: vi.fn().mockImplementation(async (key: string) => {
            if (key === 'transport') return 'rpc';
            return null;
          }),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any
      };

      const instance = new Sandbox(
        coldCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        {}
      );

      await vi.waitFor(() => {
        expect(coldCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });
      await Promise.all(
        (coldCtx.blockConcurrencyWhile as any).mock.results.map(
          (r: { value: unknown }) => r.value
        )
      );

      expect((instance as any).transport).toBe('rpc');
      expect((instance as any).hasStoredTransport).toBe(true);
      expect(instance.client.getTransportMode()).toBe('rpc');
    });

    it('storage restore does not override env-derived rpc with stored http', async () => {
      const coldCtx = {
        ...mockCtx,
        blockConcurrencyWhile: vi
          .fn()
          .mockImplementation(
            <T>(callback: () => Promise<T>): Promise<T> => callback()
          ),
        storage: {
          get: vi.fn().mockImplementation(async (key: string) => {
            // Storage has 'http' but env says 'rpc'
            if (key === 'transport') return 'http';
            return null;
          }),
          put: vi.fn().mockResolvedValue(undefined),
          delete: vi.fn().mockResolvedValue(undefined),
          list: vi.fn().mockResolvedValue(new Map())
        } as any
      };

      const instance = new Sandbox(
        coldCtx as unknown as ConstructorParameters<typeof Sandbox>[0],
        { SANDBOX_TRANSPORT: 'rpc' }
      );

      await vi.waitFor(() => {
        expect(coldCtx.blockConcurrencyWhile).toHaveBeenCalled();
      });
      await Promise.all(
        (coldCtx.blockConcurrencyWhile as any).mock.results.map(
          (r: { value: unknown }) => r.value
        )
      );

      // Storage says 'http' which differs from env 'rpc', so storage wins
      expect((instance as any).transport).toBe('http');
      expect((instance as any).hasStoredTransport).toBe(true);
    });
  });

  describe('destroy() coalescing', () => {
    /**
     * Stub the parent Container.destroy() with a caller-controlled promise so
     * we can observe how concurrent destroy() calls behave while the first
     * one is still in flight.
     */
    function stubSuperDestroy(): {
      resolve: () => void;
      reject: (err: Error) => void;
      calls: () => number;
    } {
      mockCtx.container.running = false;
      let resolve: () => void = () => {};
      let reject: (err: Error) => void = () => {};
      let calls = 0;
      const parent = Object.getPrototypeOf(Object.getPrototypeOf(sandbox)) as {
        destroy: () => Promise<void>;
      };
      parent.destroy = vi.fn().mockImplementation(
        () =>
          new Promise<void>((res, rej) => {
            calls++;
            resolve = res;
            reject = rej;
          })
      );
      return {
        resolve: () => resolve(),
        reject: (err) => reject(err),
        calls: () => calls
      };
    }

    it('coalesces concurrent destroy() calls onto a single teardown', async () => {
      const superDestroy = stubSuperDestroy();

      const first = sandbox.destroy();
      const second = sandbox.destroy();
      const third = sandbox.destroy();

      // All three callers are awaiting the same underlying work; the parent
      // container destroy must only be invoked once.
      await vi.waitFor(() => expect(superDestroy.calls()).toBe(1));

      superDestroy.resolve();
      await expect(Promise.all([first, second, third])).resolves.toEqual([
        undefined,
        undefined,
        undefined
      ]);
    });

    it('propagates the same rejection to all coalesced callers', async () => {
      const superDestroy = stubSuperDestroy();
      const first = sandbox.destroy();
      const second = sandbox.destroy();

      await vi.waitFor(() => expect(superDestroy.calls()).toBe(1));
      const firstExpectation = expect(first).rejects.toThrow(
        'container teardown failed'
      );
      const secondExpectation = expect(second).rejects.toThrow(
        'container teardown failed'
      );
      superDestroy.reject(new Error('container teardown failed'));

      await firstExpectation;
      await secondExpectation;
    });

    it('runs a fresh teardown for a later destroy() after the previous one settles', async () => {
      const first = stubSuperDestroy();
      const firstCall = sandbox.destroy();
      await vi.waitFor(() => expect(first.calls()).toBe(1));
      first.resolve();
      await firstCall;

      // Re-stub to track the second teardown independently.
      const second = stubSuperDestroy();
      const secondCall = sandbox.destroy();
      await vi.waitFor(() => expect(second.calls()).toBe(1));
      second.resolve();
      await secondCall;
    });
  });

  describe('mountBucket FUSE verification', () => {
    const mountOptions = {
      endpoint: 'https://acct.r2.cloudflarestorage.com',
      credentials: {
        accessKeyId: 'AKID',
        secretAccessKey: 'SECRET'
      }
    };

    /**
     * The mount + verification flow runs as a single in-container script.
     * Match it by the `s3fs ` prefix inside the script body and return the
     * exit code the caller would see for each scenario.
     */
    function mockMountScript(result: {
      exitCode: number;
      stdout?: string;
      stderr?: string;
    }) {
      vi.mocked(sandbox.client.commands.execute).mockImplementation(
        async (command: string) => {
          const base = {
            success: true,
            command,
            timestamp: new Date().toISOString()
          };
          if (command.includes('s3fs ') && command.includes('mountpoint -q')) {
            return {
              ...base,
              stdout: '',
              stderr: '',
              ...result
            } as any;
          }
          return { ...base, exitCode: 0, stdout: '', stderr: '' } as any;
        }
      );
    }

    it('succeeds when the mount script reports the mount is live', async () => {
      mockMountScript({ exitCode: 0 });

      await expect(
        sandbox.mountBucket('my-bucket', '/mnt/data', mountOptions)
      ).resolves.toBeUndefined();
    });

    it('throws when the s3fs parent exits non-zero', async () => {
      mockMountScript({ exitCode: 2, stdout: 'fuse: bad mount point' });

      await expect(
        sandbox.mountBucket('my-bucket', '/mnt/data', mountOptions)
      ).rejects.toThrow('S3FS mount failed: fuse: bad mount point');
    });

    it('throws with the s3fs log tail when the mount never appears', async () => {
      mockMountScript({
        exitCode: 3,
        stdout: '[ERR] check_bucket_access: 403 AccessDenied'
      });

      const err = await sandbox
        .mountBucket('my-bucket', '/mnt/data2', mountOptions)
        .catch((e: Error) => e);

      expect(err).toBeInstanceOf(Error);
      expect(err!.message).toMatch(/FUSE filesystem never appeared/);
      expect(err!.message).toMatch(/403 AccessDenied/);
      expect((sandbox as any).activeMounts.has('/mnt/data2')).toBe(false);
    });

    it('unmounts a late-arriving FUSE mount when the script reports timeout', async () => {
      // Race: the script polls 60x for `mountpoint -q` and exits 3 when none
      // succeed, but s3fs is daemonised and can complete the mount between
      // the last poll and our cleanup. The failure path must unmount that
      // mount instead of leaking it.
      const issuedCommands: string[] = [];
      vi.mocked(sandbox.client.commands.execute).mockImplementation(
        async (command: string) => {
          issuedCommands.push(command);
          const base = {
            success: true,
            command,
            timestamp: new Date().toISOString()
          };
          if (command.includes('s3fs ') && command.includes('mountpoint -q')) {
            return {
              ...base,
              exitCode: 3,
              stdout: 'mount took too long',
              stderr: ''
            } as any;
          }
          return { ...base, exitCode: 0, stdout: '', stderr: '' } as any;
        }
      );

      const err = await sandbox
        .mountBucket('my-bucket', '/mnt/late', mountOptions)
        .catch((e: Error) => e);

      expect(err).toBeInstanceOf(Error);
      expect((sandbox as any).activeMounts.has('/mnt/late')).toBe(false);
      // The cleanup path must issue an unmount conditional on `mountpoint -q`,
      // so a late-arriving FUSE mount is torn down before we drop the entry.
      expect(
        issuedCommands.some(
          (c) =>
            c.includes('mountpoint -q') &&
            c.includes('fusermount -u') &&
            c.includes('/mnt/late')
        )
      ).toBe(true);
    });
  });
});

// ---------------------------------------------------------------------------
// Sandbox.getProcess() — behaviour across HTTP and RPC transports
// ---------------------------------------------------------------------------

describe('Sandbox.getProcess()', () => {
  async function makeSandbox(transport: 'http' | 'rpc') {
    const ctx = {
      storage: {
        get: vi.fn().mockResolvedValue(null),
        put: vi.fn().mockResolvedValue(undefined),
        delete: vi.fn().mockResolvedValue(undefined),
        list: vi.fn().mockResolvedValue(new Map())
      } as any,
      blockConcurrencyWhile: vi
        .fn()
        .mockImplementation(<T>(cb: () => Promise<T>) => cb()),
      waitUntil: vi.fn(),
      id: { toString: () => 'test-id', equals: vi.fn(), name: 'test' } as any
    };
    const env = transport === 'rpc' ? { SANDBOX_TRANSPORT: 'rpc' } : {};
    const sb = new Sandbox(ctx as any, env);
    await vi.waitFor(() =>
      expect(ctx.blockConcurrencyWhile).toHaveBeenCalled()
    );
    // For RPC transport, sb.client is a ContainerControlClient whose sub-stubs
    // are capnweb Proxies that reject vi.spyOn. Replace the whole client with a
    // plain mock object after construction — the individual tests fill in the
    // methods they need.
    if (transport === 'rpc') {
      (sb as any).client = {
        getTransportMode: () => 'rpc',
        utils: {
          createSession: vi.fn().mockResolvedValue({
            success: true,
            id: 'default',
            message: 'ok'
          } as any)
        },
        processes: {}
      };
    } else {
      vi.spyOn(sb.client.utils, 'createSession').mockResolvedValue({
        success: true,
        id: 'default',
        message: 'ok'
      } as any);
    }
    return sb;
  }

  it('HTTP: response with no process field returns null', async () => {
    const sb = await makeSandbox('http');
    vi.spyOn(sb.client.processes, 'getProcess').mockResolvedValue({
      success: true,
      process: undefined,
      timestamp: ''
    } as any);
    expect(await sb.getProcess('x')).toBeNull();
  });

  it('RPC: thrown ProcessNotFoundError returns null', async () => {
    const sb = await makeSandbox('rpc');
    (sb.client.processes as any).getProcess = vi.fn().mockRejectedValue(
      new ProcessNotFoundError({
        code: 'PROCESS_NOT_FOUND',
        message: 'Process x not found',
        context: { processId: 'x' },
        httpStatus: 404,
        timestamp: ''
      } as any)
    );
    expect(await sb.getProcess('x')).toBeNull();
  });
});
