import { createClient } from "@supabase/supabase-js";
import type { Article } from "./types";
import { rewriteLegacyImageUrl, cleanLegacyImages } from "@/lib/legacy-image";

const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const SUPABASE_ANON = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
const SCHEMA = process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "av";

function client() {
  return createClient(SUPABASE_URL, SUPABASE_ANON, {
    db: { schema: SCHEMA as "public" },
    auth: { persistSession: false },
  });
}

interface ImportedPostRow {
  wp_id: number;
  wp_slug: string;
  title: string;
  excerpt: string | null;
  content: string | null;
  author_name: string | null;
  category: string | null;
  tags: string[] | null;
  featured_image: string | null;
  featured_image_alt: string | null;
  status: string;
  wp_published_at: string | null;
  noindex: boolean | null;
}

interface RewriteRow {
  id: string;
  slug: string;
  title: string;
  excerpt: string | null;
  body: string;
  category: string | null;
  tags: string[] | null;
  status: string;
  published_at: string | null;
  created_at: string;
  persona: PersonaJoin | PersonaJoin[] | null;
}

interface PersonaJoin {
  slug: string;
  name: string;
  beat: string | null;
  avatar_url: string | null;
}

const FALLBACK_IMAGE = "/assets/images/article-placeholder-v2.svg";
const REWRITE_FALLBACK_IMAGE = "/assets/images/article-placeholder-v2.svg";

type Section = Article["section"];
const SECTION_TAGS: Record<Section, string[]> = {
  gear: [
    "Audio", "Cameras", "lighting", "Lighting", "Reviews",
    "Blackmagic Design", "blackmagic", "Sony", "Canon", "ARRI", "RED",
    "monitoring", "Test and Measurement",
  ],
  technology: [
    "AI", "Streaming", "OTT", "ip", "IP", "Cloud",
    "AV", "audio-over-IP interfaces",
    "IP Workflows", "Live Production", "Cloud Production",
    "MAM", "Storage", "Encoding",
  ],
  "show-coverage": [
    "NAB", "NAB Show", "NAB 2025", "NAB 2026",
    "IBC", "IBC 2025", "IBC 2026",
    "BroadcastAsia", "BroadcastAsia 2026", "Broadcast Asia 2026",
    "InfoComm", "MPTS",
  ],
  news: [],
};

// Map between site sections and the AI-derived categories now stored in
// bb.wp_imported_posts.category. Each section query uses these to pull
// only the relevant slice of the archive — otherwise /gear and
// /technology would both return everything that doesn't match
// show-coverage (the old fallback).
//   Production-tech categories live here:
//     "Streaming & IP", "Cloud & Workflow", "AI & Automation",
//     "Live Production", "Post Production", "Broadcast Production"
//   Hardware/gear:
//     "Cameras & Capture", "Audio"
const SECTION_CATEGORIES: Record<Section, string[]> = {
  gear: ["Cameras & Capture", "Audio"],
  technology: [
    "Streaming & IP",
    "Cloud & Workflow",
    "AI & Automation",
    "Live Production",
    "Post Production",
    "Broadcast Production",
  ],
  "show-coverage": ["Show Coverage"],
  news: ["Industry News"],
};

function authorSlug(name: string | null): string {
  if (!name) return "staff-reporter";
  return (
    name
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "-")
      .replace(/^-+|-+$/g, "")
      .slice(0, 60) || "staff-reporter"
  );
}

function formatDate(iso: string | null): string {
  if (!iso) return "";
  const d = new Date(iso);
  if (isNaN(d.getTime())) return "";
  return d.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" });
}

function readTime(content: string | null): string {
  if (!content) return "1 min read";
  const text = content.replace(/<[^>]+>/g, " ");
  const words = text.trim().split(/\s+/).filter(Boolean).length;
  const minutes = Math.max(1, Math.round(words / 200));
  return `${minutes} min read`;
}

