/**
 * @jest-environment jsdom
 *
 * Tests for PlatformAnalyticsCard — the clickable admin dashboard tile that
 * eagerly fetches a traffic summary and degrades gracefully when the fetch
 * fails (the panel surfaces the real error on open).
 */

import React from 'react';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { PlatformAnalyticsCard } from '../PlatformAnalyticsCard';

// CSS module is auto-mocked by next/jest into an identity-proxy, so class names
// resolve to their key strings. We never assert on class names here.

function summaryResponse(body: unknown): Response {
  return {
    ok: true,
    status: 200,
    json: async () => body,
  } as Response;
}

function errorResponse(status = 500): Response {
  return {
    ok: false,
    status,
    json: async () => ({ error: 'boom' }),
  } as Response;
}

describe('PlatformAnalyticsCard', () => {
  const realFetch = global.fetch;

  afterEach(() => {
    global.fetch = realFetch;
    jest.clearAllMocks();
  });

  it('shows the loading copy before the fetch resolves, then renders traffic', async () => {
    global.fetch = jest
      .fn()
      .mockResolvedValue(
        summaryResponse({ traffic: { current: 1234, previous: 1000, changePercent: 23.4 } })
      ) as unknown as typeof fetch;

    render(<PlatformAnalyticsCard onClick={jest.fn()} />);

    // Loading copy is present synchronously on first render.
    expect(screen.getByText('Loading platform metrics…')).toBeInTheDocument();

    // After the fetch resolves, the formatted traffic + trend appears.
    await waitFor(() => {
      expect(screen.getByText(/1\.2K visits/)).toBeInTheDocument();
    });
    // Positive change renders an up arrow with the formatted percentage.
    expect(screen.getByText(/↑ 23\.4%/)).toBeInTheDocument();
    expect(screen.queryByText('Loading platform metrics…')).not.toBeInTheDocument();
  });

  it('renders a down arrow for negative change', async () => {
    global.fetch = jest
      .fn()
      .mockResolvedValue(
        summaryResponse({ traffic: { current: 2_500_000, previous: 5_000_000, changePercent: -12.7 } })
      ) as unknown as typeof fetch;

    render(<PlatformAnalyticsCard onClick={jest.fn()} />);

    await waitFor(() => {
      expect(screen.getByText(/2\.5M visits/)).toBeInTheDocument();
    });
    expect(screen.getByText(/↓ 12\.7%/)).toBeInTheDocument();
  });

  it('falls back to the static description when the fetch fails (rejected promise)', async () => {
    global.fetch = jest.fn().mockRejectedValue(new Error('network')) as unknown as typeof fetch;

    render(<PlatformAnalyticsCard onClick={jest.fn()} />);

    // The empty catch swallows the error; once loading clears, the static
    // fallback copy is shown and nothing crashes.
    await waitFor(() => {
      expect(screen.getByText('Platform-wide traffic and engagement')).toBeInTheDocument();
    });
  });

  it('falls back to the static description on a non-ok response (traffic stays null)', async () => {
    global.fetch = jest.fn().mockResolvedValue(errorResponse(500)) as unknown as typeof fetch;

    render(<PlatformAnalyticsCard onClick={jest.fn()} />);

    await waitFor(() => {
      expect(screen.getByText('Platform-wide traffic and engagement')).toBeInTheDocument();
    });
  });

  it('invokes onClick when the card is pressed', async () => {
    global.fetch = jest
      .fn()
      .mockResolvedValue(
        summaryResponse({ traffic: { current: 10, previous: 10, changePercent: 0 } })
      ) as unknown as typeof fetch;
    const onClick = jest.fn();

    render(<PlatformAnalyticsCard onClick={onClick} />);

    fireEvent.click(screen.getByRole('button'));
    expect(onClick).toHaveBeenCalledTimes(1);

    // Let the on-mount fetch settle inside act() so the trailing state update
    // doesn't fire during teardown (which triggers a not-wrapped-in-act warning).
    await waitFor(() => {
      expect(screen.getByText(/10 visits/)).toBeInTheDocument();
    });
  });
});
