/**
 * Tử Vi Số · Runtime settings store
 *
 * Single source of truth for site config that admins can mutate without
 * a redeploy: AI provider/key/model, site name, GA ID, feature flags.
 *
 * Lookup precedence:
 *   1. DB Setting row keyed by `${category}.${name}` (lowercase, dot-separated)
 *   2. process.env[ENV_NAME]            (fallback for fresh installs)
 *   3. Hardcoded default in this file   (last resort)
 *
 * Reads are cached in-process for `CACHE_TTL_MS` to avoid hammering the
 * DB on every API call. Writes invalidate the cache.
 */
import { prisma } from "@/lib/db";

const CACHE_TTL_MS = 30_000;

type CacheEntry = { value: string; expiresAt: number };
const cache = new Map<string, CacheEntry>();

export type SettingCategory = "ai" | "site" | "auth" | "seo";

/** Schema definition. `secret: true` means the value is never returned
 *  in plain form to admin GET endpoints — only a `<set>` / `<empty>` marker. */
export type SettingDef = {
  key: string;
  category: SettingCategory;
  envFallback: string | null;
  default: string;
  description: string;
  secret: boolean;
};

export const SETTINGS: SettingDef[] = [
  // ----- AI -----
  {
    key: "ai.provider",
    category: "ai",
    envFallback: "AI_PROVIDER",
    default: "auto",
    description: "AI provider: auto | mock | openai | anthropic",
    secret: false,
  },
  {
    key: "ai.openai_api_key",
    category: "ai",
    envFallback: "OPENAI_API_KEY",
    default: "",
    description: "OpenAI API key (sk-…). Empty = use mock provider.",
    secret: true,
  },
  {
    key: "ai.openai_base_url",
    category: "ai",
    envFallback: "OPENAI_BASE_URL",
    default: "https://api.openai.com/v1",
    description:
      "OpenAI-compatible endpoint base URL. Use this for Azure OpenAI, OpenRouter, Together, Groq, vLLM, Ollama, LiteLLM, etc. Must end at /v1 (no trailing slash). Default = official OpenAI.",
    secret: false,
  },
  {
    key: "ai.openai_model",
    category: "ai",
    envFallback: "OPENAI_MODEL",
    default: "gpt-4o-mini",
    description:
      "Model id used at /v1/chat/completions. Examples: gpt-4o-mini, gpt-4o, anthropic/claude-3.5-sonnet (OpenRouter), llama-3.1-70b-versatile (Groq), claude-sonnet-4 via litellm.",
    secret: false,
  },
  {
    key: "ai.anthropic_api_key",
    category: "ai",
    envFallback: "ANTHROPIC_API_KEY",
    default: "",
    description: "Anthropic API key (sk-ant-…). Optional.",
    secret: true,
  },
  {
    key: "ai.anthropic_model",
    category: "ai",
    envFallback: "ANTHROPIC_MODEL",
    default: "claude-3-5-sonnet-latest",
    description: "Anthropic model id",
    secret: false,
  },
  {
    key: "ai.daily_limit_free",
    category: "ai",
    envFallback: "AI_DAILY_LIMIT_FREE",
    default: "10",
    description: "Free tier daily AI request limit per user",
    secret: false,
  },
  // ----- Site -----
  {
    key: "site.url",
    category: "site",
    envFallback: "NEXT_PUBLIC_SITE_URL",
    default: "http://localhost:3000",
    description:
      "Public canonical URL (no trailing slash). Used for sitemap.xml, robots.txt, JSON-LD, OG tags, canonical links. Đổi domain chỉ cần sửa ở đây — không cần rebuild.",
    secret: false,
  },
  {
    key: "site.name",
    category: "site",
    envFallback: "NEXT_PUBLIC_SITE_NAME",
    default: "Tử Vi Số",
    description: "Site display name (header, footer, og:site_name)",
    secret: false,
  },
  {
    key: "site.tagline",
    category: "site",
    envFallback: null,
    default: "Diễn cầm tam thế",
    description: "Short slogan under the logo",
    secret: false,
  },
  {
    key: "site.contact_email",
    category: "site",
    envFallback: null,
    default: "lienhe@tuvi.local",
    description: "Public contact email shown on /lien-he",
    secret: false,
  },
  {
    key: "site.disclaimer",
    category: "site",
    envFallback: null,
    default:
      "Thông tin trên trang chỉ mang tính chất tham khảo, không thay thế tư vấn chuyên môn về y tế, tài chính hay pháp luật.",
    description: "Footer disclaimer text (legal)",
    secret: false,
  },
  // ----- SEO / Analytics -----
  {
    key: "seo.ga_id",
    category: "seo",
    envFallback: "NEXT_PUBLIC_GA_ID",
    default: "",
    description: "Google Analytics 4 measurement ID (G-XXXXXX). Empty = disabled.",
    secret: false,
  },
  {
    key: "seo.gtm_id",
    category: "seo",
    envFallback: "NEXT_PUBLIC_GTM_ID",
    default: "",
    description: "Google Tag Manager container ID (GTM-XXXXXX)",
    secret: false,
  },
];

