/**
 * لایه هوش مصنوعی با سرویس‌دهنده قابل انتخاب در پنل مدیریت:
 * - anthropic: Claude از طریق SDK رسمی Anthropic
 * - openai: هر سرویسی با API سازگار با OpenAI (OpenAI، OpenRouter، Gemini، DeepSeek و سرویس‌های ایرانی مثل AvalAI و GapGPT…)
 * کلید API با AES-256-GCM رمزگذاری‌شده در دیتابیس است و هرگز به مرورگر فرستاده نمی‌شود.
 * اگر کلیدی در پنل نباشد، متغیر محیطی AI_API_KEY (یا ANTHROPIC_API_KEY برای Claude) استفاده می‌شود.
 */
import "server-only";
import Anthropic from "@anthropic-ai/sdk";
import { betaZodOutputFormat } from "@anthropic-ai/sdk/helpers/beta/zod";
import { cache } from "react";
import type { z } from "zod";
import { db } from "@/lib/db";
import { decrypt } from "@/lib/crypto";

export type AiProvider = "anthropic" | "openai";

/** پیشنهادهای آماده در فرم تنظیمات (نام مدل سرویس‌های دیگر را مدیر از سرویس‌دهنده می‌گیرد) */
export const AI_PRESETS = [
  { id: "anthropic", provider: "anthropic", label: "Claude (Anthropic)", baseUrl: "", models: ["claude-opus-5-5", "claude-sonnet-5-5", "claude-haiku-5-5"] },
  { id: "openai", provider: "openai", label: "OpenAI", baseUrl: "https://api.openai.com/v1", models: [] },
  { id: "openrouter", provider: "openai", label: "OpenRouter (دسترسی به مدل‌های مختلف)", baseUrl: "https://openrouter.ai/api/v1", models: [] },
  { id: "gemini", provider: "openai", label: "Google Gemini", baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai", models: [] },
  { id: "deepseek", provider: "openai", label: "DeepSeek", baseUrl: "https://api.deepseek.com", models: [] },
  { id: "custom", provider: "openai", label: "سرویس دیگر سازگار با OpenAI (مثلاً سرویس‌های ایرانی)", baseUrl: "", models: [] },
] as const;

export interface AiSettings {
  /** روشن/خاموش کل امکانات هوش مصنوعی */
  enabled: boolean;
  /** گفت‌وگوی AI Beauty در سایت */
  chatEnabled: boolean;
  provider: AiProvider;
  /** شناسه پیش‌تنظیم انتخاب‌شده در فرم (فقط برای نمایش) */
  preset: string;
  model: string;
  /** کلید رمزگذاری‌شده (v1.iv.tag.data) */
  apiKeyEnc?: string;
  apiKeyLast4?: string;
  /** آدرس API؛ برای Claude خالی = آدرس رسمی، برای سرویس‌های سازگار با OpenAI الزامی */
  baseUrl?: string;
  /** عمق پاسخ (فقط Claude) */
  effort: "low" | "medium" | "high";
  assistantName: string;
  /** دستورالعمل اضافه مدیر برای لحن و رفتار دستیار */
  instructions: string;
  /** تعداد پیامی که مهمان (بدون ورود) می‌تواند بفرستد؛ بعد از آن باید ثبت‌نام کند */
  guestMessages: number;
  /** اجازه خزش ربات‌های هوش مصنوعی (GPTBot، ClaudeBot، PerplexityBot …) برای GEO */
  allowAiCrawlers: boolean;
}

export const AI_DEFAULTS: AiSettings = {
  enabled: false,
  chatEnabled: true,
  provider: "anthropic",
  preset: "anthropic",
  model: "claude-opus-5-5",
  effort: "low",
  assistantName: "دستیار زیبایی زهورا",
  instructions: "",
  guestMessages: 3,
  allowAiCrawlers: true,
};

export const getAiSettings = cache(async (): Promise<AiSettings> => {
  const row = await db.setting.findUnique({ where: { key: "ai" } });
  const v = (row?.value ?? {}) as Partial<AiSettings>;
  const provider: AiProvider = v.provider === "openai" ? "openai" : "anthropic";
  return { ...AI_DEFAULTS, ...v, provider, model: v.model?.trim() || AI_DEFAULTS.model };
});

export async function saveAiSettings(next: AiSettings) {
  await db.setting.upsert({ where: { key: "ai" }, create: { key: "ai", value: { ...next } }, update: { value: { ...next } } });
}

function apiKeyOf(s: AiSettings): string | undefined {
  if (s.apiKeyEnc) {
    try {
      return decrypt(s.apiKeyEnc);
    } catch {
      console.error("[ai] رمزگشایی کلید API ممکن نشد (DATA_ENCRYPTION_KEY عوض شده؟)");
    }
  }
  return process.env.AI_API_KEY || (s.provider === "anthropic" ? process.env.ANTHROPIC_API_KEY : undefined) || undefined;
}

// ── رابط مشترک سرویس‌دهنده‌ها ─────────────────────────────

export type ChatMessage = { role: "user" | "assistant"; content: string };
/** بخش‌های پرامپت سیستم؛ cache=true یعنی این بخش ثابت است و (در Claude) کش می‌شود */
export type SystemPart = { text: string; cache?: boolean };

export interface AiEngine {
  /** پاسخ جریانی؛ تکه‌های متن را برمی‌گرداند */
  streamChat(opts: { system: SystemPart[]; messages: ChatMessage[]; maxTokens: number }): AsyncGenerator<string>;
  /** خروجی JSON مطابق اسکیما (برای پیشنهاد سئو)؛ null یعنی پاسخ معتبر نبود */
  json<T>(opts: { system: string; user: string; schema: z.ZodType<T>; keysHint: string }): Promise<T | null>;
  /** یک درخواست کوچک برای تست اتصال */
  ping(): Promise<{ reply: string; model: string }>;
}

export class AiError extends Error {}

// ── Claude (Anthropic SDK) ─────────────────────────────

/** fallback سمت سرور در صورت رد شدن درخواست توسط مدل (Opus و Sonnet؛ Haiku پشتیبانی نمی‌کند) */
function fallbackParams(model: string) {
  return /^claude-(opus|sonnet)-5-5/.test(model) ? { betas: ["server-side-fallback-2026-07-01"], fallbacks: "default" as const } : {};
}

function anthropicEngine(s: AiSettings, apiKey: string): AiEngine {
  const client = new Anthropic({ apiKey, baseURL: s.baseUrl || undefined, timeout: 60_000, maxRetries: 1 });
  return {
    async *streamChat({ system, messages, maxTokens }) {
      const stream = client.beta.messages.stream({
        model: s.model,
        max_tokens: maxTokens,
        output_config: { effort: s.effort },
        system: system.map((p) => ({ type: "text" as const, text: p.text, ...(p.cache ? { cache_control: { type: "ephemeral" as const } } : {}) })),
        messages,
        ...fallbackParams(s.model),
      });
      for await (const event of stream) {
        if (event.type === "content_block_delta" && event.delta.type === "text_delta") yield event.delta.text;
      }
      const final = await stream.finalMessage();
      if (final.stop_reason === "refusal") yield "\n\nمتأسفم، نمی‌توانم به این سؤال پاسخ بدهم. درباره مراقبت پوست، آرایش یا محصولات زهورا بپرسید.";
    },
    async json({ system, user, schema }) {
      const res = await client.beta.messages.parse({
        model: s.model,
        max_tokens: 16000,
        output_config: { effort: "low", format: betaZodOutputFormat(schema) },
        system,
        messages: [{ role: "user", content: user }],
        ...fallbackParams(s.model),
      });
      return res.stop_reason === "refusal" ? null : (res.parsed_output ?? null);
    },
    async ping() {
      const res = await client.beta.messages.create({
        model: s.model,
        max_tokens: 1000,
        output_config: { effort: "low" },
        messages: [{ role: "user", content: "فقط بنویس: اتصال برقرار است" }],
        ...fallbackParams(s.model),
      });
      return { reply: res.content.flatMap((b) => (b.type === "text" ? [b.text] : [])).join(" ").trim(), model: res.model };
    },
  };
}

// ── سرویس‌های سازگار با OpenAI (Chat Completions) ──────────

function openaiEngine(s: AiSettings, apiKey: string): AiEngine {
  const base = (s.baseUrl ?? "").replace(/\/+$/, "");
  async function call(body: Record<string, unknown>) {
    let res: Response;
    try {
      res = await fetch(`${base}/chat/completions`, {
        method: "POST",
        headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
        body: JSON.stringify({ model: s.model, ...body }),
        signal: AbortSignal.timeout(60_000),
      });
    } catch {
      throw new AiError("اتصال به سرویس هوش مصنوعی برقرار نشد (آدرس API یا اینترنت سرور را بررسی کنید)");
    }
    if (!res.ok) {
      const detail = await res.text().catch(() => "");
      if (res.status === 401 || res.status === 403) throw new AiError("کلید API نامعتبر است یا اجازه دسترسی ندارد");
      if (res.status === 404) throw new AiError("مدل یا آدرس API پیدا نشد");
      if (res.status === 429) throw new AiError("محدودیت تعداد درخواست یا اعتبار حساب تمام شده است");
      throw new AiError(`خطای سرویس هوش مصنوعی (${res.status}) ${detail.slice(0, 120)}`);
    }
    return res;
  }
  return {
    async *streamChat({ system, messages, maxTokens }) {
      const res = await call({ stream: true, max_tokens: maxTokens, messages: [{ role: "system", content: system.map((p) => p.text).join("\n\n") }, ...messages] });
      const reader = res.body!.getReader();
      const decoder = new TextDecoder();
      let buf = "";
      for (;;) {
        const { value, done } = await reader.read();
        if (done) break;
        buf += decoder.decode(value, { stream: true });
        const lines = buf.split("\n");
        buf = lines.pop() ?? "";
        for (const line of lines) {
          const data = line.trim().replace(/^data:\s*/, "");
          if (!line.trim().startsWith("data:") || data === "[DONE]") continue;
          try {
            const text = JSON.parse(data)?.choices?.[0]?.delta?.content;
            if (typeof text === "string" && text) yield text;
          } catch {
            /* خط ناقص یا keep-alive */
          }
        }
      }
    },
    async json({ system, user, schema, keysHint }) {
      const messages = [
        { role: "system", content: `${system}\nخروجی فقط یک شیء JSON معتبر با این کلیدها باشد و هیچ متن دیگری ننویس: ${keysHint}` },
        { role: "user", content: user },
      ];
      // بعضی سرویس‌ها response_format را نمی‌شناسند؛ در آن صورت بدون آن دوباره تلاش می‌شود
      let res: Response;
      try {
        res = await call({ messages, max_tokens: 4000, response_format: { type: "json_object" } });
      } catch (e) {
        if (e instanceof AiError && /\(400\)/.test(e.message)) res = await call({ messages, max_tokens: 4000 });
        else throw e;
      }
      const text: string = (await res.json())?.choices?.[0]?.message?.content ?? "";
      const match = text.match(/\{[\s\S]*\}/);
      if (!match) return null;
      try {
        const parsed = schema.safeParse(JSON.parse(match[0]));
        return parsed.success ? parsed.data : null;
      } catch {
        return null;
      }
    },
    async ping() {
      const res = await call({ max_tokens: 50, messages: [{ role: "user", content: "فقط بنویس: اتصال برقرار است" }] });
      const j = await res.json();
      return { reply: String(j?.choices?.[0]?.message?.content ?? "").trim(), model: String(j?.model ?? s.model) };
    },
  };
}

/** سرویس هوش مصنوعی اگر فعال و کامل تنظیم شده باشد؛ وگرنه null */
export async function aiEngine(): Promise<{ engine: AiEngine; settings: AiSettings } | null> {
  const settings = await getAiSettings();
  const apiKey = apiKeyOf(settings);
  if (!settings.enabled || !apiKey || !settings.model) return null;
  if (settings.provider === "openai") {
    if (!settings.baseUrl) return null;
    return { engine: openaiEngine(settings, apiKey), settings };
  }
  return { engine: anthropicEngine(settings, apiKey), settings };
}

export async function aiReady() {
  return (await aiEngine()) !== null;
}

/** پیام خطای قابل فهم برای مدیر */
export function aiErrorMessage(e: unknown): string {
  if (e instanceof AiError) return e.message;
  if (e instanceof Anthropic.AuthenticationError) return "کلید API نامعتبر است";
  if (e instanceof Anthropic.PermissionDeniedError) return "این کلید اجازه دسترسی ندارد (یا سرویس از محل سرور در دسترس نیست)";
  if (e instanceof Anthropic.RateLimitError) return "محدودیت تعداد درخواست؛ کمی بعد دوباره امتحان کنید";
  if (e instanceof Anthropic.NotFoundError) return "مدل یا آدرس API پیدا نشد";
  if (e instanceof Anthropic.BadRequestError) return `درخواست نامعتبر: ${e.message}`;
  if (e instanceof Anthropic.APIConnectionError) return "اتصال به سرور هوش مصنوعی برقرار نشد (اینترنت سرور یا آدرس API را بررسی کنید)";
  if (e instanceof Anthropic.APIError) return `خطای سرویس هوش مصنوعی (${e.status ?? "?"})`;
  return "خطای نامشخص در ارتباط با هوش مصنوعی";
}