function rowToArticle(row: ImportedPostRow, section: Section = "news"): Article {
  const image = row.featured_image
    ? rewriteLegacyImageUrl(row.featured_image)
    : FALLBACK_IMAGE;
  const content = cleanLegacyImages(row.content || "");
  const author = row.author_name || "AV Beat";
  return {
    slug: row.wp_slug,
    section,
    title: row.title,
    excerpt: row.excerpt || "",
    category: (row.category || "NEWS").toUpperCase(),
    date: formatDate(row.wp_published_at),
    author,
    authorSlug: authorSlug(author),
    authorTitle: "Contributor",
    authorAvatar: FALLBACK_IMAGE,
    image,
    alt: row.featured_image_alt || row.title,
    readTime: readTime(row.content),
    tags: row.tags || [],
    content,
    noindex: !!row.noindex,
  };
}

function inferSectionFromCategory(cat: string | null): Section {
  if (!cat) return "news";
  const c = cat.toLowerCase();
  if (c === "post production" || c === "post-production") return "technology";
  if (c === "streaming" || c === "technology" || c === "live production") return "technology";
  if (c === "sports") return "news";
  if (c === "business") return "news";
  return "news";
}

function rewriteRowToArticle(row: RewriteRow, sectionOverride?: Section): Article {
  const sortedPublishedAt = row.published_at || row.created_at;
  const persona = Array.isArray(row.persona) ? row.persona[0] || null : row.persona || null;
  const author = persona?.name || "AV Beat";
  const personaSlug = persona?.slug || "staff-reporter";
  const avatar = persona?.avatar_url || FALLBACK_IMAGE;
  return {
    slug: row.slug,
    section: sectionOverride || inferSectionFromCategory(row.category),
    title: row.title,
    excerpt: row.excerpt || "",
    category: (row.category || "NEWS").toUpperCase(),
    date: formatDate(sortedPublishedAt),
    author,
    authorSlug: personaSlug,
    authorTitle: persona?.beat ? humanBeat(persona.beat) : "AV Beat",
    authorAvatar: avatar,
    image: REWRITE_FALLBACK_IMAGE,
    alt: row.title,
    readTime: readTime(row.body),
    tags: row.tags || [],
    content: row.body,
  };
}

function humanBeat(beat: string): string {
  return beat
    .split(/[-_]/)
    .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
    .join(" ")
    .replace("M And A", "M&A");
}

const SELECT_COLS =
  "wp_id,wp_slug,title,excerpt,content,author_name,category,tags,featured_image,featured_image_alt,status,wp_published_at,noindex";

const REWRITE_SELECT_COLS =
  "id,slug,title,excerpt,body,category,tags,status,published_at,created_at,persona:ai_personas(slug,name,beat,avatar_url)";

function rewriteOrderKey(a: RewriteRow): number {
  return new Date(a.published_at || a.created_at || 0).getTime();
}
function legacyOrderKey(a: ImportedPostRow): number {
  return new Date(a.wp_published_at || 0).getTime();
}

async function fetchPublishedRewrites(limit: number): Promise<RewriteRow[]> {
  try {
    const { data, error } = await client()
      .from("ai_rewritten_articles")
      .select(REWRITE_SELECT_COLS)
      .eq("status", "published")
      .order("published_at", { ascending: false, nullsFirst: false })
      .limit(limit);
    if (error || !data) return [];
    return data as unknown as RewriteRow[];
  } catch {
    return [];
  }
}

export async function getLegacyArticleBySlug(slug: string): Promise<Article | null> {
  try {
    const { data, error } = await client()
      .from("wp_imported_posts")
      .select(SELECT_COLS)
      .eq("wp_slug", slug)
      .eq("status", "published")
      .maybeSingle();
    if (!error && data) return rowToArticle(data as ImportedPostRow);
  } catch {
    // fall through
  }
  // Try AI rewrites
  try {
    const { data, error } = await client()
      .from("ai_rewritten_articles")
      .select(REWRITE_SELECT_COLS)
      .eq("slug", slug)
      .eq("status", "published")
      .maybeSingle();
    if (!error && data) return rewriteRowToArticle(data as unknown as RewriteRow);
  } catch {
    // fall through
  }
  return null;
}

