import { afterEach, describe, expect, it, vi } from 'vitest'

const { createDimSdkShellProps } = vi.hoisted(() => ({
  createDimSdkShellProps: vi.fn(),
}))

vi.mock('../src/dimsdk/cli/dimsdk-app.ts', () => ({
  createDimSdkShellProps,
}))

const { parseExecArgs, runExec } = await import('../src/dimsdk/cli/exec.ts')

function createFakeProc(): {
  proc: NodeJS.Process
  stdout: string[]
  stderr: string[]
} {
  const stdout: string[] = []
  const stderr: string[] = []
  const proc = {
    cwd: () => process.cwd(),
    env: { ...process.env },
    stdin: {
      isTTY: true,
      setEncoding: vi.fn(),
      async *[Symbol.asyncIterator]() {},
    },
    stdout: {
      isTTY: false,
      write: (chunk: string) => {
        stdout.push(String(chunk))
        return true
      },
    },
    stderr: {
      isTTY: false,
      write: (chunk: string) => {
        stderr.push(String(chunk))
        return true
      },
    },
  } as unknown as NodeJS.Process

  return { proc, stdout, stderr }
}

afterEach(() => {
  createDimSdkShellProps.mockReset()
})

describe('exec cli helpers', () => {
  it('parses cwd and session flags before the prompt tail', () => {
    const parsed = parseExecArgs(
      ['--cwd', './apps', '--session', 'session-42', '--', 'hello', 'world'],
      '/repo',
    )

    expect(parsed).toEqual({
      cwd: '/repo/apps',
      promptParts: ['hello', 'world'],
      sessionId: 'session-42',
    })
  })

  it('renders structured tool output and seeds one-shot env flags', async () => {
    const { proc, stdout, stderr } = createFakeProc()

    const run = vi.fn(async function* () {
      yield {
        type: 'tool.started',
        toolCallId: 'call-1',
        toolName: 'Read',
        input: {},
      }
      yield {
        type: 'tool.finished',
        toolCallId: 'call-1',
        toolName: 'Read',
        output: [{ text: 'hello from tool' }],
      }
    })

    const ensureSession = vi.fn(async () => ({
      id: 'session-1',
      run,
    }))

    createDimSdkShellProps.mockResolvedValue({
      agent: {
        ensureSession,
        getMode: () => 'plan',
      },
    })

    const exitCode = await runExec(['--session', 'exec-session', 'summarize'], proc)

    expect(exitCode).toBe(0)
    expect(createDimSdkShellProps).toHaveBeenCalledWith({
      cwd: process.cwd(),
      env: expect.objectContaining({
        DIMCODE_FAST_START: '0',
        DIMCODE_SESSION_ID: 'exec-session',
        DIMCODE_TOOL_APPROVALS: 'auto',
      }),
      sessionId: 'exec-session',
    })
    expect(ensureSession).toHaveBeenCalledWith({
      sessionId: 'exec-session',
      cwd: process.cwd(),
      modeId: 'plan',
      toolApprovalMode: 'auto',
    })
    expect(stdout.join('')).toContain('hello from tool')
    expect(stderr.join('')).toContain('[tool] Read running')
    expect(stderr.join('')).toContain('[tool] Read done')
    expect(stderr.join('')).toContain('session_id: session-1')
  })

  it('prints preview text when no settled assistant message arrives', async () => {
    const { proc, stdout } = createFakeProc()

    const run = vi.fn(async function* () {
      yield {
        type: 'assistant.delta',
        delta: 'preview only answer',
      }
    })

    const ensureSession = vi.fn(async () => ({
      id: 'session-preview-only',
      run,
    }))

    createDimSdkShellProps.mockResolvedValue({
      agent: {
        ensureSession,
        getMode: () => 'agent',
      },
    })

    const exitCode = await runExec(['summarize'], proc)

    expect(exitCode).toBe(0)
    expect(stdout.join('')).toBe('preview only answer\n')
  })

  it('dedupes identical preview and settled assistant output', async () => {
    const { proc, stdout } = createFakeProc()

    const run = vi.fn(async function* () {
      yield {
        type: 'assistant.delta',
        delta: 'same final answer',
      }
      yield {
        type: 'assistant.message',
        message: { content: 'same final answer' },
      }
    })

    const ensureSession = vi.fn(async () => ({
      id: 'session-preview-equal-final',
      run,
    }))

    createDimSdkShellProps.mockResolvedValue({
      agent: {
        ensureSession,
        getMode: () => 'agent',
      },
    })

    const exitCode = await runExec(['summarize'], proc)

    expect(exitCode).toBe(0)
    expect(stdout.join('')).toBe('same final answer\n')
  })

  it('replaces preview text with the settled assistant message when they differ', async () => {
    const { proc, stdout } = createFakeProc()

    const run = vi.fn(async function* () {
      yield {
        type: 'assistant.delta',
        delta: 'let me inspect first',
      }
      yield {
        type: 'assistant.message',
        message: { content: '# Final answer\n\nDone.' },
      }
    })

    const ensureSession = vi.fn(async () => ({
      id: 'session-preview-different-final',
      run,
    }))

    createDimSdkShellProps.mockResolvedValue({
      agent: {
        ensureSession,
        getMode: () => 'agent',
      },
    })

    const exitCode = await runExec(['summarize'], proc)

    expect(exitCode).toBe(0)
    expect(stdout.join('')).toBe('# Final answer\n\nDone.\n')
  })
})
