import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';

import BlogShow from './Show';

// Mock Head to render children so we can assert on meta tags
vi.mock('@inertiajs/react', async () => {
  const actual = await vi.importActual('@inertiajs/react');
  return {
    ...actual,
    Head: ({ title, children }: { title?: string; children?: React.ReactNode }) => (
      <head>
        {title && <title>{title}</title>}
        {children}
      </head>
    ),
    Link: ({ children, href }: { children: React.ReactNode; href: string }) => (
      <a href={href}>{children}</a>
    ),
    usePage: vi.fn(() => ({
      props: { auth: { user: null }, errors: {}, flash: {} },
    })),
  };
});

vi.mock('@/Components/marketing/MarketingNav', () => ({
  MarketingNav: () => <nav data-testid="marketing-nav" />,
}));

vi.mock('@/Components/marketing/MarketingFooter', () => ({
  MarketingFooter: () => <footer data-testid="marketing-footer" />,
}));

vi.mock('@/Components/marketing/TableOfContents', () => ({
  TableOfContents: () => <nav data-testid="toc" />,
}));

vi.mock('@/Components/marketing/BlogProductCta', () => ({
  BlogProductCta: ({ category }: { category: string | null }) => (
    <div data-testid="blog-product-cta" data-category={category ?? ''} />
  ),
}));

vi.mock('@/Components/ShareButtons', () => ({
  ShareButtons: () => <div data-testid="share-buttons" />,
}));

vi.mock('@/lib/sanitize', () => ({
  sanitizeHtml: (html: string) => html,
}));

const mockPost = {
  id: 1,
  title: 'How to Optimize WordPress SEO',
  slug: 'how-to-optimize-wordpress-seo',
  excerpt:
    'A comprehensive guide to WordPress SEO optimization using data from Google Search Console.',
  content: '<p>Content here</p>',
  author: 'Jane Doe',
  published_at: '2026-01-15T10:00:00Z',
  updated_at: '2026-02-01T12:00:00Z',
  meta_title: null,
  meta_description: null,
  og_image: null,
  canonical_url: null,
  category: 'wordpress-seo',
  estimated_reading_time: 5,
  author_title: null,
  author_bio: null,
};