export async function getLegacyRelatedArticles(
  excludeSlug: string,
  category: string | null,
  count: number,
): Promise<Article[]> {
  const collected: ImportedPostRow[] = [];
  const seen = new Set<string>([excludeSlug]);
  // First pass: same category. Skip when no category recorded.
  if (category) {
    try {
      const { data } = await client()
        .from("wp_imported_posts")
        .select(SELECT_COLS)
        .eq("status", "published")
        .eq("category", category)
        .neq("wp_slug", excludeSlug)
        .order("wp_published_at", { ascending: false })
        .limit(count);
      for (const row of (data as ImportedPostRow[] | null) || []) {
        if (!seen.has(row.wp_slug)) { collected.push(row); seen.add(row.wp_slug); }
      }
    } catch {}
  }
  // Fallback: top up to `count` with the latest published, excluding what we
  // already have. Handles tiny categories, orphan rows, and category=null.
  if (collected.length < count) {
    try {
      const { data } = await client()
        .from("wp_imported_posts")
        .select(SELECT_COLS)
        .eq("status", "published")
        .neq("wp_slug", excludeSlug)
        .order("wp_published_at", { ascending: false })
        .limit(count + collected.length + 5);
      for (const row of (data as ImportedPostRow[] | null) || []) {
        if (collected.length >= count) break;
        if (!seen.has(row.wp_slug)) { collected.push(row); seen.add(row.wp_slug); }
      }
    } catch {}
  }
  return collected.map((r) => rowToArticle(r));
}

export async function getLegacyArticlesBySection(
  section: Section,
  limit = 200,
): Promise<Article[]> {
  let legacy: ImportedPostRow[] = [];
  let rewrites: RewriteRow[] = [];

  try {
    let q = client()
      .from("wp_imported_posts")
      .select(SELECT_COLS)
      .eq("status", "published")
      // Google News-safe surface: only show articles that have been AI-rewritten
      // into original editorial. Raw press-release imports are excluded.
      .not("ai_rewritten_at", "is", null)
      .order("wp_published_at", { ascending: false })
      .limit(limit);
    const tags = SECTION_TAGS[section];
    if (tags.length > 0) q = q.overlaps("tags", tags);
    const { data, error } = await q;
    if (!error && data) legacy = data as ImportedPostRow[];
  } catch {
    // ignore
  }

  try {
    let q = client()
      .from("ai_rewritten_articles")
      .select(REWRITE_SELECT_COLS)
      .eq("status", "published")
      .order("published_at", { ascending: false, nullsFirst: false })
      .limit(limit);
    const cats = SECTION_CATEGORIES[section];
    if (cats.length > 0) q = q.in("category", cats);
    const { data, error } = await q;
    if (!error && data) rewrites = data as unknown as RewriteRow[];
  } catch {
    // ignore
  }

  const merged: Array<{ ts: number; article: Article }> = [
    ...legacy.map((r) => ({ ts: legacyOrderKey(r), article: rowToArticle(r, section) })),
    ...rewrites.map((r) => ({ ts: rewriteOrderKey(r), article: rewriteRowToArticle(r, section) })),
  ];
  merged.sort((a, b) => b.ts - a.ts);
  return merged.slice(0, limit).map((m) => m.article);
}

export async function getLegacyFeaturedArticles(limit = 200): Promise<Article[]> {
  try {
    // Show every row in the Featured category, but bubble editorial pins
    // (featured=true) to the top. Strict "featured=true only" filtering was
    // tried briefly and left the page near-empty (1 row); the broader pool
    // gives the page real depth while pins still lead.
    const { data, error } = await client()
      .from("wp_imported_posts")
      .select(SELECT_COLS + ",featured")
      .eq("status", "published")
      .ilike("category", "featured")
      .order("featured", { ascending: false })
      .order("wp_published_at", { ascending: false, nullsFirst: false })
      .limit(limit);
    if (error || !data) return [];
    return (data as ImportedPostRow[]).map((r) => rowToArticle(r));
  } catch {
    return [];
  }
}

