import {
  JsonConsoleLogger,
  LocalSecretService,
  LocalVariablesService,
  InMemoryQueueService,
} from '@pikku/core/services'
import { pikkuServices } from '../.pikku/pikku-types.gen.js'
import { TypedSecretService } from '../.pikku/secrets/pikku-secrets.gen.js'
import { TypedVariablesService } from '../.pikku/variables/pikku-variables.gen.js'
import { CFWorkerSchemaService } from '@pikku/schema-cfworker'
import { Kysely, CamelCasePlugin } from 'kysely'
import { LibsqlWebDialect } from '@pikku/kysely-sqlite'
import type { VercelAIAgentRunner } from '@pikku/ai-vercel'
import type { DB } from './types/db.types.js'

export const createSingletonServices = pikkuServices(async (config, existingServices) => {
  const variables =
    existingServices?.variables ?? new TypedVariablesService(new LocalVariablesService())
  const secrets =
    existingServices?.secrets ?? new TypedSecretService(new LocalSecretService(variables))
  const logger = existingServices?.logger ?? new JsonConsoleLogger()
  const schema = existingServices?.schema ?? new CFWorkerSchemaService(logger)

  // In CF Workers and containers DATABASE_URL is injected by fabric.
  // In local dev (pikku dev) existingServices.kysely is the node:sqlite instance
  // set up by the CLI — prefer it over DATABASE_URL so sandbox mode always uses
  // the local migrated SQLite rather than the remote Turso URL from the cascade.
  // In tests existingServices.kysely is provided by the test support layer.
  // node:sqlite / createNodeSqliteKysely is intentionally absent — it is not
  // available in CF Workers. See tests/support/services.ts for the dev path.
  let kysely: Kysely<DB> | undefined
  if (existingServices?.kysely) {
    kysely = existingServices.kysely as Kysely<DB>
  } else {
    const databaseUrl = await variables.get('DATABASE_URL')
    if (databaseUrl) {
      if (/^postgres(ql)?:\/\//.test(databaseUrl)) {
        const [{ PostgresJSDialect }, postgres] = await Promise.all([
          import('kysely-postgres-js'),
          import('postgres'),
        ])
        kysely = new Kysely<DB>({
          dialect: new PostgresJSDialect({
            postgres: postgres.default(databaseUrl),
          }),
          plugins: [new CamelCasePlugin()],
        })
      } else {
        kysely = new Kysely<DB>({
          dialect: new LibsqlWebDialect({ url: databaseUrl }),
          plugins: [new CamelCasePlugin()],
        })
      }
    } else {
      throw new Error(
        'kysely service not provided: set DATABASE_URL (Postgres or libsql) or pass kysely via existingServices',
      )
    }
  }

  const litellmProxyUrl = process.env.LITELLM_PROXY_URL ?? null
  const litellmApiKey = process.env.LITELLM_API_KEY ?? null
  let aiAgentRunner: VercelAIAgentRunner | undefined
  if (litellmProxyUrl && litellmApiKey) {
    // The AI SDKs (~3MB) are stubbed out of non-agent units at bundle time —
    // only units with the `ai-model` capability keep them. So import them
    // dynamically and guard on the module resolving to a real export; in a
    // stubbed unit the import yields `{}` and the runner is simply not built.
    const aiVercel = await import('@pikku/ai-vercel')
    const aiSdk = await import('@ai-sdk/openai-compatible')
    if (aiVercel.VercelAIAgentRunner && aiSdk.createOpenAICompatible) {
      const litellmProvider = aiSdk.createOpenAICompatible({
        name: 'litellm',
        baseURL: litellmProxyUrl,
        apiKey: litellmApiKey,
      })
      aiAgentRunner = new aiVercel.VercelAIAgentRunner({
        openai: (modelId: string) => litellmProvider.chatModel(modelId),
        anthropic: (modelId: string) => litellmProvider.chatModel(modelId),
        google: (modelId: string) => litellmProvider.chatModel(modelId),
        deepseek: (modelId: string) => litellmProvider.chatModel(modelId),
      })
    }
  }

  const queueService = existingServices?.queueService ?? new InMemoryQueueService()

  return {
    ...(existingServices ?? {}),
    config,
    variables,
    secrets,
    logger,
    schema,
    kysely,
    queueService,
    ...(aiAgentRunner ? { aiAgentRunner } : {}),
  }
})
