import { describe, expect, it } from "vitest";
import { auditPage } from "@/content/audit";
import { serviceCatalog } from "@/content/catalog";
import { circuitStages } from "@/content/circuit";
import { aboutPage, howWeWorkPage } from "@/content/company";
import { areaCopy, diagnosticQuestions } from "@/content/diagnostic";
import { homeFaqs } from "@/content/faqs";
import * as home from "@/content/home";
import { ecommerceIndustry } from "@/content/industries/ecommerce";
import { saasIndustry } from "@/content/industries/saas";
import { legalDocuments } from "@/content/legal";
import { metricsByModel, metricsSection } from "@/content/metrics";
import { cadence, processSteps, workingPrinciples } from "@/content/process";
import { resourceArticles } from "@/content/resources";
import { resourceCatalog } from "@/content/resources/catalog";
import { serviceContents } from "@/content/services";
import { isKnownInternalHref } from "@/lib/routes";
import { extractInlineHrefs } from "@/lib/text";

interface Found {
  path: string;
  text: string;
}

function collect(value: unknown, path: string, out: Found[] = []): Found[] {
  if (typeof value === "string") out.push({ path, text: value });
  else if (Array.isArray(value)) value.forEach((item, index) => collect(item, `${path}[${index}]`, out));
  else if (value && typeof value === "object") {
    for (const [key, child] of Object.entries(value)) collect(child, `${path}.${key}`, out);
  }
  return out;
}

const corpus: Found[] = [
  ...collect(home, "home"),
  ...collect(homeFaqs, "homeFaqs"),
  ...collect(circuitStages, "circuit"),
  ...collect({ metricsByModel, metricsSection }, "metrics"),
  ...collect({ processSteps, workingPrinciples, cadence }, "process"),
  ...collect({ aboutPage, howWeWorkPage }, "company"),
  ...collect(auditPage, "audit"),
  ...collect(areaCopy, "diagnosticAreas"),
  ...collect(serviceContents, "services"),
  ...collect(resourceArticles, "resources"),
  ...collect([saasIndustry, ecommerceIndustry], "industries"),
  ...collect(legalDocuments, "legal"),
];

describe("content registries", () => {
  it("has full page content for every service in the catalog, matching its summary", () => {
    for (const summary of serviceCatalog) {
      const service = serviceContents.find((item) => item.slug === summary.slug);
      expect(service, `missing service content: ${summary.slug}`).toBeDefined();
      expect(service?.name).toBe(summary.name);
      expect(service?.shortDescription).toBe(summary.shortDescription);
      expect(service?.stage).toBe(summary.stage);
    }
  });

  it("has an article for every resource in the catalog", () => {
    for (const summary of resourceCatalog) {
      const article = resourceArticles.find((item) => item.slug === summary.slug);
      expect(article, `missing article: ${summary.slug}`).toBeDefined();
      expect(article?.title).toBe(summary.title);
    }
  });
});

describe("no hype and no manufactured proof", () => {
  const banned: RegExp[] = [
    /\bunlock/i,
    /next level/i,
    /360[- ]degree/i,
    /\binnovative\b/i,
    /\bpassionate\b/i,
    /cutting[- ]edge/i,
    /world[- ]class/i,
    /best[- ]in[- ]class/i,
    /revolutioni[sz]e/i,
    /supercharg/i,
    /skyrocket/i,
    /game[- ]?changer/i,
    /\bsynerg/i,
    /\bseamless/i,
    /\bholistic\b/i,
    /\bempower/i,
    /\bleverag/i,
    /\brobust\b/i,
    /turbocharg/i,
    /data[- ]driven/i,
    /growth hack/i,
    /trusted by/i,
    /award[- ]winning/i,
    /\bcertified\b/i,
    /premier partner/i,
    /google partner/i,
    /meta business partner/i,
    /\bour clients\b/i,
    /clients (love|say)/i,
    /\bwe guarantee\b/i,
    /!/,
  ];

  it("contains none of the banned phrases", () => {
    const violations = corpus.flatMap((item) =>
      banned.filter((pattern) => pattern.test(item.text)).map((pattern) => `${item.path}: ${pattern} → "${item.text.slice(0, 90)}"`),
    );
    expect(violations).toEqual([]);
  });

  it("has no percentages, currency amounts, or multipliers outside hypothetical examples", () => {
    const numericClaim = /\d+(\.\d+)?\s?%|\$\s?\d|\b\d+(\.\d+)?x\b/i;
    const violations = corpus
      .filter((item) => !isExampleBlock(item.path))
      .filter((item) => numericClaim.test(item.text))
      .map((item) => `${item.path}: "${item.text.slice(0, 90)}"`);
    expect(violations).toEqual([]);
  });

  it("labels every worked example as hypothetical", () => {
    for (const article of resourceArticles) {
      for (const block of article.body) {
        if (block.type === "example") expect(block.title.startsWith("Hypothetical"), `${article.slug}: ${block.title}`).toBe(true);
      }
    }
  });
});

function isExampleBlock(path: string): boolean {
  const match = path.match(/^resources\[(\d+)\]\.body\[(\d+)\]/);
  if (!match) return false;
  return resourceArticles[Number(match[1])].body[Number(match[2])].type === "example";
}