const SETTINGS_BY_KEY: Record<string, SettingDef> = Object.fromEntries(
  SETTINGS.map((s) => [s.key, s])
);

/** Read a single setting. Falls back to env, then to hardcoded default. */
export async function getSetting(key: string): Promise<string> {
  const def = SETTINGS_BY_KEY[key];
  if (!def) throw new Error(`Unknown setting key: ${key}`);

  // Cache hit?
  const now = Date.now();
  const cached = cache.get(key);
  if (cached && cached.expiresAt > now) return cached.value;

  // DB
  let value: string | null = null;
  try {
    const row = await prisma.setting.findUnique({ where: { key } });
    if (row && row.value !== "") value = row.value;
  } catch {
    // DB unreachable during boot — fall through to env / default
  }

  // env fallback
  if (value === null && def.envFallback) {
    const envVal = process.env[def.envFallback];
    if (envVal && envVal !== "") value = envVal;
  }

  // hardcoded default
  if (value === null) value = def.default;

  cache.set(key, { value, expiresAt: now + CACHE_TTL_MS });
  return value;
}

/** Get a category as { key: value } map, with secrets MASKED for client safety. */
export async function getSettingsForCategory(
  category: SettingCategory,
  opts: { unmask?: boolean } = {}
): Promise<Array<SettingDef & { value: string; isSet: boolean }>> {
  const defs = SETTINGS.filter((s) => s.category === category);
  const result: Array<SettingDef & { value: string; isSet: boolean }> = [];
  for (const def of defs) {
    const raw = await getSetting(def.key);
    const isSet = raw !== "" && raw !== def.default;
    let displayValue = raw;
    if (def.secret && !opts.unmask) {
      displayValue = raw === "" ? "" : `••••${raw.slice(-4)}`;
    }
    result.push({ ...def, value: displayValue, isSet });
  }
  return result;
}

/** Upsert a setting. Empty string `value` deletes the row (revert to env/default). */
export async function setSetting(
  key: string,
  value: string,
  actorId: string | null
): Promise<void> {
  const def = SETTINGS_BY_KEY[key];
  if (!def) throw new Error(`Unknown setting key: ${key}`);

  if (value === "") {
    await prisma.setting.deleteMany({ where: { key } });
  } else {
    await prisma.setting.upsert({
      where: { key },
      create: {
        key,
        value,
        category: def.category,
        description: def.description,
        secret: def.secret,
        updatedBy: actorId,
      },
      update: {
        value,
        updatedBy: actorId,
      },
    });
  }
  cache.delete(key);
}

/** Force-clear the in-process cache. Useful in tests and after bulk imports. */
export function clearSettingsCache(): void {
  cache.clear();
}
