/**
 * Server-side AI provider abstraction
 *
 * API keys TIDAK PERNAH dikirim ke frontend.
 * Keys diambil dari:
 *   1. env_vars DB (terenkripsi, per-user)  — via resolveUserEnvVars()
 *   2. process.env                          — deployment/server fallback
 *
 * File ini HANYA boleh diimport dari server-side code.
 */

import type { ResolvedEnvVars } from "@/lib/env-vars-resolver";

export interface AIMessage {
  role: "system" | "user" | "assistant";
  content: string;
}

export interface AIGenerateOptions {
  model?: string;
  temperature?: number;
  maxTokens?: number;
}

export interface AIResponse {
  content: string;
  model: string;
  provider: string;
  usage?: {
    promptTokens: number;
    completionTokens: number;
    totalTokens: number;
  };
}

// =====================
// RATE LIMIT ERROR
// Dilempar khusus untuk HTTP 429 agar generatePrompt bisa
// membedakan "rate-limited" vs "error permanen" dan langsung
// skip ke provider berikutnya tanpa menunggu retry delay.
// =====================
export class RateLimitError extends Error {
  readonly provider: string;
  readonly retryAfterMs: number; // 0 = tidak diketahui

  constructor(provider: string, retryAfterMs = 0, message?: string) {
    super(message ?? `${provider} rate limit tercapai`);
    this.name         = "RateLimitError";
    this.provider     = provider;
    this.retryAfterMs = retryAfterMs;
  }
}

// Helper: parse Retry-After header → milliseconds
// Header bisa berupa detik integer ("60") atau HTTP-date
function parseRetryAfterMs(header: string | null): number {
  if (!header) return 0;
  const secs = parseInt(header, 10);
  if (!isNaN(secs)) return secs * 1000;
  // HTTP-date format
  const ts = Date.parse(header);
  if (!isNaN(ts)) return Math.max(0, ts - Date.now());
  return 0;
}

// =====================
// PROCESS-LEVEL CIRCUIT BREAKER
// Menyimpan timestamp kapan provider boleh dicoba lagi.
// Key: provider name. Value: timestamp epoch (ms).
// Default TTL 60 s — cukup untuk menghindari hammer loop.
// =====================
const CIRCUIT_OPEN_UNTIL = new Map<string, number>();
const CIRCUIT_DEFAULT_TTL_MS = 60_000; // 60 detik

function isCircuitOpen(provider: string): boolean {
  const until = CIRCUIT_OPEN_UNTIL.get(provider) ?? 0;
  if (Date.now() < until) return true;
  CIRCUIT_OPEN_UNTIL.delete(provider);
  return false;
}

function openCircuit(provider: string, ttlMs: number): void {
  const until = Date.now() + (ttlMs > 0 ? ttlMs : CIRCUIT_DEFAULT_TTL_MS);
  CIRCUIT_OPEN_UNTIL.set(provider, until);
  console.warn(
    `[provider] Circuit open untuk "${provider}" selama ${Math.round((until - Date.now()) / 1000)}s`
  );
}

/** Untuk testing / admin reset — bersihkan semua circuit breaker */
export function resetAllCircuits(): void {
  CIRCUIT_OPEN_UNTIL.clear();
}

// =====================
// OPENAI
// =====================
export async function generateWithOpenAI(
  messages: AIMessage[],
  apiKey: string,
  options: AIGenerateOptions = {}
): Promise<AIResponse> {
  const model = options.model ?? "gpt-4o-mini";

  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      model,
      messages,
      temperature: options.temperature ?? 0.8,
      max_tokens: options.maxTokens ?? 4096,
      response_format: { type: "json_object" },
    }),
  });

  if (res.status === 429) {
    throw new RateLimitError("openai", parseRetryAfterMs(res.headers.get("Retry-After")));
  }

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

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

  // BUG-04 FIX: null-safe access — choices bisa kosong jika provider filter/safety block
  const content = data.choices?.[0]?.message?.content;
  if (!content) {
    throw new Error(`OpenAI returned empty response (choices empty or content null)`);
  }

  return {
    content,
    model: data.model ?? model,
    provider: "openai",
    usage: data.usage
      ? {
          promptTokens:     data.usage.prompt_tokens,
          completionTokens: data.usage.completion_tokens,
          totalTokens:      data.usage.total_tokens,
        }
      : undefined,
  };
}