describe('Blog/Show — JSON-LD schema', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // jsdom doesn't have location.origin so set it
    Object.defineProperty(window, 'location', {
      value: {
        origin: 'https://example.com',
        href: 'https://example.com/blog/how-to-optimize-wordpress-seo',
      },
      writable: true,
    });
  });

  it('renders the article title', () => {
    render(<BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />);
    expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(mockPost.title);
  });

  it('renders a BlogPosting JSON-LD script tag', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );

    const scripts = container.querySelectorAll('script[type="application/ld+json"]');
    expect(scripts.length).toBeGreaterThanOrEqual(1);

    // Find the BlogPosting schema
    let blogPostingSchema: Record<string, unknown> | null = null;
    scripts.forEach((script) => {
      const parsed = JSON.parse(script.textContent || '{}') as Record<string, unknown>;
      if (parsed['@type'] === 'BlogPosting') {
        blogPostingSchema = parsed;
      }
    });

    expect(blogPostingSchema).not.toBeNull();
  });

  it('BlogPosting schema has required Article fields', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );

    const scripts = container.querySelectorAll('script[type="application/ld+json"]');
    let schema: Record<string, unknown> | null = null;
    scripts.forEach((script) => {
      const parsed = JSON.parse(script.textContent || '{}') as Record<string, unknown>;
      if (parsed['@type'] === 'BlogPosting') {
        schema = parsed;
      }
    });

    expect(schema).not.toBeNull();
    expect((schema as unknown as Record<string, unknown>)['@context']).toBe('https://schema.org');
    expect((schema as unknown as Record<string, unknown>)['headline']).toBe(mockPost.title);
    expect((schema as unknown as Record<string, unknown>)['datePublished']).toBe(
      mockPost.published_at,
    );
    expect((schema as unknown as Record<string, unknown>)['dateModified']).toBe(
      mockPost.updated_at,
    );

    const author = (schema as unknown as Record<string, unknown>)['author'] as Record<
      string,
      unknown
    >;
    expect(author['@type']).toBe('Person');
    expect(author['name']).toBe(mockPost.author);
  });

  it('BlogPosting schema falls back to published_at when updated_at equals published_at', () => {
    const postNoUpdate = { ...mockPost, updated_at: mockPost.published_at };
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={postNoUpdate} relatedPosts={[]} />,
    );

    const scripts = container.querySelectorAll('script[type="application/ld+json"]');
    let schema: Record<string, unknown> | null = null;
    scripts.forEach((script) => {
      const parsed = JSON.parse(script.textContent || '{}') as Record<string, unknown>;
      if (parsed['@type'] === 'BlogPosting') {
        schema = parsed;
      }
    });

    expect((schema as unknown as Record<string, unknown>)['dateModified']).toBe(
      mockPost.published_at,
    );
  });

  it('renders a BreadcrumbList JSON-LD script tag', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );

    const scripts = container.querySelectorAll('script[type="application/ld+json"]');
    let breadcrumbSchema: Record<string, unknown> | null = null;
    scripts.forEach((script) => {
      const parsed = JSON.parse(script.textContent || '{}') as Record<string, unknown>;
      if (parsed['@type'] === 'BreadcrumbList') {
        breadcrumbSchema = parsed;
      }
    });

    expect(breadcrumbSchema).not.toBeNull();
    const items = (breadcrumbSchema as unknown as Record<string, unknown>)[
      'itemListElement'
    ] as Array<Record<string, unknown>>;
    expect(items).toHaveLength(3);
    expect(items[0]['name']).toBe('Home');
    expect(items[1]['name']).toBe('Blog');
    expect(items[2]['name']).toBe(mockPost.title);
  });

  it('JSON-LD is valid JSON', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );

    const scripts = container.querySelectorAll('script[type="application/ld+json"]');
    scripts.forEach((script) => {
      expect(() => JSON.parse(script.textContent || '')).not.toThrow();
    });
  });

  it('uses meta_title when provided', () => {
    const postWithMetaTitle = { ...mockPost, meta_title: 'Custom SEO Title' };
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={postWithMetaTitle} relatedPosts={[]} />,
    );

    const title = container.querySelector('title');
    expect(title?.textContent).toContain('Custom SEO Title');
  });

  it('falls back to post.title when meta_title is null', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );

    const title = container.querySelector('title');
    expect(title?.textContent).toContain(mockPost.title);
  });

  it('uses fallback og:image when og_image is null', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );

    const ogImage = container.querySelector('meta[property="og:image"]');
    expect(ogImage?.getAttribute('content')).toBe(`/og/blog/${mockPost.slug}.svg`);
  });

  it('uses provided og_image', () => {
    const postWithImage = { ...mockPost, og_image: '/custom-image.jpg' };
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={postWithImage} relatedPosts={[]} />,
    );

    const ogImage = container.querySelector('meta[property="og:image"]');
    expect(ogImage?.getAttribute('content')).toBe('/custom-image.jpg');
  });

  it('renders og:type as article', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );

    const ogType = container.querySelector('meta[property="og:type"]');
    expect(ogType?.getAttribute('content')).toBe('article');
  });

  it('renders twitter:card as summary_large_image', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );

    const twitterCard = container.querySelector('meta[name="twitter:card"]');
    expect(twitterCard?.getAttribute('content')).toBe('summary_large_image');
  });

  it('renders related posts when provided', () => {
    const relatedPosts = [
      {
        id: 2,
        title: 'Related Post',
        slug: 'related-post',
        excerpt: 'Related excerpt',
        author: 'John Smith',
        published_at: '2026-01-10T10:00:00Z',
        category: null,
      },
    ];

    render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={relatedPosts} />,
    );

    expect(screen.getByText('Continue reading')).toBeInTheDocument();
    expect(screen.getByText('Related Post')).toBeInTheDocument();
  });

  it('does not render related posts section when empty', () => {
    render(<BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />);

    expect(screen.queryByText('Continue reading')).not.toBeInTheDocument();
  });
});

