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

import BlogIndex from './Index';

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>
    ),
    router: { get: vi.fn() },
    usePage: vi.fn(() => ({
      props: { auth: { user: null }, errors: {}, flash: {}, app_url: 'https://example.com' },
    })),
  };
});

vi.mock('@/Components/marketing/BlogCard', () => ({
  BlogCard: ({ title }: { title: string }) => <article>{title}</article>,
}));
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/NewsletterSignup', () => ({
  NewsletterSignup: () => <div data-testid="newsletter" />,
}));
vi.mock('@/Components/Shared/InertiaPagination', () => ({
  default: () => <nav data-testid="pagination" />,
}));

const emptyPosts = {
  data: [],
  current_page: 1,
  last_page: 1,
  per_page: 12,
  total: 0,
  links: [],
};

const baseProps = {
  canLogin: true,
  canRegister: true,
  posts: emptyPosts,
  categories: ['wordpress-seo', 'content-optimization'],
  activeCategory: null,
  currentSearch: null,
};

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

const paginatedPosts = (currentPage: number, lastPage: number) => ({
  data: [{ id: 1, title: 'Post', slug: 'post', excerpt: '', author: 'A', published_at: '2024-01-01', category: null }],
  current_page: currentPage,
  last_page: lastPage,
  per_page: 12,
  total: lastPage * 12,
  links: [],
});

function getRobotsContent(container: HTMLElement): string | null {
  const el = container.querySelector('meta[name="robots"]') as HTMLMetaElement | null;
  return el ? el.getAttribute('content') : null;
}

function getPaginationLink(container: HTMLElement, rel: 'next' | 'prev'): string | null {
  const el = container.querySelector(`link[rel="${rel}"]`) as HTMLLinkElement | null;
  return el ? el.getAttribute('href') : null;
}

describe('Blog/Index — robots noindex (COP-004 / COP-010)', () => {
  beforeEach(() => vi.clearAllMocks());

  it('does not noindex page 1 with no filter and no search', () => {
    const { container } = render(<BlogIndex {...baseProps} />);
    expect(getRobotsContent(container)).toBeNull();
  });

  it('noindexes page 2+ (unfiltered)', () => {
    const { container } = render(
      <BlogIndex {...baseProps} posts={paginatedPosts(2, 3)} />,
    );
    expect(getRobotsContent(container)).toBe('noindex, follow');
  });

  it('noindexes when activeCategory is set (page 1)', () => {
    const { container } = render(
      <BlogIndex {...baseProps} activeCategory="wordpress-seo" />,
    );
    expect(getRobotsContent(container)).toBe('noindex, follow');
  });

  it('noindexes when currentSearch is set (COP-010)', () => {
    const { container } = render(
      <BlogIndex {...baseProps} currentSearch="keyword" />,
    );
    expect(getRobotsContent(container)).toBe('noindex, follow');
  });
});

describe('Blog/Index — rel=next/prev pagination links (COP-004)', () => {
  beforeEach(() => vi.clearAllMocks());

  it('rel=next points to page 2 on plain page 1', () => {
    const { container } = render(
      <BlogIndex {...baseProps} posts={paginatedPosts(1, 3)} />,
    );
    expect(getPaginationLink(container, 'next')).toBe('https://example.com/blog?page=2');
  });

  it('rel=prev on page 2 returns clean /blog URL (no page param for page 1)', () => {
    const { container } = render(
      <BlogIndex {...baseProps} posts={paginatedPosts(2, 3)} />,
    );
    expect(getPaginationLink(container, 'prev')).toBe('https://example.com/blog');
  });

  it('rel=prev on page 3 includes page=2', () => {
    const { container } = render(
      <BlogIndex {...baseProps} posts={paginatedPosts(3, 3)} />,
    );
    expect(getPaginationLink(container, 'prev')).toBe('https://example.com/blog?page=2');
  });

  it('rel=next includes category param when activeCategory is set', () => {
    const { container } = render(
      <BlogIndex {...baseProps} posts={paginatedPosts(1, 3)} activeCategory="wordpress-seo" />,
    );
    expect(getPaginationLink(container, 'next')).toBe(
      'https://example.com/blog?page=2&category=wordpress-seo',
    );
  });

  it('rel=prev includes category param when activeCategory is set', () => {
    const { container } = render(
      <BlogIndex {...baseProps} posts={paginatedPosts(2, 3)} activeCategory="wordpress-seo" />,
    );
    expect(getPaginationLink(container, 'prev')).toBe(
      'https://example.com/blog?category=wordpress-seo',
    );
  });

  it('rel=next includes search param when currentSearch is set', () => {
    const { container } = render(
      <BlogIndex {...baseProps} posts={paginatedPosts(1, 3)} currentSearch="seo tips" />,
    );
    const href = getPaginationLink(container, 'next');
    expect(href).toContain('page=2');
    expect(href).toContain('search=seo+tips');
  });

  it('no rel=next on last page', () => {
    const { container } = render(
      <BlogIndex {...baseProps} posts={paginatedPosts(3, 3)} />,
    );
    expect(getPaginationLink(container, 'next')).toBeNull();
  });

  it('no rel=prev on page 1', () => {
    const { container } = render(
      <BlogIndex {...baseProps} posts={paginatedPosts(1, 3)} />,
    );
    expect(getPaginationLink(container, 'prev')).toBeNull();
  });

  it('rel=next includes both category and search params when both are set', () => {
    const { container } = render(
      <BlogIndex
        {...baseProps}
        posts={paginatedPosts(1, 3)}
        activeCategory="wordpress-seo"
        currentSearch="link building"
      />,
    );
    const href = getPaginationLink(container, 'next');
    expect(href).toContain('page=2');
    expect(href).toContain('category=wordpress-seo');
    expect(href).toContain('search=link+building');
  });

  it('rel=prev includes both category and search params when both are set', () => {
    const { container } = render(
      <BlogIndex
        {...baseProps}
        posts={paginatedPosts(2, 3)}
        activeCategory="wordpress-seo"
        currentSearch="link building"
      />,
    );
    const href = getPaginationLink(container, 'prev');
    // page 1 omits the page param, but still carries category and search
    expect(href).not.toContain('page=');
    expect(href).toContain('category=wordpress-seo');
    expect(href).toContain('search=link+building');
  });
});

