/**
 * Ghost Content API client. Runs at build time under Node only — the API key is
 * read from the environment here and never reaches the browser bundle.
 *
 * Direct fetch is used rather than @tryghost/content-api: this site needs one
 * read-only endpoint, and the SDK would add a dependency for a single GET.
 */

import fs from "node:fs";
import path from "node:path";
import type { GhostConfig, GhostPost, GhostPostsResponse } from "./types";

/** Only posts carrying this tag are published to this site. */
export const SITE_TAG = "lynkeduppro-new";

const CACHE_DIR = path.resolve("node_modules/.cache/ghost");
const CACHE_FILE = path.join(CACHE_DIR, `posts-${SITE_TAG}.json`);

/**
 * Reads Ghost credentials from the environment.
 * @throws if either required variable is missing.
 */
export function readGhostConfig(env: Record<string, string | undefined>): GhostConfig {
  const apiUrl = env.GHOST_API_URL?.trim();
  const contentApiKey = env.GHOST_CONTENT_API_KEY?.trim();

  const missing = [
    !apiUrl && "GHOST_API_URL",
    !contentApiKey && "GHOST_CONTENT_API_KEY",
  ].filter(Boolean);

  if (missing.length) {
    throw new Error(
      `Missing Ghost environment variable(s): ${missing.join(", ")}. ` +
        `Copy .env.example to .env and fill them in (see README of .env.example).`
    );
  }

  return {
    apiUrl: apiUrl!.replace(/\/+$/, ""),
    contentApiKey: contentApiKey!,
    siteUrl: (env.SITE_URL?.trim() || "https://lynkeduppro.com").replace(/\/+$/, ""),
  };
}

function readCache(): GhostPost[] | null {
  try {
    return JSON.parse(fs.readFileSync(CACHE_FILE, "utf8")) as GhostPost[];
  } catch {
    return null;
  }
}

function writeCache(posts: GhostPost[]): void {
  try {
    fs.mkdirSync(CACHE_DIR, { recursive: true });
    fs.writeFileSync(CACHE_FILE, JSON.stringify(posts), "utf8");
  } catch {
    /* Cache is an optimisation; a failure to persist must not fail the build. */
  }
}

/**
 * Fetches every published post tagged {@link SITE_TAG}, newest first.
 *
 * Always hits Ghost — one request per dev-server start or build is necessary, not
 * wasteful, and serving a TTL-cached copy here would silently hide freshly
 * published posts. The on-disk copy is only a fallback for when Ghost is
 * unreachable, so a dropped connection doesn't stop local work.
 *
 * @param allowStaleFallback Fall back to the last good response if the fetch
 *   fails. Dev only: a production build must fail loudly rather than publish
 *   stale content.
 */
export async function fetchSitePosts(
  config: GhostConfig,
  { allowStaleFallback = false }: { allowStaleFallback?: boolean } = {}
): Promise<GhostPost[]> {
  const url = new URL(`${config.apiUrl}/ghost/api/content/posts/`);
  url.searchParams.set("key", config.contentApiKey);
  url.searchParams.set("filter", `tag:${SITE_TAG}`);
  url.searchParams.set("include", "tags,authors");
  url.searchParams.set("limit", "all");
  url.searchParams.set("order", "published_at DESC");

  try {
    const res = await fetch(url, { headers: { "Accept-Version": "v5.0" } });

    if (!res.ok) {
      const body = await res.text().catch(() => "");
      throw new Error(
        `Ghost API responded ${res.status} ${res.statusText} for ${config.apiUrl}. ${body.slice(0, 300)}`
      );
    }

    const data = (await res.json()) as GhostPostsResponse;
    const posts = data.posts ?? [];
    writeCache(posts);
    return posts;
  } catch (error) {
    if (!allowStaleFallback) throw error;
    const cached = readCache();
    if (!cached) throw error;
    console.warn(
      `[ghost] fetch failed (${error instanceof Error ? error.message : String(error)}).\n` +
        `[ghost] Falling back to the last cached response (${cached.length} post(s)) — may be out of date.`
    );
    return cached;
  }
}
