/**
 * OpenRouter client for Token.
 *
 * Thin wrapper around OpenRouter's OpenAI-compatible API.
 * Handles streaming and non-streaming completions, token counting, and cost tracking.
 */

import { logger } from "@/lib/logger";

export interface TokenLLMRequest {
  messages: Array<{ role: string; content: string }>;
  model: string;
  temperature?: number;
  maxTokens?: number;
  stream?: boolean;
}

export interface TokenLLMResponse {
  content: string;
  model: string;
  inputTokens: number;
  outputTokens: number;
  costUsd: number;
  latencyMs: number;
}

const OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions";

export async function callOpenRouter(
  req: TokenLLMRequest,
): Promise<TokenLLMResponse> {
  const apiKey = process.env.OPENROUTER_API_KEY;
  if (!apiKey) {
    throw new Error("OPENROUTER_API_KEY not configured");
  }

  const start = Date.now();

  const payload = {
    model: req.model,
    messages: req.messages,
    temperature: req.temperature ?? 0.4,
    max_tokens: req.maxTokens ?? 4096,
    stream: false,
  };

  const res = await fetch(OPENROUTER_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "HTTP-Referer": "https://llmhosts.com",
      "X-Title": "LLMHosts Token Assistant",
    },
    body: JSON.stringify(payload),
  });

  if (!res.ok) {
    const errorText = await res.text().catch(() => "Unknown error");
    logger.error(
      { action: "token_openrouter_error", status: res.status },
      "OpenRouter API error",
    );
    throw new Error(`OpenRouter API error (${res.status}): ${errorText}`);
  }

  const data = await res.json();
  const latencyMs = Date.now() - start;

  const choice = data.choices?.[0]?.message;
  const usage = data.usage ?? {};

  // OpenRouter returns cost in the response headers or usage object
  const inputTokens = usage.prompt_tokens ?? 0;
  const outputTokens = usage.completion_tokens ?? 0;

  // Estimate cost from OpenRouter's pricing (they include it in headers)
  const totalCost = parseFloat(
    res.headers.get("x-openrouter-cost") ?? "0",
  );

  logger.info(
    {
      action: "token_completion",
      model: req.model,
      input_tokens: inputTokens,
      output_tokens: outputTokens,
      latency_ms: latencyMs,
      cost_usd: totalCost,
    },
    "Token completion",
  );

  return {
    content: choice?.content ?? "",
    model: data.model ?? req.model,
    inputTokens,
    outputTokens,
    costUsd: totalCost,
    latencyMs,
  };
}

/**
 * Streaming variant of callOpenRouter.
 *
 * Returns a ReadableStream that emits our SSE format:
 *   data: {"token":"hello","done":false}\n\n
 *   data: {"done":true,"model":"...","costUsd":0.001,...}\n\n
 *
 * The caller is responsible for piping this stream into an HTTP response.
 * After the stream closes, `onComplete` is called with the full response
 * metadata so the caller can persist to DB.
 */
export interface StreamCompletePayload {
  content: string;
  model: string;
  inputTokens: number;
  outputTokens: number;
  costUsd: number;
  latencyMs: number;
}

export async function callOpenRouterStream(
  req: TokenLLMRequest,
  onComplete: (payload: StreamCompletePayload) => Promise<void>,
): Promise<ReadableStream<Uint8Array>> {
  const apiKey = process.env.OPENROUTER_API_KEY;
  if (!apiKey) {
    throw new Error("OPENROUTER_API_KEY not configured");
  }

  const start = Date.now();

  const payload = {
    model: req.model,
    messages: req.messages,
    temperature: req.temperature ?? 0.4,
    max_tokens: req.maxTokens ?? 4096,
    stream: true,
    // Request usage in streaming — OpenRouter supports this via OpenAI compat
    stream_options: { include_usage: true },
  };

  const res = await fetch(OPENROUTER_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "HTTP-Referer": "https://llmhosts.com",
      "X-Title": "LLMHosts Token Assistant",
    },
    body: JSON.stringify(payload),
  });

  if (!res.ok) {
    const errorText = await res.text().catch(() => "Unknown error");
    logger.error(
      { action: "token_openrouter_stream_error", status: res.status },
      "OpenRouter streaming API error",
    );
    throw new Error(`OpenRouter API error (${res.status}): ${errorText}`);
  }

  // Grab cost header before we start consuming the body
  const headerCost = parseFloat(res.headers.get("x-openrouter-cost") ?? "0");

  const encoder = new TextEncoder();
  const decoder = new TextDecoder();
  const upstream = res.body;

  if (!upstream) {
    throw new Error("OpenRouter returned no response body");
  }

  // State accumulated across SSE chunks
  let fullContent = "";
  let resolvedModel = req.model;
  let inputTokens = 0;
  let outputTokens = 0;
  const costUsd = headerCost;

  return new ReadableStream<Uint8Array>({
    async start(controller) {
      const reader = upstream.getReader();
      let buffer = "";

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

          buffer += decoder.decode(value, { stream: true });

          // Process complete lines from the buffer
          const lines = buffer.split("\n");
          // Keep the last (potentially incomplete) line in the buffer
          buffer = lines.pop() ?? "";

          for (const line of lines) {
            const trimmed = line.trim();
            if (!trimmed || !trimmed.startsWith("data: ")) continue;

            const jsonStr = trimmed.slice(6);
            if (jsonStr === "[DONE]") continue;

            try {
              const chunk = JSON.parse(jsonStr);

              // Extract model from first chunk
              if (chunk.model) {
                resolvedModel = chunk.model;
              }

              // Extract usage from final chunk (stream_options: include_usage)
              if (chunk.usage) {
                inputTokens = chunk.usage.prompt_tokens ?? inputTokens;
                outputTokens = chunk.usage.completion_tokens ?? outputTokens;
              }

              // Extract content delta
              const delta = chunk.choices?.[0]?.delta;
              if (delta?.content) {
                fullContent += delta.content;
                const ssePayload = JSON.stringify({ token: delta.content, done: false });
                controller.enqueue(encoder.encode(`data: ${ssePayload}\n\n`));
              }
            } catch {
              // Skip malformed JSON chunks
              logger.warn({ action: "token_stream_parse_skip", line: trimmed }, "Skipped malformed SSE chunk");
            }
          }
        }

        // Stream complete — emit final event with metadata
        const latencyMs = Date.now() - start;
        const finalPayload = JSON.stringify({
          done: true,
          model: resolvedModel,
          costUsd,
          latencyMs,
          inputTokens,
          outputTokens,
        });
        controller.enqueue(encoder.encode(`data: ${finalPayload}\n\n`));
        controller.close();

        // Notify caller so it can persist to DB
        logger.info(
          {
            action: "token_stream_complete",
            model: resolvedModel,
            input_tokens: inputTokens,
            output_tokens: outputTokens,
            latency_ms: latencyMs,
            cost_usd: costUsd,
          },
          "Token streaming completion",
        );

        await onComplete({
          content: fullContent,
          model: resolvedModel,
          inputTokens,
          outputTokens,
          costUsd,
          latencyMs,
        });
      } catch (err) {
        logger.error({ action: "token_stream_error", err }, "Token stream processing error");
        // Attempt to send error event before closing
        try {
          const errPayload = JSON.stringify({ error: "Stream interrupted", done: true });
          controller.enqueue(encoder.encode(`data: ${errPayload}\n\n`));
        } catch {
          // Controller may already be closed
        }
        controller.error(err);
      }
    },
  });
}