describe('Blog/Index — canonical URL (COP-004)', () => {
  beforeEach(() => vi.clearAllMocks());

  function getCanonical(container: HTMLElement): string | null {
    const el = container.querySelector('link[rel="canonical"]') as HTMLLinkElement | null;
    return el ? el.getAttribute('href') : null;
  }

  it('canonical self-references /blog on plain page 1', () => {
    const { container } = render(<BlogIndex {...baseProps} />);
    expect(getCanonical(container)).toBe('https://example.com/blog');
  });

  it('canonical self-references paginated URL on page 2', () => {
    const { container } = render(
      <BlogIndex {...baseProps} posts={paginatedPosts(2, 3)} />,
    );
    expect(getCanonical(container)).toBe('https://example.com/blog?page=2');
  });

  it('canonical includes category param when activeCategory is set on page 1', () => {
    const { container } = render(
      <BlogIndex {...baseProps} activeCategory="wordpress-seo" />,
    );
    expect(getCanonical(container)).toBe('https://example.com/blog?category=wordpress-seo');
  });
});

describe('Blog/Index — BreadcrumbList JSON-LD schema', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('renders a 2-item breadcrumb when no active category', () => {
    const { container } = render(<BlogIndex {...baseProps} />);
    const schema = getBreadcrumbSchema(container);
    expect(schema).not.toBeNull();
    const items = (schema as Record<string, unknown>)['itemListElement'] as Array<
      Record<string, unknown>
    >;
    expect(items).toHaveLength(2);
    expect(items[0]['name']).toBe('Home');
    expect(items[1]['name']).toBe('Blog');
  });

  it('renders a 3-item breadcrumb when an active category is set', () => {
    const { container } = render(<BlogIndex {...baseProps} activeCategory="wordpress-seo" />);
    const schema = getBreadcrumbSchema(container);
    expect(schema).not.toBeNull();
    const items = (schema 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('WordPress SEO');
    expect(items[2]['position']).toBe(3);
    expect(items[2]['item']).toBe('https://example.com/blog?category=wordpress-seo');
  });

  it('uses raw category slug as label when no label mapping exists', () => {
    const { container } = render(<BlogIndex {...baseProps} activeCategory="unknown-category" />);
    const schema = getBreadcrumbSchema(container);
    const items = (schema as Record<string, unknown>)['itemListElement'] as Array<
      Record<string, unknown>
    >;
    expect(items[2]['name']).toBe('unknown-category');
  });

  it('URL-encodes special characters in the category slug for the breadcrumb item URL', () => {
    // Slugs with spaces and plus signs must be percent-encoded; hyphens are safe and pass through unchanged.
    // This test verifies that encodeURIComponent is actually applied.
    const { container } = render(<BlogIndex {...baseProps} activeCategory="content & strategy" />);
    const schema = getBreadcrumbSchema(container);
    const items = (schema as Record<string, unknown>)['itemListElement'] as Array<
      Record<string, unknown>
    >;
    expect(items[2]['item']).toBe('https://example.com/blog?category=content%20%26%20strategy');
  });

  it('all JSON-LD blocks are valid JSON', () => {
    const { container } = render(
      <BlogIndex {...baseProps} activeCategory="content-optimization" />,
    );
    const scripts = container.querySelectorAll('script[type="application/ld+json"]');
    scripts.forEach((s) => {
      expect(() => JSON.parse(s.textContent || '')).not.toThrow();
    });
  });
});