// =====================
// ANTHROPIC
// =====================
export async function generateWithAnthropic(
  messages: AIMessage[],
  apiKey: string,
  options: AIGenerateOptions = {}
): Promise<AIResponse> {
  const model = options.model ?? "claude-3-haiku-20240307";
  const systemMsg = messages.find((m) => m.role === "system");
  const userMsgs  = messages.filter((m) => m.role !== "system");

  // Anthropic tidak punya native JSON mode — enforce via system prompt
  const systemContent =
    (systemMsg?.content ?? "") +
    "\n\nCRITICAL: Your response MUST be valid JSON only. " +
    "Do NOT wrap in markdown code blocks. Do NOT add any text before or after the JSON. " +
    "Start your response with { and end with }.";

  const res = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "Content-Type":      "application/json",
      "x-api-key":         apiKey,
      "anthropic-version": "2023-06-01",
    },
    body: JSON.stringify({
      model,
      max_tokens: options.maxTokens ?? 4096,
      system:     systemContent,
      messages:   userMsgs,
    }),
  });

  if (res.status === 429) {
    throw new RateLimitError("anthropic", parseRetryAfterMs(res.headers.get("Retry-After")));
  }

  if (!res.ok) {
    const err = await res.text();
    throw new Error(`Anthropic ${res.status}: ${err}`);
  }

  const data = await res.json() as {
    content: Array<{ text: string }>;
    model: string;
    usage?: { input_tokens: number; output_tokens: number };
  };

  // BUG-04 FIX: null-safe access — content bisa kosong jika safety filter aktif
  const content = data.content?.[0]?.text;
  if (!content) {
    throw new Error(`Anthropic returned empty response (content array empty or text null)`);
  }

  return {
    content,
    model:   data.model ?? model,
    provider: "anthropic",
    usage: data.usage
      ? {
          promptTokens:     data.usage.input_tokens,
          completionTokens: data.usage.output_tokens,
          totalTokens:      data.usage.input_tokens + data.usage.output_tokens,
        }
      : undefined,
  };
}

// =====================
// GOOGLE AI
// =====================
export async function generateWithGoogleAI(
  messages: AIMessage[],
  apiKey: string,
  options: AIGenerateOptions = {}
): Promise<AIResponse> {
  const model = options.model ?? "gemini-1.5-flash";
  const systemMsg = messages.find((m) => m.role === "system");
  const userMsgs  = messages.filter((m) => m.role !== "system");

  const contents = userMsgs.map((m) => ({
    role:  m.role === "user" ? "user" : "model",
    parts: [{ text: m.content }],
  }));

  const res = await fetch(
    `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`,
    {
      method:  "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        system_instruction: systemMsg
          ? { parts: [{ text: systemMsg.content }] }
          : undefined,
        contents,
        generationConfig: {
          temperature:      options.temperature ?? 0.8,
          maxOutputTokens:  options.maxTokens   ?? 4096,
          responseMimeType: "application/json",
        },
      }),
    }
  );

  if (res.status === 429) {
    throw new RateLimitError("google", parseRetryAfterMs(res.headers.get("Retry-After")));
  }

  if (!res.ok) {
    const err = await res.text();
    throw new Error(`Google AI ${res.status}: ${err}`);
  }

  const data = await res.json() as {
    candidates: Array<{ content: { parts: Array<{ text: string }> } }>;
    usageMetadata?: {
      promptTokenCount:     number;
      candidatesTokenCount: number;
      totalTokenCount:      number;
    };
  };

  // BUG-04 FIX: triple null-safe chain — candidates/content/parts semua bisa kosong
  // (terjadi saat Gemini safety filter memblokir output)
  const content = data.candidates?.[0]?.content?.parts?.[0]?.text;
  if (!content) {
    throw new Error(`Google AI returned empty response (candidates/parts empty or safety blocked)`);
  }

  return {
    content,
    model,
    provider: "google",
    usage: data.usageMetadata
      ? {
          promptTokens:     data.usageMetadata.promptTokenCount,
          completionTokens: data.usageMetadata.candidatesTokenCount,
          totalTokens:      data.usageMetadata.totalTokenCount,
        }
      : undefined,
  };
}

