/**
 * NAV-001 V2 — SidebarSiteNavV2 integration tests.
 *
 * Covers Day-0 dimmed state, steady state, active hub auto-expand, chevron
 * toggle, localStorage persistence keyed by user, keyboard tab order. Plan
 * R3 lines 49–57.
 */
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { usePage } from '@inertiajs/react';

import type { HubReadiness, PageProps, SiteSummary } from '@/types';

import { SidebarProvider } from './sidebar-context';
import { SidebarSiteNavV2 } from './SidebarSiteNavV2';

const HUB_KEY_PREFIX = 'rankwiz.sidebar.expanded.';

function makeSite(overrides: Partial<SiteSummary> = {}): SiteSummary {
  return {
    id: '1',
    name: 'Acme',
    domain: 'acme.test',
    has_gsc: false,
    gsc_synced: false,
    has_wp: false,
    wp_connected: false,
    has_analysis: false,
    has_reviewed_recommendation: false,
    has_ai_key: false,
    ai_available: false,
    has_ga4: false,
    current_role: 'owner',
    ...overrides,
  } as SiteSummary;
}

function mockPage({
  url = '/sites/1/overview',
  site,
  readiness,
}: {
  url?: string;
  site: SiteSummary;
  readiness: HubReadiness | null;
}) {
  const props: Partial<PageProps> = {
    auth: { user: null },
    flash: {},
    errors: {},
    sites: [site],
    current_site: site,
    hub_readiness: readiness,
    polling_interval_ms: 5000,
  } as Partial<PageProps>;
  vi.mocked(usePage).mockReturnValue({
    url,
    props,
  } as unknown as ReturnType<typeof usePage>);
}

function renderWithProvider(
  userId: number | null = 42,
  ui: React.ReactNode = <SidebarSiteNavV2 />,
) {
  return render(<SidebarProvider userId={userId}>{ui}</SidebarProvider>);
}

