/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { ShellExecutionService } from '../services/shellExecutionService.js';
import {
  ListBackgroundProcessesTool,
  ReadBackgroundOutputTool,
} from './shellBackgroundTools.js';
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
import { NoopSandboxManager } from '../services/sandboxManager.js';
import type { AgentLoopContext } from '../config/agent-loop-context.js';
import os from 'node:os';
import * as fs from 'node:fs';
import * as path from 'node:path';

// Integration test simulating model interaction cycle
describe('Background Tools Integration', () => {
  const bus = createMockMessageBus();
  let listTool: ListBackgroundProcessesTool;
  let readTool: ReadBackgroundOutputTool;
  let tempRootDir: string;

  beforeEach(() => {
    vi.clearAllMocks();
    const mockContext = {
      config: { getSessionId: () => 'default' },
    } as unknown as AgentLoopContext;
    listTool = new ListBackgroundProcessesTool(mockContext, bus);
    readTool = new ReadBackgroundOutputTool(mockContext, bus);

    tempRootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'shell-bg-test-'));

    // Clear history to avoid state leakage from previous runs
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (ShellExecutionService as any).backgroundProcessHistory.clear();
  });

  afterEach(() => {
    if (tempRootDir && fs.existsSync(tempRootDir)) {
      fs.rmSync(tempRootDir, { recursive: true, force: true });
    }
  });

  it('should support interaction cycle: start background -> list -> read logs', async () => {
    const controller = new AbortController();

    // 1. Start a backgroundable process
    // We use node to print continuous logs until killed
    const scriptPath = path.join(tempRootDir, 'log.js');
    fs.writeFileSync(
      scriptPath,
      "setInterval(() => console.log('Log line'), 100);",
    );

    // Using 'node' directly avoids cross-platform shell quoting issues with absolute paths.
    const commandString = `node "${scriptPath}"`;

    const realHandle = await ShellExecutionService.execute(
      commandString,
      process.cwd(),
      () => {},
      controller.signal,
      true,
      {
        originalCommand: 'node continuous_log',
        sessionId: 'default',
        sanitizationConfig: {
          allowedEnvironmentVariables: [],
          blockedEnvironmentVariables: [],
          enableEnvironmentVariableRedaction: false,
        },
        sandboxManager: new NoopSandboxManager(),
      },
    );

    const pid = realHandle.pid;
    if (pid === undefined) {
      throw new Error('pid is undefined');
    }
    expect(pid).toBeGreaterThan(0);

    // 2. Simulate model triggering background operations
    ShellExecutionService.background(pid, 'default', 'node continuous_log');

    // 3. Model decides to inspect list
    const listInvocation = listTool.build({});
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (listInvocation as any).context = {
      config: { getSessionId: () => 'default' },
    };
    const listResult = await listInvocation.execute({
      abortSignal: new AbortController().signal,
    });

    expect(listResult.llmContent).toContain(
      `[PID ${pid}] RUNNING: \`node continuous_log\``,
    );

    // 4. Give it time to write output to interval
    await new Promise((resolve) => setTimeout(resolve, 2000));

    // 5. Model decides to read logs
    const readInvocation = readTool.build({ pid, lines: 2 });
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (readInvocation as any).context = {
      config: { getSessionId: () => 'default' },
    };
    const readResult = await readInvocation.execute({
      abortSignal: new AbortController().signal,
    });

    expect(readResult.llmContent).toContain('Showing last');
    expect(readResult.llmContent).toContain('Log line');

    // Cleanup
    await ShellExecutionService.kill(pid);
    controller.abort();
  });
});