// =====================
// AIHUBMIX
// OpenAI-compatible gateway — base: https://aihubmix.com/v1
// Support: GPT, Gemini, Claude, Llama, dan model gratis
// Model gratis tersedia: coding-glm-5-turbo-free, step-3.7-flash-free, xiaomi-mimo-v2-omni-free
// =====================
export async function generateWithAIHubMix(
  messages: AIMessage[],
  apiKey: string,
  options: AIGenerateOptions = {}
): Promise<AIResponse> {
  // Default model: GPT-4o-mini via AIHubMix
  // Bisa dioverride dengan model spesifik termasuk model gratis
  const model = options.model ?? "gpt-4o-mini";

  const res = await fetch("https://aihubmix.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type":  "application/json",
      "Authorization": `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      model,
      messages,
      temperature:     options.temperature ?? 0.8,
      max_tokens:      options.maxTokens   ?? 4096,
      response_format: { type: "json_object" },
    }),
  });

  if (res.status === 429) {
    throw new RateLimitError("aihubmix", parseRetryAfterMs(res.headers.get("Retry-After")));
  }

  if (!res.ok) {
    const err = await res.text();
    throw new Error(`AIHubMix ${res.status}: ${err}`);
  }

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

  // null-safe access
  const content = data.choices?.[0]?.message?.content;
  if (!content) {
    throw new Error(`AIHubMix returned empty response (choices empty or content null)`);
  }

  return {
    content,
    model:    data.model ?? model,
    provider: "aihubmix",
    usage: data.usage
      ? {
          promptTokens:     data.usage.prompt_tokens,
          completionTokens: data.usage.completion_tokens,
          totalTokens:      data.usage.total_tokens,
        }
      : undefined,
  };
}

// =====================
// AGENTROUTER
// OpenAI-compatible gateway — akses 30+ provider via satu key
// Base URL: https://agentrouter.org/v1
// Docs:     https://docs.agentrouter.org
// =====================
export async function generateWithAgentRouter(
  messages: AIMessage[],
  apiKey: string,
  options: AIGenerateOptions = {}
): Promise<AIResponse> {
  // Format model: "provider/model-id" atau model-id biasa — AgentRouter handles routing
  const model = options.model ?? "claude-3-5-haiku-20241022";

  const res = await fetch("https://agentrouter.org/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type":  "application/json",
      "Authorization": `Bearer ${apiKey}`,
      "HTTP-Referer":  "https://promptmotion.ai",
      "X-Title":       "PromptMotion AI",
    },
    body: JSON.stringify({
      model,
      messages,
      temperature:     options.temperature ?? 0.8,
      max_tokens:      options.maxTokens   ?? 4096,
      response_format: { type: "json_object" },
    }),
  });

  // 429 → RateLimitError agar generatePrompt langsung skip ke provider berikutnya
  if (res.status === 429) {
    throw new RateLimitError("agentrouter", parseRetryAfterMs(res.headers.get("Retry-After")));
  }

  if (!res.ok) {
    const err = await res.text();
    throw new Error(`AgentRouter ${res.status}: ${err}`);
  }

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

  // BUG-04 FIX: null-safe access — sama seperti OpenAI karena AgentRouter memakai format yang sama
  const content = data.choices?.[0]?.message?.content;
  if (!content) {
    throw new Error(`AgentRouter returned empty response (choices empty or content null)`);
  }

  return {
    content,
    model:    data.model ?? model,
    provider: "agentrouter",
    usage: data.usage
      ? {
          promptTokens:     data.usage.prompt_tokens,
          completionTokens: data.usage.completion_tokens,
          totalTokens:      data.usage.total_tokens,
        }
      : undefined,
  };
}

// =====================
// MAIN ENTRY POINT
// Terima ResolvedEnvVars (dari DB/env) dan pilih provider sesuai preferensi.
// Urutan fallback: agentrouter → aihubmix (primary → gemini → free_1..3) → openai → anthropic → google.
// Fix #3: AIHubMix sekarang punya internal key rotation — primary → gemini → free keys.
// RateLimitError membuka circuit breaker dan langsung skip ke provider/key berikutnya.
// =====================
export async function generatePrompt(
  messages: AIMessage[],
  options: AIGenerateOptions = {},
  resolvedVars?: Partial<ResolvedEnvVars>
): Promise<AIResponse> {
  const vars = resolvedVars ?? {};

  // Resolve keys: DB vars → process.env fallback
  const openaiKey      = vars.openaiApiKey      || process.env.OPENAI_API_KEY;
  const anthropicKey   = vars.anthropicApiKey   || process.env.ANTHROPIC_API_KEY;
  const googleKey      = vars.googleAiApiKey    || process.env.GOOGLE_AI_API_KEY;
  const agentRouterKey = vars.agentRouterApiKey || process.env.AGENTROUTER_API_KEY;

  // Fix #3: Build AIHubMix key pool: primary → gemini → free keys (filter nulls)
  const aihubmixPrimary  = vars.aihubmixApiKey    || process.env.AIHUBMIX_API_KEY;
  const aihubmixGemini   = vars.aihubmixGeminiKey || process.env.AIHUBMIX_GEMINI_KEY;
  const aihubmixFreeEnv  = [
    process.env.AIHUBMIX_FREE_KEY_1,
    process.env.AIHUBMIX_FREE_KEY_2,
    process.env.AIHUBMIX_FREE_KEY_3,
  ].filter((k): k is string => !!k);
  const aihubmixFreeDb   = vars.aihubmixFreeKeys ?? [];
  // Merge DB keys + env keys, deduplicate, filter empty
  const aihubmixKeyPool: string[] = [
    ...(aihubmixPrimary ? [aihubmixPrimary] : []),
    ...(aihubmixGemini  ? [aihubmixGemini]  : []),
    ...aihubmixFreeDb,
    ...aihubmixFreeEnv,
  ].filter((k, i, arr) => k.length > 0 && arr.indexOf(k) === i); // deduplicate

  const preferred     = vars.preferredProvider ?? "auto";
  const modelOverride = vars.preferredModel    ?? options.model;

  if (!openaiKey && !anthropicKey && !googleKey && !agentRouterKey && aihubmixKeyPool.length === 0) {
    throw new Error(
      "Tidak ada AI API key yang dikonfigurasi. " +
      "Tambahkan API key di halaman Settings → API Keys."
    );
  }

  type Provider = "openai" | "anthropic" | "google" | "agentrouter" | "aihubmix";
  const allProviders: Provider[] = ["openai", "anthropic", "google", "agentrouter", "aihubmix"];

  let tryOrder: Provider[];
  if (preferred === "auto") {
    // Urutan fallback: agentrouter → aihubmix (dengan key rotation) → openai → anthropic → google
    if (agentRouterKey) {
      tryOrder = ["agentrouter", "aihubmix", "openai", "anthropic", "google"];
    } else if (aihubmixKeyPool.length > 0) {
      tryOrder = ["aihubmix", "openai", "anthropic", "google"];
    } else {
      tryOrder = ["openai", "anthropic", "google"];
    }
  } else {
    tryOrder = [preferred as Provider, ...allProviders.filter((p) => p !== preferred)];
  }

  // Pisahkan error biasa vs rate-limit untuk pesan error yang lebih informatif
  const errors: string[]      = [];
  const rateLimited: string[] = [];

  for (const provider of tryOrder) {
    // Fix #3: AIHubMix punya key pool — coba setiap key sebelum circuit breaker
    if (provider === "aihubmix") {
      // Fix Issue C: aihubmixSucceeded dead variable dihapus.
      // Jika semua key dalam pool gagal/rate-limited, loop selesai dan
      // outer for-loop melanjutkan ke provider berikutnya secara alami.
      for (const key of aihubmixKeyPool) {
        // Buat circuit breaker key unik per API key (bukan per provider)
        const cbKey = `aihubmix:${key.slice(-8)}`; // 8 char suffix sebagai ID
        if (isCircuitOpen(cbKey)) {
          rateLimited.push(`[aihubmix:${key.slice(-8)}] circuit open — rate-limited`);
          continue;
        }
        const modelOpts = { ...options, model: modelOverride ?? options.model };
        try {
          // Return langsung jika berhasil — exit loop dan fungsi sekaligus
          return await generateWithAIHubMix(messages, key, modelOpts);
        } catch (e) {
          if (e instanceof RateLimitError) {
            openCircuit(cbKey, e.retryAfterMs);
            rateLimited.push(
              `[aihubmix:${key.slice(-8)}] rate limit${e.retryAfterMs ? ` (retry after ${Math.round(e.retryAfterMs / 1000)}s)` : ""}`
            );
            continue; // coba key berikutnya
          }
          errors.push(`[aihubmix:${key.slice(-8)}] ${(e as Error).message}`);
        }
      }
      // Semua key pool AIHubMix gagal → outer loop lanjut ke provider berikutnya
      continue;
    }

    // Skip jika circuit breaker terbuka (masih dalam pendinginan rate-limit)
    if (isCircuitOpen(provider)) {
      rateLimited.push(`[${provider}] circuit open — masih dalam pendinginan rate-limit`);
      continue;
    }

    const modelOpts = { ...options, model: modelOverride ?? options.model };

    try {
      if (provider === "agentrouter" && agentRouterKey) {
        return await generateWithAgentRouter(messages, agentRouterKey, modelOpts);
      }
      if (provider === "openai" && openaiKey) {
        return await generateWithOpenAI(messages, openaiKey, modelOpts);
      }
      if (provider === "anthropic" && anthropicKey) {
        return await generateWithAnthropic(messages, anthropicKey, modelOpts);
      }
      if (provider === "google" && googleKey) {
        return await generateWithGoogleAI(messages, googleKey, modelOpts);
      }
    } catch (e) {
      if (e instanceof RateLimitError) {
        // Buka circuit breaker — jangan hammer provider yang sedang rate-limited
        openCircuit(provider, e.retryAfterMs);
        rateLimited.push(
          `[${provider}] rate limit${e.retryAfterMs ? ` (retry after ${Math.round(e.retryAfterMs / 1000)}s)` : ""}`
        );
        continue; // langsung ke provider berikutnya
      }
      // Error non-rate-limit (5xx, network, dll)
      errors.push(`[${provider}] ${(e as Error).message}`);
    }
  }

  // Semua provider gagal — pesan error yang informatif
  const allIssues = [...rateLimited, ...errors];

  if (rateLimited.length > 0 && errors.length === 0) {
    throw new Error(
      `Semua AI provider sedang rate-limited. Coba lagi dalam beberapa saat.\n` +
      rateLimited.join("\n")
    );
  }

  throw new Error(`Semua AI provider gagal:\n${allIssues.join("\n")}`);
}
