import { describe, it, expect, beforeEach } from 'vitest';
import { cleanup, render, screen } from '@testing-library/react';
import { Sparkline } from '../Sparkline.js';
import { BarSparkline } from '../BarSparkline.js';
import { KpiCard } from '../KpiCard.js';
import { ProgressRing } from '../ProgressRing.js';
import { Heatmap } from '../Heatmap.js';

describe('Sparkline', () => {
  beforeEach(() => cleanup());
  it('renders svg with given dimensions', () => {
    render(<Sparkline data={[1, 2, 3, 2, 4, 5]} width={100} height={32} />);
    const el = screen.getByTestId('bl-sparkline');
    expect(el.getAttribute('width')).toBe('100');
    expect(el.getAttribute('height')).toBe('32');
  });
  it('hides last point when showLastPoint=false', () => {
    render(<Sparkline data={[1, 2, 3]} showLastPoint={false} />);
    expect(screen.getByTestId('bl-sparkline').querySelector('circle')).toBeNull();
  });
  it('does not draw a path with fewer than 2 points', () => {
    render(<Sparkline data={[]} />);
    const path = screen.getByTestId('bl-sparkline').querySelector('path');
    expect(path?.getAttribute('d')).toBe('');
  });
  it('derives gradient id from React useId, not Math.random (SSR-safe)', () => {
    // Regression: previously used Math.random() which caused Next.js
    // hydration mismatches. React's useId emits ids shaped like ":r3:" /
    // "_r_3_" — deterministic per render-position. We assert the id
    // matches that shape (only the React-id chars), with no 6-hex random
    // suffix.
    const { container } = render(<Sparkline data={[1, 2, 3]} />);
    const id = container.querySelector('linearGradient')?.getAttribute('id') ?? '';
    expect(id).toMatch(/^bl-spark-[a-zA-Z0-9_-]+$/);
    // Math.random().toString(36).slice(2, 8) always produced exactly 6
    // chars after `bl-spark-`. useId produces something different.
    const suffix = id.replace(/^bl-spark-/, '');
    expect(/^[0-9a-f]{6}$/.test(suffix)).toBe(false);
  });
  it('omits the last-point circle when given a single datum', () => {
    render(<Sparkline data={[5]} />);
    // Single-point series has no meaningful trend, so no stale (0,0) circle.
    expect(screen.getByTestId('bl-sparkline').querySelector('circle')).toBeNull();
  });
});

describe('BarSparkline', () => {
  beforeEach(() => cleanup());
  it('renders one rect per datum', () => {
    render(<BarSparkline data={[1, 4, 2, 6, 3]} />);
    const rects = screen.getByTestId('bl-bar-sparkline').querySelectorAll('rect');
    expect(rects.length).toBe(5);
  });
  it('emphasizes the max bar (opacity=1)', () => {
    render(<BarSparkline data={[1, 9, 2]} />);
    const rects = screen.getByTestId('bl-bar-sparkline').querySelectorAll('rect');
    // The second bar (index 1) is the max.
    expect(rects[1]?.getAttribute('opacity')).toBe('1');
    expect(rects[0]?.getAttribute('opacity')).toBe('0.55');
  });
});

describe('KpiCard', () => {
  beforeEach(() => cleanup());
  it('renders label + headline value', () => {
    render(<KpiCard label="DAU" value="8,421" />);
    expect(screen.getByText('DAU')).toBeDefined();
    expect(screen.getByTestId('bl-kpi-value').textContent).toBe('8,421');
  });
  it('positive delta renders with up arrow and success color', () => {
    render(<KpiCard label="X" value={1} deltaPercent={6.6} />);
    const d = screen.getByTestId('bl-kpi-delta');
    expect(d.textContent).toContain('+6.6%');
    expect(d.textContent).toContain('▲');
  });
  it('negative delta renders with down arrow', () => {
    render(<KpiCard label="X" value={1} deltaPercent={-3.2} />);
    expect(screen.getByTestId('bl-kpi-delta').textContent).toContain('▼');
  });
  it('goodWhen=lower inverts the success/danger interpretation', () => {
    // For latency: a negative delta (faster) is good.
    const { rerender } = render(
      <KpiCard label="latency" value="120ms" deltaPercent={-10} goodWhen="lower" />
    );
    expect(screen.getByTestId('bl-kpi-delta').getAttribute('data-good')).toBe('true');
    rerender(<KpiCard label="latency" value="120ms" deltaPercent={5} goodWhen="lower" />);
    expect(screen.getByTestId('bl-kpi-delta').getAttribute('data-good')).toBe('false');
  });
  it('renders sparkline when trend provided', () => {
    render(<KpiCard label="X" value={1} trend={[1, 2, 3, 4]} />);
    expect(screen.getByTestId('bl-sparkline')).toBeDefined();
  });
});

describe('ProgressRing', () => {
  beforeEach(() => cleanup());
  it('clamps value to 0..1 and emits aria-label percent', () => {
    const { rerender } = render(<ProgressRing value={2} />);
    expect(screen.getByTestId('bl-progress-ring').getAttribute('aria-label')).toBe('100 percent');
    rerender(<ProgressRing value={-1} />);
    expect(screen.getByTestId('bl-progress-ring').getAttribute('aria-label')).toBe('0 percent');
  });
  it('treats NaN / non-finite value as 0 percent (no `NaN percent` aria-label)', () => {
    render(<ProgressRing value={Number.NaN} />);
    expect(screen.getByTestId('bl-progress-ring').getAttribute('aria-label')).toBe('0 percent');
    cleanup();
    render(<ProgressRing value={Number.POSITIVE_INFINITY} />);
    expect(screen.getByTestId('bl-progress-ring').getAttribute('aria-label')).toBe('0 percent');
  });
  it('renders inner content slot', () => {
    render(
      <ProgressRing value={0.5}>
        <span data-testid="center">50%</span>
      </ProgressRing>
    );
    expect(screen.getByTestId('center').textContent).toBe('50%');
  });
});

describe('Heatmap', () => {
  beforeEach(() => cleanup());
  it('renders one cell per datum', () => {
    const cells = Array.from({ length: 14 }, (_, i) => ({ date: `d${i}`, value: i }));
    render(<Heatmap cells={cells} />);
    expect(screen.getAllByTestId(/bl-heatmap-cell-/).length).toBe(14);
  });
  it('zero-value cells render with empty intensity', () => {
    render(
      <Heatmap
        cells={[
          { date: 'a', value: 0 },
          { date: 'b', value: 5 },
        ]}
      />
    );
    const a = screen.getByTestId('bl-heatmap-cell-0');
    const b = screen.getByTestId('bl-heatmap-cell-1');
    expect(a.getAttribute('data-intensity')).toBe('0.00');
    expect(parseFloat(b.getAttribute('data-intensity') ?? '0')).toBeGreaterThan(0);
  });
});
