import test from 'node:test'
import assert from 'node:assert/strict'
import { mkdtempSync, rmSync, writeFileSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { ensureRuntimeAfterAuthLogin, hasRuntimeSensitiveSettingsUpdate, mergeRuntimeProviderModels } from '../apps/desktop/src/main/ipc/app-handlers.ts'
import { clearConfigCaches, getPublicAppConfig } from '../apps/desktop/src/main/config-loader.ts'

test('ensureRuntimeAfterAuthLogin reboots an active runtime after successful sign-in', async () => {
  const calls: string[] = []

  await ensureRuntimeAfterAuthLogin({
    authenticated: true,
    setupComplete: true,
    hasActiveRuntime: true,
    bootRuntime: async () => {
      calls.push('boot')
    },
    rebootRuntime: async () => {
      calls.push('reboot')
    },
  })

  assert.deepEqual(calls, ['reboot'])
})

test('ensureRuntimeAfterAuthLogin boots when sign-in succeeded and no runtime is active', async () => {
  const calls: string[] = []

  await ensureRuntimeAfterAuthLogin({
    authenticated: true,
    setupComplete: true,
    hasActiveRuntime: false,
    bootRuntime: async () => {
      calls.push('boot')
    },
    rebootRuntime: async () => {
      calls.push('reboot')
    },
  })

  assert.deepEqual(calls, ['boot'])
})

test('ensureRuntimeAfterAuthLogin does nothing for incomplete or failed auth flows', async () => {
  const calls: string[] = []

  await ensureRuntimeAfterAuthLogin({
    authenticated: false,
    setupComplete: true,
    hasActiveRuntime: true,
    bootRuntime: async () => {
      calls.push('boot')
    },
    rebootRuntime: async () => {
      calls.push('reboot')
    },
  })

  await ensureRuntimeAfterAuthLogin({
    authenticated: true,
    setupComplete: false,
    hasActiveRuntime: true,
    bootRuntime: async () => {
      calls.push('boot')
    },
    rebootRuntime: async () => {
      calls.push('reboot')
    },
  })

  assert.deepEqual(calls, [])
})

test('small model changes are runtime-sensitive settings updates', () => {
  assert.equal(hasRuntimeSensitiveSettingsUpdate({ selectedSmallModelId: 'openrouter/deepseek/deepseek-v4-flash:free' }), true)
  assert.equal(hasRuntimeSensitiveSettingsUpdate({ selectedSmallModelId: null }), true)
  assert.equal(hasRuntimeSensitiveSettingsUpdate({ workflowDesktopNotifications: false }), false)
})

test('mergeRuntimeProviderModels drops provider defaults absent from the live runtime catalog', () => {
  const tempRoot = mkdtempSync(join(tmpdir(), 'opencowork-provider-runtime-default-'))
  const configPath = join(tempRoot, 'open-cowork.config.json')
  const previousOverride = process.env.OPEN_COWORK_CONFIG_PATH

  writeFileSync(configPath, JSON.stringify({
    providers: {
      available: ['acme-provider'],
      defaultProvider: 'acme-provider',
      defaultModel: null,
      descriptors: {
        'acme-provider': {
          runtime: 'builtin',
          name: 'Acme Provider',
          description: 'Acme provider',
          defaultModel: 'stale-default',
          credentials: [],
          models: [],
        },
      },
    },
  }))

  process.env.OPEN_COWORK_CONFIG_PATH = configPath
  clearConfigCaches()

  try {
    const config = getPublicAppConfig()
    assert.equal(config.providers.available[0]?.defaultModel, 'stale-default')

    const merged = mergeRuntimeProviderModels(config, [{
      id: 'acme-provider',
      models: {
        live: { name: 'Live' },
      },
      connected: true,
    }])
    assert.equal(merged.providers.available[0]?.defaultModel, undefined)
    assert.equal(merged.providers.available[0]?.connected, true)

    const withRuntimeDefault = mergeRuntimeProviderModels(config, [{
      id: 'acme-provider',
      models: {
        live: { name: 'Live' },
      },
      defaultModel: 'live',
    }])
    assert.equal(withRuntimeDefault.providers.available[0]?.defaultModel, 'live')
  } finally {
    if (previousOverride === undefined) delete process.env.OPEN_COWORK_CONFIG_PATH
    else process.env.OPEN_COWORK_CONFIG_PATH = previousOverride
    clearConfigCaches()
    rmSync(tempRoot, { recursive: true, force: true })
  }
})

test('mergeRuntimeProviderModels prefers OpenCode live defaults over app-wide defaults', () => {
  const tempRoot = mkdtempSync(join(tmpdir(), 'opencowork-provider-runtime-default-precedence-'))
  const configPath = join(tempRoot, 'open-cowork.config.json')
  const previousOverride = process.env.OPEN_COWORK_CONFIG_PATH

  writeFileSync(configPath, JSON.stringify({
    providers: {
      available: ['acme-provider'],
      defaultProvider: 'acme-provider',
      defaultModel: 'app-wide',
      descriptors: {
        'acme-provider': {
          runtime: 'builtin',
          name: 'Acme Provider',
          description: 'Acme provider',
          defaultModel: 'stale-local',
          credentials: [],
          models: [
            { id: 'app-wide', name: 'App Wide' },
          ],
        },
      },
    },
  }))

  process.env.OPEN_COWORK_CONFIG_PATH = configPath
  clearConfigCaches()

  try {
    const merged = mergeRuntimeProviderModels(getPublicAppConfig(), [{
      id: 'acme-provider',
      models: {
        'app-wide': { name: 'App Wide' },
        live: { name: 'Live' },
      },
      defaultModel: 'live',
    }])
    assert.equal(merged.providers.available[0]?.defaultModel, 'live')
  } finally {
    if (previousOverride === undefined) delete process.env.OPEN_COWORK_CONFIG_PATH
    else process.env.OPEN_COWORK_CONFIG_PATH = previousOverride
    clearConfigCaches()
    rmSync(tempRoot, { recursive: true, force: true })
  }
})

test('mergeRuntimeProviderModels preserves OpenCode defaults when the runtime omits the model list', () => {
  const tempRoot = mkdtempSync(join(tmpdir(), 'opencowork-provider-runtime-default-no-models-'))
  const configPath = join(tempRoot, 'open-cowork.config.json')
  const previousOverride = process.env.OPEN_COWORK_CONFIG_PATH

  writeFileSync(configPath, JSON.stringify({
    providers: {
      available: ['acme-provider'],
      defaultProvider: 'acme-provider',
      defaultModel: 'static-model',
      descriptors: {
        'acme-provider': {
          runtime: 'builtin',
          name: 'Acme Provider',
          description: 'Acme provider',
          credentials: [],
          models: [
            { id: 'static-model', name: 'Static Model' },
          ],
        },
      },
    },
  }))

  process.env.OPEN_COWORK_CONFIG_PATH = configPath
  clearConfigCaches()

  try {
    const merged = mergeRuntimeProviderModels(getPublicAppConfig(), [{
      id: 'acme-provider',
      defaultModel: 'runtime-owned-model',
      connected: true,
    }])
    assert.equal(merged.providers.available[0]?.defaultModel, 'runtime-owned-model')
    assert.equal(merged.providers.available[0]?.connected, true)
  } finally {
    if (previousOverride === undefined) delete process.env.OPEN_COWORK_CONFIG_PATH
    else process.env.OPEN_COWORK_CONFIG_PATH = previousOverride
    clearConfigCaches()
    rmSync(tempRoot, { recursive: true, force: true })
  }
})

test('mergeRuntimeProviderModels exposes OpenCode reasoning model metadata', () => {
  const tempRoot = mkdtempSync(join(tmpdir(), 'opencowork-provider-runtime-reasoning-'))
  const configPath = join(tempRoot, 'open-cowork.config.json')
  const previousOverride = process.env.OPEN_COWORK_CONFIG_PATH

  writeFileSync(configPath, JSON.stringify({
    providers: {
      available: ['acme-provider'],
      defaultProvider: 'acme-provider',
      defaultModel: null,
      descriptors: {
        'acme-provider': {
          runtime: 'builtin',
          name: 'Acme Provider',
          description: 'Acme provider',
          credentials: [],
          models: [
            { id: 'live', name: 'Configured Live', featured: true },
          ],
        },
      },
    },
  }))

  process.env.OPEN_COWORK_CONFIG_PATH = configPath
  clearConfigCaches()

  try {
    const merged = mergeRuntimeProviderModels(getPublicAppConfig(), [{
      id: 'acme-provider',
      models: {
        live: {
          name: 'Live Reasoning',
          capabilities: { reasoning: true },
          variants: {
            xhigh: {},
            low: {},
            disabled: { disabled: true },
          },
        },
      },
      defaultModel: 'live',
      connected: true,
    }])
    const model = merged.providers.available[0]?.models.find((entry) => entry.id === 'live')

    assert.equal(model?.reasoning, true)
    assert.deepEqual(model?.variants, ['low', 'xhigh'])
    assert.equal(model?.name, 'Configured Live')
    assert.equal(model?.featured, true)
  } finally {
    if (previousOverride === undefined) delete process.env.OPEN_COWORK_CONFIG_PATH
    else process.env.OPEN_COWORK_CONFIG_PATH = previousOverride
    clearConfigCaches()
    rmSync(tempRoot, { recursive: true, force: true })
  }
})
