import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import type { StoreSiteBlock, StorefrontShell } from "@alqove/api-client";
import { BlockRenderer } from "../block-renderer";
import type { StorefrontRenderContext } from "../links";

const SHELL: StorefrontShell = {
  store: {
    id: "store-1",
    slug: "vintage-vault",
    name: "Vintage Vault",
    street1: "12 Main St",
    city: "Portland",
    state: "OR",
    zip: "97201",
    is_verified: true,
  },
  theme: {},
  hours: [
    { day: 1, closed: false, open: "10:00", close: "18:00" },
    { day: 0, closed: true, open: null, close: null },
  ],
  social_links: [],
  contact_email: "hello@vintagevault.test",
  contact_phone: "+1 555 555 0123",
  nav: [],
  canonical_url: "https://alqove.com/s/vintage-vault",
};

const CTX: StorefrontRenderContext = {
  shell: SHELL,
  slug: "vintage-vault",
  basePath: "/s/vintage-vault",
};

function renderBlocks(
  blocks: StoreSiteBlock[],
  ctx: StorefrontRenderContext = CTX,
  productsByBlock = {},
) {
  return render(<BlockRenderer blocks={blocks} ctx={ctx} productsByBlock={productsByBlock} />);
}

describe("BlockRenderer", () => {
  it("renders blocks in array order", () => {
    renderBlocks([
      { id: "a", type: "rich_text", data: { heading: "First", body: "one" } },
      { id: "b", type: "rich_text", data: { heading: "Second", body: "two" } },
    ]);

    const headings = screen.getAllByRole("heading", { level: 2 }).map((h) => h.textContent);
    expect(headings).toEqual(["First", "Second"]);
  });

  it("skips a block type it does not know instead of throwing", () => {
    renderBlocks([
      { id: "a", type: "carousel_of_doom" as never, data: {} },
      { id: "b", type: "rich_text", data: { body: "still here" } },
    ]);

    expect(screen.getByText("still here")).toBeInTheDocument();
  });

  it("renders an empty page without blowing up", () => {
    const { container } = renderBlocks([]);
    expect(container).toBeEmptyDOMElement();
  });

  describe("hero", () => {
    it("shows the headline and a rebased CTA link", () => {
      renderBlocks([
        {
          id: "h",
          type: "hero",
          data: { headline: "Welcome", cta_label: "Shop", cta_href: "/shop" },
        },
      ]);

      expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Welcome");
      // A seller writes "/shop"; under /s/{slug} it has to resolve inside the storefront.
      expect(screen.getByRole("link", { name: "Shop" })).toHaveAttribute(
        "href",
        "/s/vintage-vault/shop",
      );
    });

    it("leaves the CTA alone on a custom domain, where the storefront is the root", () => {
      renderBlocks(
        [{ id: "h", type: "hero", data: { headline: "Welcome", cta_label: "Shop", cta_href: "/shop" } }],
        { ...CTX, basePath: "" },
      );

      expect(screen.getByRole("link", { name: "Shop" })).toHaveAttribute("href", "/shop");
    });

    it("drops a javascript: CTA rather than rendering the link", () => {
      renderBlocks([
        {
          id: "h",
          type: "hero",
          data: { headline: "Welcome", cta_label: "Click", cta_href: "javascript:alert(1)" },
        },
      ]);

      expect(screen.queryByRole("link", { name: "Click" })).not.toBeInTheDocument();
    });

    it("falls back to the store name when the headline is missing", () => {
      renderBlocks([{ id: "h", type: "hero", data: {} }]);
      expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Vintage Vault");
    });
  });

  describe("rich_text", () => {
    it("splits the body into paragraphs and never renders it as markup", () => {
      renderBlocks([
        {
          id: "t",
          type: "rich_text",
          data: { body: "One\n\n<b>not bold</b>" },
        },
      ]);

      expect(screen.getByText("One")).toBeInTheDocument();
      // Seller text is escaped, so the tag shows up literally.
      expect(screen.getByText("<b>not bold</b>")).toBeInTheDocument();
    });
  });

  describe("products", () => {
    it("renders the listings it was handed", () => {
      renderBlocks(
        [{ id: "p", type: "products", data: { heading: "New in", source: "newest" } }],
        CTX,
        {
          p: [
            {
              id: "item-1",
              title: "Wool coat",
              brand: "Pendleton",
              price: 8900,
              condition: "EUC",
              imageUrl: null,
            },
          ],
        },
      );

      expect(screen.getByText("Wool coat")).toBeInTheDocument();
      expect(screen.getByText("$89.00")).toBeInTheDocument();
      // Item links leave the storefront, so they must be absolute.
      expect(screen.getByRole("link", { name: /Wool coat/ })).toHaveAttribute(
        "href",
        "https://alqove.com/items/item-1",
      );
    });

    it("says so when the store has nothing listed", () => {
      renderBlocks([{ id: "p", type: "products", data: { source: "newest" } }]);
      expect(screen.getByText(/Nothing listed here just yet/)).toBeInTheDocument();
    });
  });

  describe("hours", () => {
    it("lists every day, Monday first", () => {
      renderBlocks([{ id: "h", type: "hours", data: {} }]);

      expect(screen.getByText("Monday")).toBeInTheDocument();
      expect(screen.getByText("10 AM – 6 PM")).toBeInTheDocument();
      expect(screen.getByText("Sunday")).toBeInTheDocument();
      expect(screen.getAllByText("Closed").length).toBeGreaterThan(0);
    });
  });

  describe("map", () => {
    it("shows the store address and a directions link", () => {
      renderBlocks([{ id: "m", type: "map", data: {} }]);

      expect(screen.getByText("12 Main St")).toBeInTheDocument();
      expect(screen.getByRole("link", { name: "Get directions" })).toHaveAttribute(
        "href",
        expect.stringContaining("google.com/maps"),
      );
    });

    it("renders nothing when the store has no address and no note", () => {
      const { container } = renderBlocks([{ id: "m", type: "map", data: {} }], {
        ...CTX,
        shell: { ...SHELL, store: { id: "store-1", name: "Vault" }, directions_note: null },
      });

      expect(container).toBeEmptyDOMElement();
    });
  });

  describe("contact", () => {
    it("shows the site's email and phone", () => {
      renderBlocks([{ id: "c", type: "contact", data: {} }]);

      expect(screen.getByRole("link", { name: "hello@vintagevault.test" })).toHaveAttribute(
        "href",
        "mailto:hello@vintagevault.test",
      );
      expect(screen.getByRole("link", { name: "+1 555 555 0123" })).toHaveAttribute(
        "href",
        "tel:+15555550123",
      );
    });

    it("honours the seller's toggles", () => {
      renderBlocks([
        {
          id: "c",
          type: "contact",
          data: { show_email: false, show_phone: false, show_message_link: false, body: "Say hi" },
        },
      ]);

      expect(screen.queryByRole("link", { name: /@/ })).not.toBeInTheDocument();
      expect(screen.getByText("Say hi")).toBeInTheDocument();
    });
  });

  describe("gallery", () => {
    it("renders only images with a usable URL", () => {
      renderBlocks([
        {
          id: "g",
          type: "gallery",
          data: {
            images: [
              { url: "https://cdn.test/a.jpg", alt: "Rack" },
              { url: "javascript:alert(1)", alt: "Bad" },
              { url: "", alt: "Empty" },
            ],
          },
        },
      ]);

      expect(screen.getByAltText("Rack")).toBeInTheDocument();
      expect(screen.queryByAltText("Bad")).not.toBeInTheDocument();
      expect(screen.getAllByRole("img")).toHaveLength(1);
    });

    it("renders nothing when every image is unusable", () => {
      const { container } = renderBlocks([
        { id: "g", type: "gallery", data: { images: [{ url: "javascript:alert(1)" }] } },
      ]);
      expect(container).toBeEmptyDOMElement();
    });
  });

  describe("faq", () => {
    it("renders question and answer pairs", () => {
      renderBlocks([
        {
          id: "f",
          type: "faq",
          data: { items: [{ question: "Do you buy?", answer: "Yes, Tuesdays." }] },
        },
      ]);

      expect(screen.getByText("Do you buy?")).toBeInTheDocument();
      expect(screen.getByText("Yes, Tuesdays.")).toBeInTheDocument();
    });

    it("drops entries missing a question or an answer", () => {
      const { container } = renderBlocks([
        { id: "f", type: "faq", data: { items: [{ question: "Only a question" }] } },
      ]);
      expect(container).toBeEmptyDOMElement();
    });
  });

  describe("cta", () => {
    it("needs a headline, label and href before it renders", () => {
      const { container } = renderBlocks([
        { id: "c", type: "cta", data: { headline: "Come by", label: "Visit" } },
      ]);
      expect(container).toBeEmptyDOMElement();
    });

    it("renders the button when all three are present", () => {
      renderBlocks([
        {
          id: "c",
          type: "cta",
          data: { headline: "Come by", label: "Visit", href: "https://example.com" },
        },
      ]);

      expect(screen.getByRole("link", { name: "Visit" })).toHaveAttribute(
        "href",
        "https://example.com",
      );
    });
  });
});