// Default raised from 50 → 5000 because users expect search to return
// every match. The 29k-article archive is small enough that title/excerpt
// ilike filters stay fast even without an explicit cap. Pass a smaller
// `limit` (e.g. 10) for autocomplete-style typeahead queries.
export async function searchLegacyArticles(
  query: string,
  limit = 5000,
): Promise<Article[]> {
  const q = (query || "").trim();
  if (!q) return [];
  const like = `%${q.replace(/[%_]/g, " ")}%`;

  let legacy: ImportedPostRow[] = [];
  let rewrites: RewriteRow[] = [];

  try {
    const { data, error } = await client()
      .from("wp_imported_posts")
      .select(SELECT_COLS)
      .eq("status", "published")
      .or(`title.ilike.${like},excerpt.ilike.${like},author_name.ilike.${like}`)
      .order("wp_published_at", { ascending: false })
      .limit(limit);
    if (!error && data) legacy = data as ImportedPostRow[];
  } catch {
    // ignore
  }

  try {
    const { data, error } = await client()
      .from("ai_rewritten_articles")
      .select(REWRITE_SELECT_COLS)
      .eq("status", "published")
      .or(`title.ilike.${like},excerpt.ilike.${like}`)
      .order("published_at", { ascending: false, nullsFirst: false })
      .limit(limit);
    if (!error && data) rewrites = data as unknown as RewriteRow[];
  } catch {
    // ignore
  }

  const merged: Array<{ ts: number; article: Article }> = [
    ...legacy.map((r) => ({ ts: legacyOrderKey(r), article: rowToArticle(r, "news") })),
    ...rewrites.map((r) => ({ ts: rewriteOrderKey(r), article: rewriteRowToArticle(r) })),
  ];
  merged.sort((a, b) => b.ts - a.ts);
  return merged.slice(0, limit).map((m) => m.article);
}

/** Pick keywords from an article title to match against forum threads.
    Drops short/common words; keeps brand/product nouns. */
function titleKeywords(s: string): string[] {
  const STOP = new Set([
    "the","a","an","and","or","but","of","for","to","in","on","at","with","from",
    "by","as","is","are","was","were","be","been","being","this","that","these",
    "those","it","its","new","now","more","most","how","why","when","where","what",
    "who","which","also","plus","via","over","under","into","than","then","each",
    "all","any","every","some","just","one","two","three","four","five","six",
  ]);
  return Array.from(new Set(
    s.toLowerCase()
      .replace(/[^a-z0-9 -]+/g, " ")
      .split(/\s+/)
      .filter((w) => w.length >= 4 && !STOP.has(w))
  )).slice(0, 6);
}

export interface RelatedForumThread {
  id: string;
  title: string;
  body: string;
  reply_count: number;
  view_count: number;
  vote_score: number;
  last_reply_at: string | null;
  category_slug: string | null;
  category_name: string | null;
}

/** Find forum threads relevant to an article's title. Strategy: extract
    longish title tokens, match any against forum_threads.title (ILIKE OR),
    rank by reply_count + recency, cap at `limit`. Falls back to recently-
    active threads if no keyword matches. */
export async function getRelatedForumThreads(
  articleTitle: string,
  limit = 6,
): Promise<RelatedForumThread[]> {
  const kws = titleKeywords(articleTitle);
  let rows: any[] = [];
  if (kws.length > 0) {
    const filter = kws.map((k) => `title.ilike.%${k.replace(/[%_]/g, "")}%`).join(",");
    try {
      const { data } = await client()
        .from("forum_threads")
        .select("id, title, body, reply_count, view_count, vote_score, last_reply_at, category_id")
        .or(filter)
        .order("reply_count", { ascending: false })
        .order("last_reply_at", { ascending: false, nullsFirst: false })
        .limit(limit * 2);
      rows = data || [];
    } catch { /* ignore */ }
  }
  if (rows.length < limit) {
    // Top up with recently-active threads so we never render an empty box.
    try {
      const { data } = await client()
        .from("forum_threads")
        .select("id, title, body, reply_count, view_count, vote_score, last_reply_at, category_id")
        .order("last_reply_at", { ascending: false, nullsFirst: false })
        .limit(limit * 2);
      const seen = new Set(rows.map((r) => r.id));
      for (const r of data || []) {
        if (rows.length >= limit * 2) break;
        if (!seen.has(r.id)) rows.push(r);
      }
    } catch { /* ignore */ }
  }
  // Resolve category slug/name in one batch
  const catIds = Array.from(new Set(rows.map((r) => r.category_id).filter(Boolean)));
  let catById = new Map<string, { slug: string; name: string }>();
  if (catIds.length) {
    try {
      const { data } = await client()
        .from("forum_categories")
        .select("id, slug, name")
        .in("id", catIds);
      for (const c of data || []) catById.set(c.id, { slug: c.slug, name: c.name });
    } catch { /* ignore */ }
  }
  return rows.slice(0, limit).map((r) => ({
    id: r.id,
    title: r.title,
    body: r.body,
    reply_count: r.reply_count ?? 0,
    view_count: r.view_count ?? 0,
    vote_score: r.vote_score ?? 0,
    last_reply_at: r.last_reply_at,
    category_slug: catById.get(r.category_id)?.slug || null,
    category_name: catById.get(r.category_id)?.name || null,
  }));
}

