import { describe, expect, it, vi } from 'vitest'
import type { ResolvedRuntimeSettings } from '../runtime-settings-store'
import type { BrowserToolService } from '../browser/browser-model-tools'
import {
  createAgentRuntime,
  createModelProfileRuntime
} from './create-runtime'
import { AgentRuntimeController } from './runtime-controller'

function createBrowserService(): BrowserToolService & {
  dispose: ReturnType<typeof vi.fn>
} {
  return {
    getOrigin: vi.fn(() => undefined),
    navigate: vi.fn(),
    snapshot: vi.fn(),
    click: vi.fn(),
    type: vi.fn(),
    select: vi.fn(),
    back: vi.fn(),
    screenshot: vi.fn(),
    releaseConversation: vi.fn(async () => undefined),
    dispose: vi.fn(async () => undefined)
  }
}

function settings(
  overrides: Partial<ResolvedRuntimeSettings> = {}
): ResolvedRuntimeSettings {
  const defaultModelProfileId =
    '00000000-0000-4000-8000-000000000001'
  return {
    provider: 'model',
    modelBaseUrl: 'http://127.0.0.1:11434/v1',
    modelName: 'qwen3',
    modelProtocol: 'openai-chat-completions',
    modelAuthentication: 'none',
    imageGenerationQuality: 'auto',
    modelProfiles: [
      {
        id: defaultModelProfileId,
        name: '默认模型',
        baseUrl: 'http://127.0.0.1:11434/v1',
        modelName: 'qwen3',
        protocol: 'openai-chat-completions',
        authentication: 'none',
        imageGenerationQuality: 'auto'
      }
    ],
    defaultModelProfileId,
    opencodeBaseUrl: '',
    opencodeEmbedded: false,
    opencodeBinaryPath: '',
    opencodeConfigPath: '',
    continueBinaryPath: '',
    continueConfigPath: '',
    continueMode: 'chat',
    runtimeSandboxMode: 'off',
    subagentSmartRoutingEnabled: false,
    knowledgeEmbeddingEnabled: false,
    knowledgeEmbeddingBaseUrl:
      'http://127.0.0.1:11434/v1/embeddings',
    knowledgeEmbeddingModel: 'nomic-embed-text',
    workspacePath: process.cwd(),
    toolApproval: 'always',
    ...overrides
  }
}