describe("links", () => {
  it("only links to pages that exist", () => {
    const inlineHrefs = corpus.flatMap((item) => extractInlineHrefs(item.text).map((href) => ({ href, path: item.path })));
    const capabilityHrefs = [saasIndustry, ecommerceIndustry].flatMap((industry) =>
      industry.capabilities.flatMap((capability) => capability.links.map((link) => ({ href: link.href, path: `${industry.slug}.${capability.id}` }))),
    );
    const broken = [...inlineHrefs, ...capabilityHrefs]
      .filter(({ href }) => !href.startsWith("https://"))
      .filter(({ href }) => !isKnownInternalHref(href));
    expect(broken).toEqual([]);
  });

  it("sends external links over https only", () => {
    const external = corpus
      .flatMap((item) => extractInlineHrefs(item.text).map((href) => ({ href, path: item.path })))
      .filter(({ href }) => !href.startsWith("/"));
    const insecure = external.filter(({ href }) => !href.startsWith("https://"));
    expect(insecure).toEqual([]);
  });
});

describe("legal documents", () => {
  it("gives every document an ISO effective date, a summary, and unique section headings", () => {
    for (const document of legalDocuments) {
      expect(document.effectiveDate, document.title).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      expect(document.summary.length, document.title).toBeGreaterThanOrEqual(3);
      expect(document.sections.length, document.title).toBeGreaterThanOrEqual(5);
      const headings = document.sections.map((section) => section.heading);
      expect(new Set(headings).size, document.title).toBe(headings.length);
    }
  });

  it("gives every section something to render, and keeps table rows the width of their header", () => {
    for (const document of legalDocuments) {
      for (const section of document.sections) {
        const filled = Boolean(section.paragraphs?.length || section.list?.length || section.table);
        expect(filled, `${document.title}: ${section.heading}`).toBe(true);
        if (!section.table) continue;
        for (const row of section.table.rows) {
          expect(row.length, `${document.title}: ${section.heading}`).toBe(section.table.columns.length);
        }
      }
    }
  });

  it("keeps meta descriptions inside the length the SEO check enforces", () => {
    for (const document of legalDocuments) {
      expect(document.description.length, document.title).toBeGreaterThanOrEqual(70);
      expect(document.description.length, document.title).toBeLessThanOrEqual(170);
    }
  });

  it("points the reader at the published contact address", () => {
    const text = legalDocuments.flatMap((document) => collect(document, "legal")).map((item) => item.text);
    expect(text.some((item) => item.includes("info@axidysmedia.com"))).toBe(true);
  });
});

describe("SEO metadata", () => {
  const pages = [
    ...serviceContents.map((service) => ({ key: service.slug, ...service.seo })),
    ...resourceArticles.map((article) => ({ key: article.slug, ...article.seo })),
    { key: "saas", ...saasIndustry.seo },
    { key: "ecommerce", ...ecommerceIndustry.seo },
    { key: "about", ...aboutPage.seo },
    { key: "how-we-work", ...howWeWorkPage.seo },
    { key: "audit", ...auditPage.seo },
  ];

  it("keeps titles short enough to display in results, including the brand suffix", () => {
    const tooLong = pages.filter((page) => `${page.title} · Axidys Media`.length > 65).map((page) => page.key);
    expect(tooLong).toEqual([]);
  });

  it("keeps descriptions between 110 and 165 characters", () => {
    const outOfRange = pages
      .filter((page) => page.description.length < 110 || page.description.length > 165)
      .map((page) => `${page.key} (${page.description.length})`);
    expect(outOfRange).toEqual([]);
  });

  it("uses a unique title on every page", () => {
    const titles = pages.map((page) => page.title);
    expect(new Set(titles).size).toBe(titles.length);
  });
});

describe("page structure", () => {
  it("gives every service page the full section set", () => {
    for (const service of serviceContents) {
      expect(service.problem.items, service.slug).toHaveLength(4);
      expect(service.approach.steps, service.slug).toHaveLength(5);
      expect(service.deliverables.items, service.slug).toHaveLength(8);
      expect(service.applications.saas.points, service.slug).toHaveLength(5);
      expect(service.applications.ecommerce.points, service.slug).toHaveLength(5);
      expect(service.process.steps, service.slug).toHaveLength(5);
      expect(service.measurement.tiers.map((tier) => tier.tier), service.slug).toEqual(["Business outcome", "Efficiency", "Diagnostic"]);
      expect(service.faqs, service.slug).toHaveLength(5);
      expect(service.related.services, service.slug).not.toContain(service.slug);
    }
  });

  it("gives industry pages the sections the brief requires", () => {
    expect(saasIndustry.capabilities).toHaveLength(5);
    expect(ecommerceIndustry.capabilities).toHaveLength(6);
    for (const industry of [saasIndustry, ecommerceIndustry]) {
      expect(industry.challenges.items).toHaveLength(6);
      expect(industry.system.steps).toHaveLength(6);
      expect(industry.process.steps.map((step) => step.title)).toEqual(["Diagnose", "Model", "Prioritize", "Build", "Experiment", "Scale"]);
    }
  });

  it("keeps article headings free of trailing periods and gives four takeaways", () => {
    for (const article of resourceArticles) {
      expect(article.takeaways, article.slug).toHaveLength(4);
      for (const block of article.body) {
        if (block.type === "h2" || block.type === "h3") expect(block.text.endsWith("."), `${article.slug}: ${block.text}`).toBe(false);
      }
    }
  });

  it("offers two business models and five questions in the diagnostic", () => {
    expect(diagnosticQuestions).toHaveLength(5);
    expect(diagnosticQuestions[0].options.map((option) => option.value)).toEqual(["saas", "ecommerce"]);
  });
});