describe('SidebarSiteNavV2 — NAV-001 V2', () => {
  beforeEach(() => {
    localStorage.clear();
  });
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('renders nothing when there are no sites', () => {
    vi.mocked(usePage).mockReturnValue({
      url: '/dashboard',
      props: {
        auth: { user: null },
        sites: [],
        flash: {},
        errors: {},
        polling_interval_ms: 5000,
      },
    } as unknown as ReturnType<typeof usePage>);
    const { container } = renderWithProvider();
    expect(container.querySelector('[data-testid="sidebar-site-nav-v2"]')).toBeNull();
  });

  it('Day-0: dims Recover and Performance (no GSC), keeps Overview/Settings enabled (R6UXS-004)', () => {
    const site = makeSite({ has_gsc: false });
    mockPage({
      site,
      readiness: {
        overview: true,
        // R6UXS-004: 'opportunities' + 'content' merged into 'recover'
        opportunities: false,
        content: false,
        recover: false,
        performance: false,
        settings: true,
      },
    });

    renderWithProvider();

    // Dim reason rendered for dimmed hubs only (recover replaces opportunities + content)
    expect(screen.getByTestId('hub-dim-reason-recover')).toBeInTheDocument();
    expect(screen.getByTestId('hub-dim-reason-performance')).toBeInTheDocument();
    expect(screen.queryByTestId('hub-dim-reason-overview')).toBeNull();
    expect(screen.queryByTestId('hub-dim-reason-settings')).toBeNull();
  });

  it('Day-0 dimmed hubs do NOT render their children (clean Day-0 contract)', () => {
    const site = makeSite({ has_gsc: false });
    mockPage({
      site,
      readiness: {
        overview: true,
        // R6UXS-004: recover hub replaces opportunities + content
        opportunities: false,
        content: false,
        recover: false,
        performance: false,
        settings: true,
      },
    });
    renderWithProvider();
    // Active hub auto-expands; expand Recover to confirm children stay hidden.
    // Recover hub is dimmed — children must not render.
    expect(screen.queryByTestId('hub-child-content-inventory')).toBeNull();
    expect(screen.queryByTestId('hub-child-batch-ai')).toBeNull();
  });

  it('Steady state: all hubs enabled, no dim subtext (R6UXS-004: recover replaces opportunities+content)', () => {
    const site = makeSite({ has_gsc: true, gsc_synced: true, has_analysis: true });
    mockPage({
      site,
      readiness: {
        overview: true,
        opportunities: true,
        content: true,
        recover: true,
        performance: true,
        settings: true,
      },
    });
    renderWithProvider();
    expect(screen.queryByTestId('hub-dim-reason-overview')).toBeNull();
    expect(screen.queryByTestId('hub-dim-reason-recover')).toBeNull();
    expect(screen.queryByTestId('hub-dim-reason-performance')).toBeNull();
  });

  it('auto-expands the active hub on first mount when no saved state exists', () => {
    // R6UXS-004: content-inventory is now a child of the 'recover' hub
    // (Opportunities + Content merged). URL /sites/1/content-inventory should
    // auto-expand the recover hub.
    const site = makeSite({ has_gsc: true, has_analysis: true });
    mockPage({
      url: '/sites/1/content-inventory',
      site,
      readiness: {
        overview: true,
        opportunities: true,
        content: true,
        recover: true,
        performance: true,
        settings: true,
      },
    });
    renderWithProvider();

    // content-inventory is a child of 'recover' — its hub should be
    // auto-expanded so children are visible.
    expect(screen.getByTestId('hub-children-recover')).toBeInTheDocument();
    expect(screen.getByTestId('hub-child-content-inventory')).toBeInTheDocument();
  });

  it('auto-expand does NOT persist (only explicit toggles write)', () => {
    const site = makeSite({ has_gsc: true, has_analysis: true });
    mockPage({
      url: '/sites/1/recommendations',
      site,
      readiness: {
        overview: true,
        opportunities: true,
        content: true,
        recover: true,
        performance: true,
        settings: true,
      },
    });
    renderWithProvider(42);

    // Auto-expansion fired but storage stays empty
    expect(localStorage.getItem(`${HUB_KEY_PREFIX}42`)).toBeNull();
  });

  it('chevron click toggles hub expansion and persists to per-user localStorage (R6UXS-004: recover hub)', async () => {
    const user = userEvent.setup();
    const site = makeSite({ has_gsc: true, has_analysis: true });
    mockPage({
      url: '/sites/1/overview',
      site,
      readiness: {
        overview: true,
        opportunities: true,
        content: true,
        recover: true,
        performance: true,
        settings: true,
      },
    });
    renderWithProvider(42);

    // Recover hub is not active (URL is overview), so children hidden initially.
    expect(screen.queryByTestId('hub-children-recover')).toBeNull();

    await user.click(screen.getByTestId('hub-toggle-recover'));
    expect(screen.getByTestId('hub-children-recover')).toBeInTheDocument();

    const stored = localStorage.getItem(`${HUB_KEY_PREFIX}42`);
    expect(JSON.parse(stored as string)).toContain('recover');

    // Toggle off
    await user.click(screen.getByTestId('hub-toggle-recover'));
    expect(screen.queryByTestId('hub-children-recover')).toBeNull();
    const stored2 = JSON.parse(localStorage.getItem(`${HUB_KEY_PREFIX}42`) as string);
    expect(stored2).not.toContain('recover');
  });

  it('hydrates saved expanded state on mount (R6UXS-004: recover hub replaces content)', () => {
    // R6UXS-004: 'content' hub is merged into 'recover'; use 'recover' in localStorage.
    localStorage.setItem(`${HUB_KEY_PREFIX}42`, JSON.stringify(['recover', 'settings']));
    const site = makeSite({ has_gsc: true, has_analysis: true });
    mockPage({
      url: '/sites/1/overview',
      site,
      readiness: {
        overview: true,
        opportunities: true,
        content: true,
        recover: true,
        performance: true,
        settings: true,
      },
    });
    renderWithProvider(42);

    expect(screen.getByTestId('hub-children-recover')).toBeInTheDocument();
    expect(screen.getByTestId('hub-children-settings')).toBeInTheDocument();
  });

  it('renders all 4 hubs in fixed order (R6UXS-004: Opportunities + Content merged into Recover)', () => {
    const site = makeSite({ has_gsc: true });
    mockPage({
      site,
      readiness: {
        overview: true,
        opportunities: true,
        content: true,
        recover: true,
        performance: true,
        settings: true,
      },
    });
    renderWithProvider();
    const hubs = screen.getAllByTestId(/^hub-item-/);
    // R6UXS-004: 4 hubs — Overview · Recover · Performance · Settings
    expect(hubs.map((el) => el.dataset.testid)).toEqual([
      'hub-item-overview',
      'hub-item-recover',
      'hub-item-performance',
      'hub-item-settings',
    ]);
  });

  it('R9UX-101: quick-action link text is "Recovery Runs", not "Batch AI"', () => {
    const site = makeSite({ has_gsc: true, gsc_synced: true, has_analysis: true });
    mockPage({
      url: '/sites/1/overview',
      site,
      readiness: {
        overview: true,
        opportunities: true,
        content: true,
        recover: true,
        performance: true,
        settings: true,
      },
    });
    renderWithProvider();

    const quickActions = screen.getByTestId('sidebar-quick-actions');
    expect(quickActions).toBeInTheDocument();

    // R9UX-101: quick-action must say 'Recovery Runs'.
    // The mock route resolves batch-ai.index to '/sites/1/batch-ai'.
    const link = Array.from(quickActions.querySelectorAll('a')).find((a) =>
      /batch-ai/.test(a.getAttribute('href') ?? ''),
    );
    expect(link).toBeDefined();
    expect(link?.textContent?.trim()).toBe('Recovery Runs');

    // 'Batch AI' literal must not appear in the quick-actions widget.
    expect(quickActions.textContent).not.toMatch(/Batch AI/i);
  });

  it('keyboard Tab order walks hub-link → chevron → next hub-link', async () => {
    const user = userEvent.setup();
    const site = makeSite({ has_gsc: true });
    mockPage({
      url: '/sites/1/overview',
      site,
      readiness: {
        overview: true,
        opportunities: true,
        content: true,
        recover: true,
        performance: true,
        settings: true,
      },
    });
    renderWithProvider();

    const overviewLink = screen.getByTestId('hub-link-overview');
    overviewLink.focus();
    await user.tab();
    expect(document.activeElement).toBe(screen.getByTestId('hub-toggle-overview'));
  });

  // ─── Cut 5 — sidebar consistency restored: all children always render ──────────

  it('Day-0 enabled hub renders ALL children including disabled ones (consistency over prune)', () => {
    // Cut 3 originally pruned disabled children, but it produced empty hubs
    // (Opportunities/Performance showed 0 children) which read as "broken."
    // Cut 5 reverts: show every child, with disabled-tooltip on gated ones.
    // R6IA-002: Overview hub trimmed to Analyze + ROI (Pipeline + Traffic Alerts
    // moved to hub page — no longer sidebar children).
    // R6UXS-004: 'recover' hub replaces 'opportunities' + 'content'.
    const site = makeSite({ has_gsc: false, has_analysis: false });
    mockPage({
      url: '/sites/1/overview',
      site,
      readiness: {
        overview: true,
        opportunities: false,
        content: false,
        recover: false,
        performance: false,
        settings: true,
      },
    });
    renderWithProvider();

    // Always-on child
    expect(screen.getByTestId('hub-child-roi')).toBeInTheDocument();
    // Disabled-because-no-GSC sibling still renders (as disabled span; we
    // assert the LABEL since disabled children are not links).
    expect(screen.getByText('Analyze')).toBeInTheDocument();
    // Pipeline and Traffic Alerts are NOT sidebar children after R6IA-002.
    expect(screen.queryByTestId('hub-child-pipeline')).toBeNull();
    expect(screen.queryByText('Traffic Alerts')).toBeNull();
  });

  it('active disabled child still renders as the active link (you-are-here beats you-cant-be-here)', () => {
    // HubChild's active-overrides-disabled rule still applies: even though
    // /analyze is gated, the user is on it, so it must render as the
    // active link, not the disabled span.
    // R6UXS-004: 'recover' hub replaces 'opportunities' + 'content'.
    const site = makeSite({ has_gsc: false, has_analysis: false });
    mockPage({
      url: '/sites/1/analyze',
      site,
      readiness: {
        overview: true,
        opportunities: false,
        content: false,
        recover: false,
        performance: false,
        settings: true,
      },
    });
    renderWithProvider();

    const analyze = screen.getByTestId('hub-child-analyze');
    expect(analyze).toBeInTheDocument();
    expect(analyze).toHaveAttribute('aria-current', 'page');
  });
});
