import {
  AuthError,
  FatalError,
  QuotaExhaustedError,
  RateLimitError,
  TransientError,
  setAdapterFactories,
  type AIContentBlock,
  type AIMessage,
  type AIRequest,
} from './index.js'

export type GoogleGenAIClient = {
  models: {
    generateContent(params: {
      model: string
      contents: unknown
      config?: {
        systemInstruction?: string
        maxOutputTokens?: number
        temperature?: number
        stopSequences?: string[]
      }
    }): Promise<unknown>
  }
}

export type GoogleCreds = {
  apiKey: string
  createClient?: (apiKey: string) => GoogleGenAIClient
}

type GeminiUsageMetadata = {
  promptTokenCount?: number
  candidatesTokenCount?: number
}

type GeminiResponse = {
  text?: string
  usageMetadata?: GeminiUsageMetadata
}

export function extractGeminiSystem(messages: AIMessage[]): string | undefined {
  const systemParts = messages
    .filter((m) => m.role === 'system')
    .map((m) =>
      typeof m.content === 'string'
        ? m.content
        : m.content
            .filter((b): b is Extract<AIContentBlock, { type: 'text' }> => b.type === 'text')
            .map((b) => b.text)
            .join(''),
    )
  return systemParts.length > 0 ? systemParts.join('\n\n') : undefined
}

export function mapContentToGemini(
  content: string | AIContentBlock[],
): string | Array<{ text?: string; inlineData?: { mimeType: string; data: string } }> {
  if (typeof content === 'string') return content
  return content.map((block) => {
    if (block.type === 'text') {
      return { text: block.text }
    }
    const img = block.image
    return {
      inlineData: {
        mimeType: img.mediaType,
        data: img.data,
      },
    }
  })
}

type GeminiPart = { text?: string; inlineData?: { mimeType: string; data: string } }

type GeminiContent = {
  role: 'user' | 'model'
  parts: GeminiPart[] | string
}

export function mapMessagesToGemini(messages: AIMessage[]): GeminiContent[] {
  return messages
    .filter((m) => m.role !== 'system')
    .map((message) => {
      const mapped = mapContentToGemini(message.content)
      const parts = typeof mapped === 'string' ? [{ text: mapped }] : mapped
      return {
        role: message.role === 'assistant' ? ('model' as const) : ('user' as const),
        parts,
      }
    })
}

export function mapGeminiResponse(body: GeminiResponse, provider: string, model: string) {
  return {
    content: body.text?.trim() ?? '',
    model,
    provider,
    usage: {
      promptTokens: body.usageMetadata?.promptTokenCount ?? 0,
      completionTokens: body.usageMetadata?.candidatesTokenCount ?? 0,
    },
    finishReason: undefined,
  }
}

export function classifyGoogleError(status: number | undefined, message: string, provider: string) {
  if (status === 429) {
    if (/RESOURCE_EXHAUSTED|quota/i.test(message)) {
      return new QuotaExhaustedError(message, provider)
    }
    return new RateLimitError(message, provider)
  }
  if (status === 401 || status === 403) {
    return new AuthError(message, provider)
  }
  if (status !== undefined && status >= 500) {
    return new TransientError(message, provider)
  }
  return new FatalError(message, provider)
}

function mapSdkError(err: unknown, provider: string): never {
  const status = (err as { status?: number }).status
  const code = (err as { code?: string }).code
  const msg = (err as { message?: string }).message ?? String(err)

  if (status === 429) {
    if (/RESOURCE_EXHAUSTED|quota/i.test(msg)) {
      throw new QuotaExhaustedError(msg, provider)
    }
    throw new RateLimitError(msg, provider)
  }
  if (status === 401 || status === 403) throw new AuthError(msg, provider)
  if (
    code === 'ECONNRESET' ||
    code === 'ETIMEDOUT' ||
    code === 'ENOTFOUND' ||
    (status !== undefined && status >= 500)
  ) {
    throw new TransientError(msg, provider)
  }
  throw classifyGoogleError(status, msg, provider)
}

async function createGoogleClient(creds: GoogleCreds): Promise<GoogleGenAIClient> {
  if (creds.createClient) return creds.createClient(creds.apiKey)
  const { GoogleGenAI } = await import('@google/genai')
  return new GoogleGenAI({ apiKey: creds.apiKey }) as GoogleGenAIClient
}

export function makeGoogleAdapter(creds: GoogleCreds) {
  return {
    provider: 'google',
    supportsImage: true,
    async call(req: AIRequest) {
      const client = await createGoogleClient(creds)
      const system = extractGeminiSystem(req.messages)
      const contents = mapMessagesToGemini(req.messages)

      try {
        const raw = await client.models.generateContent({
          model: req.model,
          contents,
          config: {
            ...(system ? { systemInstruction: system } : {}),
            ...(req.maxTokens !== undefined ? { maxOutputTokens: req.maxTokens } : {}),
            ...(req.temperature !== undefined ? { temperature: req.temperature } : {}),
            ...(req.stop !== undefined
              ? { stopSequences: Array.isArray(req.stop) ? req.stop : [req.stop] }
              : {}),
          },
        })
        return mapGeminiResponse(raw as GeminiResponse, 'google', req.model)
      } catch (err) {
        mapSdkError(err, 'google')
      }
    },
  }
}

/** Register the google adapter factory with core `getAdapter`. */
export function registerGoogleAdapter(): void {
  setAdapterFactories({
    google: (creds) => makeGoogleAdapter(creds as GoogleCreds),
  })
}