export async function getLegacyRecentSlugs(limit = 200): Promise<string[]> {
  let legacy: string[] = [];
  let rewrites: string[] = [];
  try {
    const { data } = await client()
      .from("wp_imported_posts")
      .select("wp_slug")
      .eq("status", "published")
      .order("wp_published_at", { ascending: false })
      .limit(limit);
    legacy = (data || []).map((r: { wp_slug: string }) => r.wp_slug);
  } catch {
    // ignore
  }
  try {
    const { data } = await client()
      .from("ai_rewritten_articles")
      .select("slug,published_at")
      .eq("status", "published")
      .order("published_at", { ascending: false, nullsFirst: false })
      .limit(limit);
    rewrites = (data || []).map((r: { slug: string }) => r.slug);
  } catch {
    // ignore
  }
  return [...rewrites, ...legacy].slice(0, limit);
}

export async function getLegacyRecentSitemapEntries(
  limit = 25000,
): Promise<{ slug: string; date: string }[]> {
  const out: { slug: string; date: string }[] = [];

  // PostgREST caps each response at max_rows (1000 by default), so we
  // page with explicit Range headers via .range() until either limit is
  // hit OR we get a short page back (end of data).
  const PAGE = 1000;
  async function pageThrough(
    table: string,
    selectCols: string,
    orderCol: string,
    slugCol: string,
    dateCols: string[],
    cap: number,
  ) {
    let from = 0;
    while (from < cap) {
      const to = Math.min(from + PAGE - 1, cap - 1);
      let q = client().from(table).select(selectCols).eq("status", "published");
      q = q.order(orderCol, { ascending: false, nullsFirst: false });
      const { data, error } = await q.range(from, to);
      if (error) break;
      const rows = (data || []) as any[];
      if (rows.length === 0) break;
      for (const r of rows) {
        const slug = r[slugCol];
        if (!slug) continue;
        let date: string | null = null;
        for (const c of dateCols) {
          if (r[c]) { date = r[c]; break; }
        }
        out.push({ slug, date: date || new Date().toISOString() });
      }
      if (rows.length < PAGE) break;
      from += rows.length;
    }
  }

  try {
    await pageThrough(
      "wp_imported_posts",
      "wp_slug,wp_published_at",
      "wp_published_at",
      "wp_slug",
      ["wp_published_at"],
      limit,
    );
  } catch {
    // ignore
  }
  try {
    await pageThrough(
      "ai_rewritten_articles",
      "slug,published_at,created_at",
      "published_at",
      "slug",
      ["published_at", "created_at"],
      limit,
    );
  } catch {
    // ignore
  }
  out.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
  return out.slice(0, limit);
}

export async function getRecentAiRewrites(limit = 20): Promise<Article[]> {
  const rows = await fetchPublishedRewrites(limit);
  return rows.map((r) => rewriteRowToArticle(r));
}

export async function getLatestImportedArticles(
  limit = 100,
  offset = 0,
): Promise<{ items: Article[]; total: number }> {
  try {
    const { data, error, count } = await client()
      .from("wp_imported_posts")
      .select(SELECT_COLS, { count: "exact" })
      .eq("status", "published")
      .order("wp_published_at", { ascending: false, nullsFirst: false })
      .range(offset, offset + limit - 1);
    if (error || !data) return { items: [], total: 0 };
    return {
      items: (data as ImportedPostRow[]).map((r) => rowToArticle(r)),
      total: count ?? data.length,
    };
  } catch {
    return { items: [], total: 0 };
  }
}
