/**
 * Google Gemini adapter — system-ai.
 * Uses @google/generative-ai. Supports vision via inline image data.
 */
import { GoogleGenerativeAI, HarmBlockThreshold, HarmCategory } from '@google/generative-ai'
import type { Part } from '@google/generative-ai'
import type { AIAdapter, AIContentBlock, AIMessage, AIRequest, AIResponse } from '../adapter'
import { AIProviderError } from '../errors'
import type { AIEnv } from './index'

function mapContentBlocksToParts(content: string | AIContentBlock[]): Part[] {
  if (typeof content === 'string') return [{ text: content }]
  return content.map((b): Part => {
    if (b.type === 'text') {
      return { text: b.text ?? '' }
    }
    // image block → Gemini inlineData
    const img = b.image!
    return {
      inlineData: {
        mimeType: img.mediaType,
        data: img.data,
      },
    }
  })
}

function normalizeError(err: unknown, provider: string): AIProviderError {
  if (err instanceof Error) {
    const msg = err.message.toLowerCase()
    if (msg.includes('429') || msg.includes('rate') || msg.includes('quota')) {
      return new AIProviderError('rate_limit', provider, true, `Rate limited: ${err.message}`)
    }
    if (msg.includes('401') || msg.includes('403') || msg.includes('api key') || msg.includes('unauthorized')) {
      return new AIProviderError('auth_error', provider, false, `Auth error: ${err.message}`)
    }
    if (msg.includes('404') || msg.includes('not found') || msg.includes('model')) {
      return new AIProviderError('model_unavailable', provider, true, `Model unavailable: ${err.message}`)
    }
    if (err.name === 'AbortError' || msg.includes('timeout')) {
      return new AIProviderError('timeout', provider, true, `Request timed out: ${err.message}`)
    }
    return new AIProviderError('unknown', provider, false, `Unknown error: ${err.message}`)
  }
  return new AIProviderError('unknown', provider, false, `Unknown error: ${String(err)}`)
}

export class GoogleAdapter implements AIAdapter {
  readonly provider = 'google' as const
  private genAI: GoogleGenerativeAI

  constructor(env: AIEnv) {
    this.genAI = new GoogleGenerativeAI(env.GOOGLE_AI_API_KEY)
  }

  async call(request: AIRequest): Promise<AIResponse> {
    const start = Date.now()
    try {
      // Extract system instruction from messages
      const systemMessages = request.messages.filter((m) => m.role === 'system')
      const systemInstruction = systemMessages.length > 0
        ? systemMessages.map((m) =>
            typeof m.content === 'string' ? m.content : m.content.map((b) => b.text ?? '').join('')
          ).join('\n\n')
        : undefined

      const model = this.genAI.getGenerativeModel({
        model: request.model,
        ...(systemInstruction ? { systemInstruction } : {}),
        safetySettings: [
          { category: HarmCategory.HARM_CATEGORY_HARASSMENT, threshold: HarmBlockThreshold.BLOCK_NONE },
          { category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE },
          { category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold: HarmBlockThreshold.BLOCK_NONE },
          { category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold: HarmBlockThreshold.BLOCK_NONE },
        ],
        generationConfig: {
          ...(request.maxTokens !== undefined ? { maxOutputTokens: request.maxTokens } : {}),
          ...(request.temperature !== undefined ? { temperature: request.temperature } : {}),
        },
      })

      // Convert messages to Gemini history (all except last user message)
      const nonSystemMessages = request.messages.filter((m) => m.role !== 'system')
      const historyMessages = nonSystemMessages.slice(0, -1)
      const lastMessage = nonSystemMessages[nonSystemMessages.length - 1]

      if (!lastMessage) {
        throw new Error('No user message provided')
      }

      const history = historyMessages.map((m) => ({
        role: m.role === 'assistant' ? 'model' : 'user',
        parts: mapContentBlocksToParts(m.content),
      }))

      const chat = model.startChat({ history })
      const result = await chat.sendMessage(mapContentBlocksToParts(lastMessage.content))

      const response = result.response
      const content = response.text()
      const usageMeta = response.usageMetadata

      return {
        content,
        inputTokens: usageMeta?.promptTokenCount ?? 0,
        outputTokens: usageMeta?.candidatesTokenCount ?? 0,
        model: request.model,
        provider: this.provider,
        durationMs: Date.now() - start,
      }
    } catch (err) {
      throw normalizeError(err, this.provider)
    }
  }

  stream(request: AIRequest): ReadableStream<string> {
    const self = this
    return new ReadableStream<string>({
      async start(controller) {
        try {
          const systemMessages = request.messages.filter((m) => m.role === 'system')
          const systemInstruction = systemMessages.length > 0
            ? systemMessages.map((m) =>
                typeof m.content === 'string' ? m.content : m.content.map((b) => b.text ?? '').join('')
              ).join('\n\n')
            : undefined

          const model = self.genAI.getGenerativeModel({
            model: request.model,
            ...(systemInstruction ? { systemInstruction } : {}),
            generationConfig: {
              ...(request.maxTokens !== undefined ? { maxOutputTokens: request.maxTokens } : {}),
            },
          })

          const nonSystemMessages = request.messages.filter((m) => m.role !== 'system')
          const lastMessage = nonSystemMessages[nonSystemMessages.length - 1]
          if (!lastMessage) {
            controller.close()
            return
          }

          const result = await model.generateContentStream(mapContentBlocksToParts(lastMessage.content))
          for await (const chunk of result.stream) {
            const text = chunk.text()
            if (text) controller.enqueue(text)
          }
          controller.close()
        } catch (err) {
          controller.error(normalizeError(err, self.provider))
        }
      },
    })
  }
}