describe('Blog/Show — SD-003: JSON-LD completeness + microdata', () => {
  const getSchema = (container: HTMLElement, type: string): Record<string, unknown> | null => {
    let found: Record<string, unknown> | null = null;
    container.querySelectorAll('script[type="application/ld+json"]').forEach((s) => {
      const parsed = JSON.parse(s.textContent || '{}') as Record<string, unknown>;
      if (parsed['@type'] === type) found = parsed;
    });
    return found;
  };

  it('BlogPosting JSON-LD includes wordCount', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );
    const schema = getSchema(container, 'BlogPosting');
    expect(schema).not.toBeNull();
    const wordCount = (schema as Record<string, unknown>)['wordCount'];
    expect(typeof wordCount).toBe('number');
    expect(wordCount as number).toBeGreaterThanOrEqual(1);
  });

  it('BlogPosting JSON-LD includes image as ImageObject with url/width/height', () => {
    const postWithImage = { ...mockPost, og_image: 'https://cdn.example.com/img.jpg' };
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={postWithImage} relatedPosts={[]} />,
    );
    const schema = getSchema(container, 'BlogPosting');
    const image = (schema as Record<string, unknown>)['image'] as Record<string, unknown>;
    expect(image['@type']).toBe('ImageObject');
    expect(image['url']).toBe('https://cdn.example.com/img.jpg');
    expect(image['width']).toBe(1200);
    expect(image['height']).toBe(630);
  });

  it('BlogPosting JSON-LD includes publisher with name and logo', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );
    const schema = getSchema(container, 'BlogPosting');
    const publisher = (schema as Record<string, unknown>)['publisher'] as Record<string, unknown>;
    expect(publisher['@type']).toBe('Organization');
    expect(typeof publisher['name']).toBe('string');
    const logo = publisher['logo'] as Record<string, unknown>;
    expect(logo['@type']).toBe('ImageObject');
    expect(typeof logo['url']).toBe('string');
  });

  it('BlogPosting JSON-LD includes keywords when post.keywords is set', () => {
    const postWithKeywords = { ...mockPost, keywords: ['seo', 'wordpress', 'gsc'] };
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={postWithKeywords} relatedPosts={[]} />,
    );
    const schema = getSchema(container, 'BlogPosting');
    expect((schema as Record<string, unknown>)['keywords']).toBe('seo, wordpress, gsc');
  });

  it('BlogPosting JSON-LD omits keywords when post.keywords is empty', () => {
    const postNoKeywords = { ...mockPost, keywords: [] };
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={postNoKeywords} relatedPosts={[]} />,
    );
    const schema = getSchema(container, 'BlogPosting');
    expect(Object.prototype.hasOwnProperty.call(schema, 'keywords')).toBe(false);
  });

  it('BlogPosting JSON-LD omits keywords when post.keywords is null', () => {
    const postNullKeywords = { ...mockPost, keywords: null };
    const { container } = render(
      <BlogShow
        canLogin={true}
        canRegister={true}
        post={postNullKeywords as typeof mockPost & { keywords: null }}
        relatedPosts={[]}
      />,
    );
    const schema = getSchema(container, 'BlogPosting');
    expect(Object.prototype.hasOwnProperty.call(schema, 'keywords')).toBe(false);
  });

  it('BlogPosting JSON-LD omits keywords when post.keywords is undefined', () => {
    const { keywords: _omit, ...postWithoutKeywords } = { ...mockPost, keywords: undefined };
    const { container } = render(
      <BlogShow
        canLogin={true}
        canRegister={true}
        post={postWithoutKeywords as typeof mockPost}
        relatedPosts={[]}
      />,
    );
    const schema = getSchema(container, 'BlogPosting');
    expect(Object.prototype.hasOwnProperty.call(schema, 'keywords')).toBe(false);
  });

  it('article element carries itemScope + BlogPosting itemType', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );
    const article = container.querySelector(
      'article[itemscope][itemtype="https://schema.org/BlogPosting"]',
    );
    expect(article).not.toBeNull();
  });

  it('h1 headline carries itemProp="headline"', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );
    const h1 = container.querySelector('h1[itemprop="headline"]');
    expect(h1).not.toBeNull();
    expect(h1?.textContent).toContain(mockPost.title);
  });

  it('author element carries itemProp="author"', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );
    const authorEl = container.querySelector('[itemprop="author"]');
    expect(authorEl).not.toBeNull();
    expect(authorEl?.textContent).toContain(mockPost.author);
  });

  it('datePublished time element carries itemProp="datePublished"', () => {
    const { container } = render(
      <BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />,
    );
    const timeEl = container.querySelector('time[itemprop="datePublished"]');
    expect(timeEl).not.toBeNull();
    expect(timeEl?.getAttribute('datetime')).toBe(mockPost.published_at);
  });
});

describe('Blog/Show — LEA-003: internal linking richness', () => {
  it('renders the Explore RankWiz resource links section', () => {
    render(<BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />);
    expect(screen.getByText('Explore RankWiz')).toBeInTheDocument();
  });

  it('resource links section includes a features link', () => {
    render(<BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />);
    const featuresLink = screen.getByRole('link', { name: /see all rankwiz features/i });
    expect(featuresLink).toBeInTheDocument();
    expect(featuresLink.getAttribute('href')).toContain('/features');
  });

  it('resource links section includes a pricing link', () => {
    render(<BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />);
    const pricingLink = screen.getByRole('link', { name: /pricing/i });
    expect(pricingLink).toBeInTheDocument();
    expect(pricingLink.getAttribute('href')).toContain('/pricing');
  });

  it('resource links section includes competitor comparison links', () => {
    render(<BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />);
    expect(screen.getByRole('link', { name: /rankwiz vs surfer seo/i })).toBeInTheDocument();
    expect(screen.getByRole('link', { name: /rankwiz vs ahrefs/i })).toBeInTheDocument();
    expect(screen.getByRole('link', { name: /rankwiz vs semrush/i })).toBeInTheDocument();
  });

  it('renders category-mapped product CTA', () => {
    render(<BlogShow canLogin={true} canRegister={true} post={mockPost} relatedPosts={[]} />);
    const cta = screen.getByTestId('blog-product-cta');
    expect(cta).toBeInTheDocument();
    expect(cta.getAttribute('data-category')).toBe(mockPost.category);
  });
});