describe('createAgentRuntime model compatibility', () => {
  it('creates an available direct runtime for a no-auth model', async () => {
    const runtime = createAgentRuntime(process.cwd(), settings())

    await expect(runtime.getStatus()).resolves.toMatchObject({
      id: 'model',
      available: true,
      supportsToolExecution: true,
      detail: expect.stringContaining('OpenAI Chat Completions')
    })
    await runtime.dispose()
  })

  it('shares injected browser service without runtime-owned disposal', async () => {
    const browserService = createBrowserService()
    const first = createAgentRuntime(process.cwd(), settings(), {
      browserService
    })
    const second = createAgentRuntime(process.cwd(), settings(), {
      browserService
    })
    const controller = new AgentRuntimeController(first)

    await controller.releaseConversation('conversation-one')
    await controller.replace(second)
    await controller.releaseConversation('conversation-two')
    await controller.dispose()

    expect(browserService.releaseConversation).toHaveBeenNthCalledWith(
      1,
      'conversation-one'
    )
    expect(browserService.releaseConversation).toHaveBeenNthCalledWith(
      2,
      'conversation-two'
    )
    expect(browserService.dispose).not.toHaveBeenCalled()
  })

  it('does not expose the browser service to OpenCode runtimes', async () => {
    const browserService = createBrowserService()
    const runtime = createAgentRuntime(
      process.cwd(),
      settings({
        provider: 'opencode',
        opencodeBaseUrl: 'http://127.0.0.1:4096'
      }),
      { browserService }
    )

    await runtime.releaseConversation?.('opencode-conversation')
    await runtime.dispose()

    expect(browserService.releaseConversation).not.toHaveBeenCalled()
    expect(browserService.dispose).not.toHaveBeenCalled()
  })

  it('treats a blank OpenCode Server as bundled local mode even for legacy false settings', async () => {
    const runtime = createAgentRuntime(
      process.cwd(),
      settings({
        provider: 'opencode',
        opencodeBaseUrl: '',
        opencodeEmbedded: false
      })
    )

    await expect(runtime.getStatus()).resolves.not.toMatchObject({
      detail: '未配置 OpenCode Server'
    })
    await runtime.dispose()
  })

  it.each([
    ['openai-chat-completions', 'none'],
    ['openai-responses', 'api-key']
  ] as const)(
    'accepts an OpenCode %s independent profile',
    async (protocol, authentication) => {
      const runtime = createAgentRuntime(
        process.cwd(),
        settings({
          provider: 'opencode',
          opencodeModelProfile: {
            id: '00000000-0000-4000-8000-000000000031',
            name: 'OpenAI profile',
            baseUrl: 'https://api.example/v1',
            modelName: 'model',
            protocol,
            authentication,
            imageGenerationQuality: 'auto',
            ...(authentication === 'api-key'
              ? { apiKey: 'secret' }
              : {})
          }
        })
      )

      expect(runtime.requiresToolApproval).toBe(false)
      await runtime.dispose()
    }
  )

  it('marks direct image runtimes and rejects them for Agent Runtimes', async () => {
    const imageSettings = settings({
      modelBaseUrl: 'https://bigtoken.ai/v1',
      modelName: 'gpt-image-2',
      modelProtocol: 'openai-images-generations',
      modelAuthentication: 'api-key',
      imageGenerationQuality: 'high',
      apiKey: 'secret',
      modelProfiles: [
        {
          id: '00000000-0000-4000-8000-000000000001',
          name: '默认图像模型',
          baseUrl: 'https://bigtoken.ai/v1',
          modelName: 'gpt-image-2',
          protocol: 'openai-images-generations',
          authentication: 'api-key',
          imageGenerationQuality: 'high',
          apiKey: 'secret'
        }
      ]
    })
    const runtime = createAgentRuntime(process.cwd(), imageSettings)
    await expect(runtime.getStatus()).resolves.toMatchObject({
      capability: 'image-generation'
    })
    await runtime.dispose()

    expect(() =>
      createAgentRuntime(
        process.cwd(),
        settings({
          provider: 'continue',
          continueModelProfile: {
            id: '00000000-0000-4000-8000-000000000032',
            name: 'Image profile',
            baseUrl: 'https://bigtoken.ai/v1',
            modelName: 'gpt-image-2',
            protocol: 'openai-images-generations',
            authentication: 'api-key',
            imageGenerationQuality: 'high',
            apiKey: 'secret'
          }
        })
      )
    ).toThrow('Continue 独立模型连接仅支持')
    expect(() =>
      createAgentRuntime(
        process.cwd(),
        settings({
          provider: 'opencode',
          opencodeModelProfile: {
            id: '00000000-0000-4000-8000-000000000033',
            name: 'Image profile',
            baseUrl: 'https://api.openai.com/v1',
            modelName: 'gpt-image-2',
            protocol: 'openai-images-generations',
            authentication: 'api-key',
            imageGenerationQuality: 'auto',
            apiKey: 'secret'
          }
        })
      )
    ).toThrow('OpenCode 独立模型连接仅支持')
  })

  it('accepts a Continue Responses independent profile', async () => {
    const runtime = createAgentRuntime(
      process.cwd(),
      settings({
        provider: 'continue',
        continueModelProfile: {
          id: '00000000-0000-4000-8000-000000000035',
          name: 'Responses profile',
          baseUrl: 'https://api.example/v1',
          modelName: 'gpt-compatible',
          protocol: 'openai-responses',
          authentication: 'api-key',
          imageGenerationQuality: 'auto',
          apiKey: 'secret'
        }
      })
    )

    expect(runtime.requiresToolApproval).toBe(false)
    await runtime.dispose()
  })

  it('creates a testable runtime for an image model profile', async () => {
    const resolved = settings()
    const runtime = createModelProfileRuntime(
      process.cwd(),
      resolved,
      {
        id: '00000000-0000-4000-8000-000000000034',
        name: 'Image profile',
        baseUrl: 'https://bigtoken.ai/v1',
        modelName: 'gpt-image-2',
        protocol: 'openai-images-generations',
        authentication: 'api-key',
        imageGenerationQuality: 'high',
        apiKey: 'secret'
      }
    )

    await expect(runtime.getStatus()).resolves.toMatchObject({
      id: 'model',
      capability: 'image-generation',
      available: true
    })
    await runtime.dispose()
  })
})
