"use server";

import { requireAdmin } from "@/lib/admin-guard";
import { prisma } from "@/lib/db";

export type SchemaCheck = {
  level: "ok" | "warn" | "error";
  message: string;
};

export type SchemaItem = {
  type: string;
  raw: unknown;
  checks: SchemaCheck[];
};

export type ValidateResult = {
  ok: boolean;
  url?: string;
  fetchedAt?: string;
  status?: number;
  items?: SchemaItem[];
  rawBlocks?: number;
  pageTitle?: string;
  pageDescription?: string;
  error?: string;
};

/**
 * Fetch a URL, extract every <script type="application/ld+json"> block,
 * parse its JSON, and run a small set of common validation rules per @type.
 * Returns one entry per top-level item (handles @graph by flattening).
 */
export async function validateSchemaAction(url: string): Promise<ValidateResult> {
  await requireAdmin();
  if (!url) return { ok: false, error: "Cần URL." };

  let target: URL;
  try {
    target = new URL(url);
  } catch {
    return { ok: false, error: "URL không hợp lệ." };
  }

  let html: string;
  let status: number;
  try {
    const r = await fetch(target.toString(), {
      headers: {
        "User-Agent":
          "Mozilla/5.0 (compatible; FinzoneSchemaValidator/1.0; +https://v2.finzone.io.vn)",
        Accept: "text/html",
      },
      signal: AbortSignal.timeout(15000),
      cache: "no-store",
    });
    status = r.status;
    if (!r.ok) {
      return {
        ok: false,
        url: target.toString(),
        status,
        error: `HTTP ${r.status}`,
      };
    }
    html = await r.text();
  } catch (e) {
    return {
      ok: false,
      url: target.toString(),
      error: e instanceof Error ? e.message : "Fetch lỗi",
    };
  }

  // Pull <title> and <meta name="description">
  const titleMatch = html.match(/<title[^>]*>([^<]*)<\/title>/i);
  const descMatch = html.match(
    /<meta\s+(?:name|property)="description"[^>]*content="([^"]*)"/i,
  );

  // Match every JSON-LD script
  const blockRe =
    /<script[^>]+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
  const blocks: string[] = [];
  let m: RegExpExecArray | null;
  while ((m = blockRe.exec(html)) !== null) {
    blocks.push(m[1].trim());
  }

  const items: SchemaItem[] = [];

  for (const raw of blocks) {
    let parsed: unknown;
    try {
      parsed = JSON.parse(raw);
    } catch (e) {
      items.push({
        type: "<invalid>",
        raw,
        checks: [
          {
            level: "error",
            message: `JSON không parse được: ${e instanceof Error ? e.message : "?"}`,
          },
        ],
      });
      continue;
    }

    // Flatten @graph
    const list: unknown[] = [];
    if (Array.isArray(parsed)) list.push(...parsed);
    else if (parsed && typeof parsed === "object") {
      const obj = parsed as Record<string, unknown>;
      if (Array.isArray(obj["@graph"])) list.push(...(obj["@graph"] as unknown[]));
      else list.push(obj);
    }

    for (const node of list) {
      if (!node || typeof node !== "object") continue;
      const obj = node as Record<string, unknown>;
      const type = String(obj["@type"] ?? "Unknown");
      items.push({
        type,
        raw: obj,
        checks: validateNode(type, obj),
      });
    }
  }

  return {
    ok: true,
    url: target.toString(),
    status,
    fetchedAt: new Date().toISOString(),
    items,
    rawBlocks: blocks.length,
    pageTitle: titleMatch ? decodeEntities(titleMatch[1].trim()) : undefined,
    pageDescription: descMatch ? decodeEntities(descMatch[1].trim()) : undefined,
  };
}

function decodeEntities(s: string): string {
  return s
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&quot;/g, '"')
    .replace(/&#039;/g, "'");
}

function validateNode(type: string, obj: Record<string, unknown>): SchemaCheck[] {
  const checks: SchemaCheck[] = [];
  const need = (field: string, level: SchemaCheck["level"] = "error", note?: string) => {
    if (!obj[field]) {
      checks.push({
        level,
        message: `Thiếu \`${field}\`${note ? ` — ${note}` : ""}`,
      });
    }
  };

  // Common
  if (!obj["@context"]) {
    checks.push({ level: "error", message: "Thiếu `@context` (schema.org)" });
  } else if (
    typeof obj["@context"] === "string" &&
    !String(obj["@context"]).includes("schema.org")
  ) {
    checks.push({ level: "warn", message: "`@context` không trỏ tới schema.org" });
  }

  switch (type) {
    case "NewsArticle":
    case "Article":
    case "BlogPosting":
      need("headline");
      need("datePublished");
      need("author");
      need("publisher");
      need("image", "warn", "rich snippet thường yêu cầu image");
      need("mainEntityOfPage", "warn", "Google khuyên có cho Article");
      if (typeof obj["headline"] === "string" && (obj["headline"] as string).length > 110) {
        checks.push({
          level: "warn",
          message: `Headline ${(obj["headline"] as string).length} ký tự — Google khuyên ≤110`,
        });
      }
      break;
    case "Organization":
    case "NewsMediaOrganization":
      need("name");
      need("url");
      need("logo", "warn", "ImageObject ≥112×112px");
      if (!obj["sameAs"])
        checks.push({
          level: "warn",
          message: "Thiếu `sameAs` — link mạng xã hội giúp Google liên kết entity",
        });
      break;
    case "WebSite":
      need("name");
      need("url");
      if (!obj["potentialAction"])
        checks.push({
          level: "warn",
          message: "Thiếu `potentialAction` — sitelinks search box cần SearchAction",
        });
      break;
    case "BreadcrumbList":
      need("itemListElement");
      if (Array.isArray(obj["itemListElement"])) {
        const items = obj["itemListElement"] as unknown[];
        if (items.length < 2)
          checks.push({
            level: "warn",
            message: "Breadcrumb chỉ 1 phần tử — nên có ≥2 (home → page)",
          });
      }
      break;
    case "FAQPage":
      need("mainEntity", "error", "phải là array các Question");
      break;
    case "Person":
      need("name");
      break;
    case "WebPage":
      need("url");
      need("name", "warn");
      break;
    default:
      checks.push({
        level: "warn",
        message: `Type "${type}" — không có rule kiểm tra cụ thể, validate thủ công`,
      });
  }

  if (checks.filter((c) => c.level === "error").length === 0) {
    checks.unshift({ level: "ok", message: "Bắt buộc đầy đủ — pass" });
  }

  return checks;
}
