import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { nextTick, reactive } from 'vue'
import { buildCliCreateAgentRuntimeOptions } from '../src/dimsdk/cli/runtime-host.ts'

const {
  createCliAgentMock,
  createDimcodeContextMock,
  resolveStartupSessionIdMock,
} = vi.hoisted(() => ({
  createCliAgentMock: vi.fn(),
  createDimcodeContextMock: vi.fn(),
  resolveStartupSessionIdMock: vi.fn(),
}))

vi.mock('../src/dimsdk/cli/agent.ts', () => ({
  createCliAgent: createCliAgentMock,
}))

vi.mock('../src/dimsdk/cli/context.ts', () => ({
  createDimcodeContext: createDimcodeContextMock,
}))

vi.mock('../src/dimsdk/cli/dimsdk/startup-session.ts', () => ({
  resolveStartupSessionId: resolveStartupSessionIdMock,
}))

const { createDimSdkShellProps } = await import('../src/dimsdk/cli/dimsdk/shell-props.ts')

const tempDirs: string[] = []
const realFetch = globalThis.fetch

function createStore() {
  return reactive({
    setCwd(cwd: string) {
      this.state.context.cwd = cwd
    },
    setEnvVar(key: string, value: string | undefined) {
      if (!value)
        delete this.state.context.env[key]
      else
        this.state.context.env[key] = value
    },
    setFocusFiles(paths: string[]) {
      this.state.context.focusFiles = [...paths]
    },
    state: {
      approval: {
        active: null,
        mode: 'normal' as 'auto' | 'normal' | 'strict',
      },
      config: {
        context_window: 8192,
        interleaved_thinking: false,
        model: '',
        temperature: 0.3,
      },
      context: {
        cwd: '/repo',
        env: {} as Record<string, string | undefined>,
        focusFiles: [] as string[],
        system: 'test',
      },
      lastRequestBudget: null,
      mcp: {
        servers: [] as unknown[],
      },
      messages: [] as unknown[],
      paused: null,
      provider: {
        activeId: '',
        activeModelByProvider: {} as Record<string, string>,
        connections: {} as Record<string, unknown>,
        custom: {
          apiKey: undefined as string | undefined,
          baseUrl: undefined as string | undefined,
          models: [] as Array<{ id: string, name?: string }>,
        },
        customProviders: [] as unknown[],
      },
      runningAgentRound: false,
      sessionId: 'session_live',
      tools: {
        websearch: {
          apiEndpoint: 'https://google.serper.dev/search?format=json',
          apiKey: '',
          numResults: 10,
        },
      },
      ui: {
        greeting: {
          lastSeenDate: '',
        },
        locale: 'en',
        onboarding: {
          lastSeenVersion: '',
        },
        providerFunnel: {
          counts: {} as Record<string, number>,
        },
        shortcuts: {
          commands: 'meta+j',
          newSession: 'meta+n',
          timeline: 'meta+h',
        },
        statusCompact: false,
        theme: {
          mode: 'dark',
        },
        workspaceSelectionByCwd: {} as Record<string, unknown>,
      },
      usage: {
        completion_tokens: 0,
        prompt_tokens: 0,
        total_tokens: 0,
      },
    },
  })
}

afterEach(() => {
  while (tempDirs.length > 0)
    rmSync(tempDirs.pop() as string, { force: true, recursive: true })
  createCliAgentMock.mockReset()
  createDimcodeContextMock.mockReset()
  resolveStartupSessionIdMock.mockReset()
  Object.defineProperty(globalThis, 'fetch', {
    configurable: true,
    value: realFetch,
  })
  vi.restoreAllMocks()
})

describe('dimsdk shell props', () => {
  it('updates web-search capability for the next turn when the key is added mid-conversation', async () => {
    const configDir = mkdtempSync(join(tmpdir(), 'best-agent-dimsdk-shell-props-'))
    tempDirs.push(configDir)
    let capturedTools:
      | {
        websearch?: {
          apiKey?: string | (() => string)
          apiEndpoint?: string | (() => string | undefined)
          numResults?: number | (() => number | undefined)
        }
      }
      | undefined

    createCliAgentMock.mockImplementation((opts: { tools?: typeof capturedTools }) => {
      capturedTools = opts.tools
      return {
        sessions: null,
      }
    })
    createDimcodeContextMock.mockResolvedValue({
      cache: {
        lastSessionId: '',
        settings: {},
        updatedAt: 0,
        version: 1,
      },
      cacheDir: configDir,
      cacheFile: join(configDir, 'cache.json'),
      config: {
        settings: {},
        updatedAt: 0,
        version: 1,
      },
      configFile: join(configDir, 'config.json'),
      context: {
        cwd: '/repo',
        env: {},
        system: 'test system prompt',
      },
      mcpJsonFile: join(configDir, 'mcp.json'),
      mcpServers: [],
      tools: {
        settings: {
          mcpServers: [],
          websearch: {
            apiEndpoint: 'https://google.serper.dev/search?format=json',
            apiKey: '',
            numResults: 10,
          },
        },
        updatedAt: 0,
        version: 1,
      },
      toolsFile: join(configDir, 'tools.json'),
    })
    resolveStartupSessionIdMock.mockResolvedValue(null)

    const props = await createDimSdkShellProps({
      cwd: '/repo',
      env: {},
    })
    const store = createStore()

    props.onStoreCreated(store as never)
    await nextTick()

    expect(capturedTools?.websearch?.apiKey).toBeTypeOf('function')

    const runtimeOptions = buildCliCreateAgentRuntimeOptions({
      context: {
        env: {},
        system: 'test system prompt',
      } as never,
      stateDir: join(configDir, 'state'),
      tools: capturedTools,
    }) as {
      webSearch?: (input: {
        domains?: string[]
        limit: number
        query: string
        recencyDays?: number
      }) => Promise<string>
      webSearchAvailability?: () => boolean
    }

    expect(runtimeOptions.webSearchAvailability?.()).toBe(false)

    store.state.tools.websearch.apiKey = 'serper-live-key'
    store.state.tools.websearch.apiEndpoint = 'https://example.test/search'
    store.state.tools.websearch.numResults = 7
    await nextTick()

    expect(runtimeOptions.webSearchAvailability?.()).toBe(true)

    const fetchMock = vi.fn(async () => ({
      json: async () => ({
        organic: [
          {
            title: 'Live Search Result',
            link: 'https://example.test/docs/live',
            snippet: 'Result after adding the key mid-conversation.',
          },
        ],
      }),
      ok: true,
      status: 200,
      statusText: 'OK',
      text: async () => '',
    }))
    Object.defineProperty(globalThis, 'fetch', {
      configurable: true,
      value: fetchMock,
    })

    const output = await runtimeOptions.webSearch?.({
      limit: 99,
      query: 'best agent live docs',
    })

    expect(fetchMock).toHaveBeenCalledWith(
      'https://example.test/search',
      expect.objectContaining({
        body: JSON.stringify({
          num: 10,
          q: 'best agent live docs',
        }),
        headers: {
          'Content-Type': 'application/json',
          'X-API-KEY': 'serper-live-key',
        },
        method: 'POST',
      }),
    )
    expect(output).toContain('Search results for "best agent live docs" (1 found):')
    expect(output).toContain('1. Live Search Result')
    expect(output).toContain('URL: https://example.test/docs/live')
  })
})
