import { fetch } from 'undici';
import { createModuleLogger } from '../../utils/logger.js';
import { config } from '../../utils/config.js';
import type { OpenRouterRequest, OpenRouterResponse } from '../../types/api.js';

const logger = createModuleLogger('openrouter-client');

const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
const DEFAULT_MODEL = 'google/gemini-3.1-flash-lite-preview';
const DEFAULT_MAX_TOKENS = 500;
const DEFAULT_TEMPERATURE = 0.8;
const MAX_RETRIES = 3;
const INITIAL_BACKOFF_MS = 1000;

export interface ChatCompletionOptions {
  temperature?: number;
  max_tokens?: number;
  top_p?: number;
}

export interface OpenRouterClientOptions {
  initialBackoffMs?: number;
}

export type MessageContent =
  | string
  | Array<{ type: 'text'; text: string } | { type: 'image_url'; image_url: { url: string } }>;

export interface ChatMessage {
  role: 'system' | 'user' | 'assistant';
  content: MessageContent;
}

export class OpenRouterClient {
  private readonly apiKey: string;
  private readonly initialBackoffMs: number;

  constructor(apiKey: string, options?: OpenRouterClientOptions) {
    this.apiKey = apiKey || '';
    this.initialBackoffMs = options?.initialBackoffMs ?? INITIAL_BACKOFF_MS;
  }

  async chatCompletion(
    messages: ChatMessage[],
    options?: ChatCompletionOptions,
  ): Promise<OpenRouterResponse> {
    const body: OpenRouterRequest = {
      model: DEFAULT_MODEL,
      messages,
      temperature: clampTemperature(options?.temperature ?? DEFAULT_TEMPERATURE),
      max_tokens: options?.max_tokens ?? DEFAULT_MAX_TOKENS,
      ...(options?.top_p !== undefined && { top_p: options.top_p }),
    };

    return this.sendRequest(body);
  }

  async chatCompletionWithVision(
    messages: ChatMessage[],
    options?: ChatCompletionOptions,
  ): Promise<OpenRouterResponse> {
    // Vision requests use the same endpoint; the multimodal content
    // is encoded inside the message content array.
    return this.chatCompletion(messages, options);
  }

  private async sendRequest(body: OpenRouterRequest): Promise<OpenRouterResponse> {
    let lastError: Error | undefined;

    for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
      try {
        logger.debug({ attempt, model: body.model }, 'Sending chat completion request');

        const response = await fetch(`${OPENROUTER_BASE_URL}/chat/completions`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${this.apiKey}`,
          },
          body: JSON.stringify(body),
        });

        if (!response.ok) {
          const errorText = await response.text();
          const err = new Error(
            `OpenRouter API error: ${response.status} ${response.statusText} - ${errorText}`,
          );
          logger.warn({ status: response.status, attempt, errorText }, 'API request failed');

          // Don't retry on client errors (4xx) except 429 (rate limit)
          if (response.status >= 400 && response.status < 500 && response.status !== 429) {
            throw err;
          }
          lastError = err;
        } else {
          const data = (await response.json()) as OpenRouterResponse;
          logger.debug({ id: data.id, usage: data.usage }, 'Chat completion successful');
          return data;
        }
      } catch (error) {
        if (lastError === undefined || lastError !== error) {
          lastError = error instanceof Error ? error : new Error(String(error));
        }
        // If it's a non-retryable client error, throw immediately
        if (lastError.message.includes('OpenRouter API error: 4') && !lastError.message.includes('429')) {
          throw lastError;
        }
        logger.warn({ attempt, error: lastError.message }, 'Request attempt failed');
      }

      // Only sleep between retries, not after the last attempt
      if (attempt < MAX_RETRIES) {
        const backoff = this.initialBackoffMs * Math.pow(2, attempt - 1);
        logger.debug({ backoff, attempt }, 'Waiting before retry');
        await sleep(backoff);
      }
    }

    logger.error({ error: lastError?.message }, 'All retry attempts exhausted');
    throw lastError ?? new Error('OpenRouter request failed after all retries');
  }
}

function clampTemperature(value: number): number {
  return Math.min(0.9, Math.max(0.7, value));
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export function createOpenRouterClient(): OpenRouterClient {
  return new OpenRouterClient(config.openrouterApiKey);
}
