import { describe, expect, it } from 'vitest';
import { SDK_PROVIDER_DEFINITIONS } from '../adapter/SdkProviderSpi.js';
import { buildRuntimeEnv, createTerminalOutputDecoder, decodeTerminalOutputChunk, formatTerminalPrompt, formatSdkOutputEvent, normalizeTerminalCommandInput, parseTerminalDirectoryChangeTarget, resolveTerminalOutputEncoding, resolveSdkProviderId, } from './terminal.js';
describe('terminal route validation', () => {
    it('requires agentId and workspacePath for start', () => {
        const validateStartRequest = (body) => {
            if (!body.agentId || !body.workspacePath)
                return { valid: false, error: 'agentId and workspacePath are required' };
            return { valid: true };
        };
        expect(validateStartRequest({}).valid).toBe(false);
        expect(validateStartRequest({ agentId: 'agent-1', workspacePath: '/path' }).valid).toBe(true);
    });
    it('uses SDK default command when not provided', () => {
        const resolveCommand = (command) => command || 'claude';
        expect(resolveCommand(undefined)).toBe('claude');
        expect(resolveCommand('codex')).toBe('codex');
    });
    it('uses SDK launch mode for unsupported mode values', () => {
        const resolveLaunchMode = (mode) => (mode === 'sdk' ? 'sdk' : 'sdk');
        expect(resolveLaunchMode(undefined)).toBe('sdk');
        expect(resolveLaunchMode('sdk')).toBe('sdk');
        expect(resolveLaunchMode('unsupported')).toBe('sdk');
    });
    it('keeps shortcut mode available for manual key injection', () => {
        const buildInputResponse = (mode) => mode === 'shortcut' ? { ok: true, mode: 'shortcut' } : { ok: true, mode: 'sdk' };
        expect(buildInputResponse('shortcut')).toEqual({ ok: true, mode: 'shortcut' });
        expect(buildInputResponse(undefined)).toEqual({ ok: true, mode: 'sdk' });
    });
    it('does not downgrade unsupported shortcut input to normal SDK messages', () => {
        const buildShortcutFallbackResponse = (hasShortcutSupport) => (hasShortcutSupport
            ? { status: 200, body: { ok: true, mode: 'shortcut' } }
            : { status: 400, body: { error: 'shortcut input is not supported by this SDK provider' } });
        expect(buildShortcutFallbackResponse(false)).toEqual({
            status: 400,
            body: { error: 'shortcut input is not supported by this SDK provider' },
        });
    });
    it('requires sessionId and input for SDK terminal input', () => {
        const validateInputRequest = (body) => {
            if (!body.sessionId || body.input === undefined || body.input === null) {
                return { valid: false, error: 'sessionId and input are required' };
            }
            return { valid: true };
        };
        expect(validateInputRequest({}).valid).toBe(false);
        expect(validateInputRequest({ sessionId: 'session-1', input: 'continue' }).valid).toBe(true);
    });
    it('keeps resize contract as SDK no-op compatibility endpoint', () => {
        const buildResizeResponse = (cols, rows) => ({ ok: true, mode: 'sdk', noop: true, cols, rows });
        expect(buildResizeResponse(120, 30)).toEqual({ ok: true, mode: 'sdk', noop: true, cols: 120, rows: 30 });
    });
    it('requires sessionId for stop', () => {
        const validateStopRequest = (body) => {
            if (!body.sessionId)
                return { valid: false, error: 'sessionId is required' };
            return { valid: true };
        };
        expect(validateStopRequest({}).valid).toBe(false);
        expect(validateStopRequest({ sessionId: 'session-1' }).valid).toBe(true);
    });
});
describe('terminal configuration', () => {
    it('builds correct SDK runtime environment', () => {
        const env = buildRuntimeEnv('agent-123', undefined, { PATH: '/usr/bin' });
        expect(env.AWS_AGENT_ID).toBe('agent-123');
        expect(env.AWS_MCP_LAUNCH_BINDING_ID).toBeUndefined();
        expect(env.AWS_MCP_CLAIM_LAUNCH_BINDING).toBe('true');
    });
    it('merges launch env overrides without allowing agent identity override', () => {
        const env = buildRuntimeEnv('agent-123', '/workspace', { PATH: '/usr/bin' }, {
            ANTHROPIC_BASE_URL: ' https://api.example.com ',
            ANTHROPIC_AUTH_TOKEN: 'secret-token',
            ANTHROPIC_MODEL: 'claude-sonnet-4-5',
            AWS_AGENT_ID: 'malicious-agent',
            EMPTY_VALUE: '   ',
        });
        expect(env.ANTHROPIC_BASE_URL).toBe('https://api.example.com');
        expect(env.ANTHROPIC_AUTH_TOKEN).toBe('secret-token');
        expect(env.ANTHROPIC_MODEL).toBe('claude-sonnet-4-5');
        expect(env.EMPTY_VALUE).toBeUndefined();
        expect(env.AWS_AGENT_ID).toBe('agent-123');
        expect(env.AWS_WORKSPACE_PATH).toBe('/workspace');
    });
});
describe('terminal command helpers', () => {
    it('normalizes carriage-return terminal input into executable command text', () => {
        expect(normalizeTerminalCommandInput('npm test\r')).toBe('npm test');
        expect(normalizeTerminalCommandInput(' pwd \n')).toBe('pwd');
    });
    it('parses cd commands without treating other shell commands as directory changes', () => {
        expect(parseTerminalDirectoryChangeTarget('cd')).toBeNull();
        expect(parseTerminalDirectoryChangeTarget('cd /d "D:\\code\\repo"')).toBe('D:\\code\\repo');
        expect(parseTerminalDirectoryChangeTarget('npm run build')).toBeUndefined();
    });
    it('formats the working directory prompt as terminal output text', () => {
        expect(formatTerminalPrompt('D:\\code\\repo')).toBe('\x1b[36mD:\\code\\repo>\x1b[0m ');
    });
    it('defaults Windows terminal command output to GB18030 decoding', () => {
        expect(resolveTerminalOutputEncoding('win32', {})).toBe('gb18030');
        expect(resolveTerminalOutputEncoding('linux', {})).toBe('utf-8');
        expect(resolveTerminalOutputEncoding('win32', { AWS_TERMINAL_OUTPUT_ENCODING: 'utf-8' })).toBe('utf-8');
    });
    it('decodes GB18030 Chinese output across chunk boundaries', () => {
        const decoder = createTerminalOutputDecoder('gb18030');
        const first = decodeTerminalOutputChunk(decoder, Buffer.from([0xd6]));
        const second = decodeTerminalOutputChunk(decoder, Buffer.from([0xd0, 0xce, 0xc4]));
        const flushed = decodeTerminalOutputChunk(decoder, undefined, true);
        expect(`${first}${second}${flushed}`).toBe('中文');
    });
});
describe('resolveSdkProviderId', () => {
    it('exposes codex through the SDK provider SPI', () => {
        expect(SDK_PROVIDER_DEFINITIONS.map((definition) => definition.runtimeMode)).toContain('codex');
        expect(SDK_PROVIDER_DEFINITIONS.find((definition) => definition.runtimeMode === 'codex')).toMatchObject({
            providerId: 'codex',
            defaultCommand: 'codex',
        });
    });
    it('resolves codex commands to codex provider', () => {
        expect(resolveSdkProviderId('codex')).toBe('codex');
        expect(resolveSdkProviderId('npx codex --model gpt-5-codex')).toBe('codex');
    });
    it('resolves opencode commands to opencode provider', () => {
        expect(resolveSdkProviderId('opencode')).toBe('opencode');
    });
    it('falls back to claude-code for claude or unknown commands', () => {
        expect(resolveSdkProviderId('claude')).toBe('claude-code');
        expect(resolveSdkProviderId('custom-binary')).toBe('claude-code');
        expect(resolveSdkProviderId(undefined)).toBe('claude-code');
    });
});
describe('SDK output forwarding', () => {
    it('formats text and thinking provider events for the terminal output callback path', () => {
        const textEvent = {
            type: 'text_delta',
            sessionId: 'session-1',
            timestamp: new Date(0).toISOString(),
            data: { text: 'hello' },
        };
        const thinkingEvent = {
            type: 'thinking',
            sessionId: 'session-1',
            timestamp: new Date(0).toISOString(),
            data: { text: 'thinking...' },
        };
        expect(formatSdkOutputEvent(textEvent)).toBe('hello');
        expect(formatSdkOutputEvent(thinkingEvent)).toBe('thinking...');
    });
    it('formats SDK errors as terminal output and ignores non-output events', () => {
        const errorEvent = {
            type: 'error',
            sessionId: 'session-1',
            timestamp: new Date(0).toISOString(),
            data: { text: 'boom' },
        };
        const toolEvent = {
            type: 'tool_use_start',
            sessionId: 'session-1',
            timestamp: new Date(0).toISOString(),
            data: { toolName: 'read_file' },
        };
        expect(formatSdkOutputEvent(errorEvent)).toBe('\r\n[SDK Error] boom\r\n');
        expect(formatSdkOutputEvent(toolEvent)).toBeUndefined();
    });
});
