/**
 * Azure OpenAI LLM provider.
 *
 * Uses Azure OpenAI REST API with api-key authentication.
 * Reads config from AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_KEY, AZURE_OPENAI_DEPLOYMENT.
 * Supports text, vision (multipart content), streaming, and embeddings.
 */

import type {
  ChatCompletionRequest,
  ChatCompletionResponse,
  EmbeddingRequest,
  EmbeddingResponse,
  LLMProvider,
} from '../types.js';

export interface AzureOpenAIConfig {
  endpoint: string;
  apiKey: string;
  deployment: string;
  embeddingDeployment?: string;
  apiVersion?: string;
}

export class AzureOpenAIProvider implements LLMProvider {
  private config: AzureOpenAIConfig;

  constructor(config?: Partial<AzureOpenAIConfig>) {
    this.config = {
      endpoint: config?.endpoint || process.env.AZURE_OPENAI_ENDPOINT || '',
      apiKey: config?.apiKey || process.env.AZURE_OPENAI_KEY || process.env.OPENAI_API_KEY || '',
      deployment:
        config?.deployment ||
        process.env.AZURE_OPENAI_DEPLOYMENT ||
        process.env.OPENAI_MODEL ||
        'gpt-4o-mini',
      embeddingDeployment:
        config?.embeddingDeployment ||
        process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT ||
        'text-embedding-3-small',
      apiVersion: config?.apiVersion || process.env.AZURE_OPENAI_API_VERSION || '2024-06-01',
    };
  }

  isConfigured(): boolean {
    return Boolean(this.config.endpoint && this.config.apiKey && this.config.deployment);
  }

  private getBaseUrl(): string {
    return this.config.endpoint.replace(/\/+$/, '');
  }

  private getChatUrl(): string {
    const base = this.getBaseUrl();
    const deployment = encodeURIComponent(this.config.deployment);
    const version = encodeURIComponent(this.config.apiVersion!);
    return `${base}/openai/deployments/${deployment}/chat/completions?api-version=${version}`;
  }

  private getHeaders(): Record<string, string> {
    return {
      'Content-Type': 'application/json',
      'api-key': this.config.apiKey,
    };
  }

  async chatCompletion(req: ChatCompletionRequest): Promise<ChatCompletionResponse> {
    if (!this.isConfigured()) {
      throw new Error(
        'Azure OpenAI is not configured (missing AZURE_OPENAI_ENDPOINT or AZURE_OPENAI_KEY)'
      );
    }

    const url = this.getChatUrl();

    const body = {
      messages: req.messages,
      temperature: req.temperature,
      max_tokens: req.maxTokens,
      top_p: req.topP,
      stop: req.stop,
      response_format: req.responseFormat,
    };

    const response = await fetch(url, {
      method: 'POST',
      headers: this.getHeaders(),
      body: JSON.stringify(body),
    });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Azure OpenAI error ${response.status}: ${text}`);
    }

    const data = (await response.json()) as {
      choices: Array<{ message: { content: string }; finish_reason: string }>;
      model: string;
      usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number };
    };

    return {
      content: data.choices[0]?.message?.content ?? '',
      model: data.model,
      finishReason:
        (data.choices[0]?.finish_reason as ChatCompletionResponse['finishReason']) ?? null,
      usage: {
        promptTokens: data.usage.prompt_tokens,
        completionTokens: data.usage.completion_tokens,
        totalTokens: data.usage.total_tokens,
      },
    };
  }

  async *chatCompletionStream(req: ChatCompletionRequest): AsyncIterable<string> {
    if (!this.isConfigured()) {
      throw new Error(
        'Azure OpenAI is not configured (missing AZURE_OPENAI_ENDPOINT or AZURE_OPENAI_KEY)'
      );
    }

    const url = this.getChatUrl();

    const body = {
      messages: req.messages,
      temperature: req.temperature,
      max_tokens: req.maxTokens,
      top_p: req.topP,
      stop: req.stop,
      response_format: req.responseFormat,
      stream: true,
    };

    const response = await fetch(url, {
      method: 'POST',
      headers: this.getHeaders(),
      body: JSON.stringify(body),
    });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Azure OpenAI streaming error ${response.status}: ${text}`);
    }

    if (!response.body) {
      throw new Error('Azure OpenAI streaming: no response body');
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let buffer = '';

    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop() ?? '';

        for (const line of lines) {
          const trimmed = line.trim();
          if (!trimmed || !trimmed.startsWith('data: ')) continue;
          const data = trimmed.slice(6);
          if (data === '[DONE]') return;
          try {
            const parsed = JSON.parse(data) as {
              choices: Array<{ delta: { content?: string } }>;
            };
            const delta = parsed.choices?.[0]?.delta?.content;
            if (delta) yield delta;
          } catch {
            // skip malformed SSE chunks
          }
        }
      }
    } finally {
      reader.releaseLock();
    }
  }

  async embed(req: EmbeddingRequest): Promise<EmbeddingResponse> {
    if (!this.isConfigured()) {
      throw new Error(
        'Azure OpenAI is not configured (missing AZURE_OPENAI_ENDPOINT or AZURE_OPENAI_KEY)'
      );
    }

    const base = this.getBaseUrl();
    const deployment = encodeURIComponent(this.config.embeddingDeployment!);
    const version = encodeURIComponent(this.config.apiVersion!);
    const url = `${base}/openai/deployments/${deployment}/embeddings?api-version=${version}`;

    const body = {
      input: req.input,
    };

    const response = await fetch(url, {
      method: 'POST',
      headers: this.getHeaders(),
      body: JSON.stringify(body),
    });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Azure OpenAI embedding error ${response.status}: ${text}`);
    }

    const data = (await response.json()) as {
      data: Array<{ embedding: number[]; index: number }>;
      model: string;
      usage: { prompt_tokens: number; total_tokens: number };
    };

    return {
      embeddings: data.data.sort((a, b) => a.index - b.index).map(d => d.embedding),
      model: data.model,
      usage: {
        promptTokens: data.usage.prompt_tokens,
        completionTokens: 0,
        totalTokens: data.usage.total_tokens,
      },
    };
  }
}
