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

import { CostMeter } from '../CostMeter.js';
import { ConfidenceTag } from '../ConfidenceTag.js';
import { RefusalCard } from '../RefusalCard.js';

beforeEach(() => cleanup());

describe('CostMeter', () => {
  it('renders token count without budget (neutral tier)', () => {
    render(<CostMeter tokensUsed={1234} />);
    const el = screen.getByTestId('bl-cost-meter');
    expect(el.getAttribute('data-tier')).toBe('neutral');
    expect(el.textContent).toMatch(/1,234/);
  });

  it('tints ok / warn / danger based on budget %', () => {
    const { rerender } = render(<CostMeter tokensUsed={100} budgetTokens={1000} />);
    expect(screen.getByTestId('bl-cost-meter').getAttribute('data-tier')).toBe('ok');
    rerender(<CostMeter tokensUsed={750} budgetTokens={1000} />);
    expect(screen.getByTestId('bl-cost-meter').getAttribute('data-tier')).toBe('warn');
    rerender(<CostMeter tokensUsed={970} budgetTokens={1000} />);
    expect(screen.getByTestId('bl-cost-meter').getAttribute('data-tier')).toBe('danger');
  });

  it('shows USD estimate when costPer1kUsd is provided', () => {
    render(<CostMeter tokensUsed={2000} costPer1kUsd={0.5} />);
    expect(screen.getByTestId('bl-cost-meter-usd').textContent).toBe('$1.00');
  });

  it('hideUsd suppresses the dollar readout', () => {
    render(<CostMeter tokensUsed={2000} costPer1kUsd={0.5} hideUsd />);
    expect(screen.queryByTestId('bl-cost-meter-usd')).toBeNull();
  });

  it('clamps negative + non-finite token counts to 0', () => {
    render(<CostMeter tokensUsed={Number.NaN as number} />);
    expect(screen.getByTestId('bl-cost-meter').textContent).toMatch(/^[^0-9]*0[^0-9]/);
  });
});

describe('ConfidenceTag', () => {
  it('buckets numeric scores into high / medium / low', () => {
    const { rerender } = render(<ConfidenceTag value={0.9} />);
    expect(screen.getByTestId('bl-confidence-tag').getAttribute('data-level')).toBe('high');
    rerender(<ConfidenceTag value={0.6} />);
    expect(screen.getByTestId('bl-confidence-tag').getAttribute('data-level')).toBe('medium');
    rerender(<ConfidenceTag value={0.2} />);
    expect(screen.getByTestId('bl-confidence-tag').getAttribute('data-level')).toBe('low');
  });

  it('maps out-of-range numbers to unknown', () => {
    render(<ConfidenceTag value={1.5} />);
    expect(screen.getByTestId('bl-confidence-tag').getAttribute('data-level')).toBe('unknown');
  });

  it('accepts an explicit ConfidenceLevel string', () => {
    render(<ConfidenceTag value="medium" />);
    expect(screen.getByTestId('bl-confidence-tag').getAttribute('data-level')).toBe('medium');
  });

  it('respects custom thresholds', () => {
    render(<ConfidenceTag value={0.65} thresholds={{ high: 0.6, medium: 0.3 }} />);
    expect(screen.getByTestId('bl-confidence-tag').getAttribute('data-level')).toBe('high');
  });

  it('renders percentage when showScore is true', () => {
    render(<ConfidenceTag value={0.84} showScore />);
    expect(screen.getByTestId('bl-confidence-tag').textContent).toMatch(/84%/);
  });
});

describe('RefusalCard', () => {
  it('renders heading + glyph + explanation based on reason', () => {
    render(
      <RefusalCard
        reason="safety"
        explanation="I can't generate that kind of content."
      />,
    );
    const el = screen.getByTestId('bl-refusal-card');
    expect(el.getAttribute('data-reason')).toBe('safety');
    expect(el.textContent).toMatch(/I can't help with that/);
    expect(el.textContent).toMatch(/I can't generate that kind of content/);
  });

  it('caps actions at 3 and fires the right onClick', () => {
    const a = vi.fn();
    const b = vi.fn();
    const c = vi.fn();
    const d = vi.fn();
    render(
      <RefusalCard
        reason="capability"
        explanation="Try rephrasing."
        actions={[
          { label: 'One', onClick: a },
          { label: 'Two', onClick: b, primary: true },
          { label: 'Three', onClick: c },
          { label: 'Four', onClick: d },
        ]}
      />,
    );
    expect(screen.queryByText('Four')).toBeNull();
    fireEvent.click(screen.getByTestId('bl-refusal-card-action-1'));
    expect(b).toHaveBeenCalledOnce();
    expect(a).not.toHaveBeenCalled();
  });

  it('falls back to "Declined" for an unknown reason', () => {
    render(
      <RefusalCard
        reason={'unknown' as const}
        explanation="No reason given."
      />,
    );
    expect(screen.getByTestId('bl-refusal-card').textContent).toMatch(/Declined/);
  });

  it('renders the footer slot', () => {
    render(
      <RefusalCard
        reason="policy"
        explanation="…"
        footer={<a href="/policy" data-testid="policy-link">Read policy</a>}
      />,
    );
    expect(screen.getByTestId('policy-link')).toBeDefined();
  });
});

// ── Wave 13.C.4-.6 ──────────────────────────────────────────────────────

import { PrivacyBadge } from '../PrivacyBadge.js';
import { ProvenanceDrawer, type ProvenanceEvent } from '../ProvenanceDrawer.js';
import { DebugOverlay } from '../DebugOverlay.js';

describe('PrivacyBadge', () => {
  it('records mode + label for each of the 4 modes', () => {
    const modes = ['on-device', 'cloud', 'hybrid', 'unknown'] as const;
    modes.forEach((m) => {
      cleanup();
      render(<PrivacyBadge mode={m} />);
      const el = screen.getByTestId('bl-privacy-badge');
      expect(el.getAttribute('data-mode')).toBe(m);
    });
  });
  it('renders detail line when provided', () => {
    render(<PrivacyBadge mode="cloud" detail="gpt-4o-mini" />);
    expect(screen.getByTestId('bl-privacy-badge').textContent).toMatch(/gpt-4o-mini/);
  });
  it('iconOnly variant suppresses text content', () => {
    render(<PrivacyBadge mode="on-device" iconOnly />);
    const el = screen.getByTestId('bl-privacy-badge');
    expect(el.textContent ?? '').toBe('');
  });
});

const EVENTS: ProvenanceEvent[] = [
  { id: 'e1', at: '2026-05-27T19:30:00Z', kind: 'prompt', summary: 'User asked about X' },
  { id: 'e2', at: '2026-05-27T19:30:01Z', kind: 'model', summary: 'gpt-4o picked the answer', details: <code>tokens=412</code> },
];

describe('ProvenanceDrawer', () => {
  it('renders null when closed', () => {
    render(<ProvenanceDrawer open={false} onClose={() => {}} events={EVENTS} />);
    expect(screen.queryByTestId('bl-provenance-drawer')).toBeNull();
  });
  it('renders dialog + events when open', () => {
    render(<ProvenanceDrawer open onClose={() => {}} events={EVENTS} />);
    expect(screen.getByTestId('bl-provenance-drawer').getAttribute('aria-modal')).toBe('true');
    expect(screen.getAllByTestId('bl-provenance-row')).toHaveLength(2);
  });
  it('calls onClose when backdrop is clicked', () => {
    const onClose = vi.fn();
    render(<ProvenanceDrawer open onClose={onClose} events={EVENTS} />);
    fireEvent.click(screen.getByTestId('bl-provenance-drawer-backdrop'));
    expect(onClose).toHaveBeenCalledOnce();
  });
  it('calls onClose on Escape', () => {
    const onClose = vi.fn();
    render(<ProvenanceDrawer open onClose={onClose} events={EVENTS} />);
    fireEvent.keyDown(window, { key: 'Escape' });
    expect(onClose).toHaveBeenCalled();
  });
  it('shows the empty-state copy when events=[]', () => {
    render(<ProvenanceDrawer open onClose={() => {}} events={[]} />);
    expect(screen.getByTestId('bl-provenance-drawer').textContent).toMatch(/No events/);
  });
});

describe('DebugOverlay', () => {
  it('does NOT open on plain click', () => {
    render(
      <DebugOverlay payload={{ title: 'Bubble', data: { a: 1 } }}>
        <span data-testid="child">hello</span>
      </DebugOverlay>,
    );
    fireEvent.click(screen.getByTestId('child'));
    expect(screen.queryByTestId('bl-debug-overlay-modal')).toBeNull();
  });
  it('opens on Shift-click and shows JSON payload', () => {
    render(
      <DebugOverlay payload={{ id: 'm1', title: 'Bubble', data: { hello: 'world' } }}>
        <span data-testid="child">hello</span>
      </DebugOverlay>,
    );
    fireEvent.click(screen.getByTestId('bl-debug-overlay-wrapper'), { shiftKey: true });
    const pre = screen.getByTestId('bl-debug-overlay-payload');
    expect(pre.textContent).toMatch(/"hello": "world"/);
  });
  it('respects disabled — no modal opens even with Shift', () => {
    render(
      <DebugOverlay disabled payload={{ data: {} }}>
        <span data-testid="child">hello</span>
      </DebugOverlay>,
    );
    fireEvent.click(screen.getByTestId('bl-debug-overlay-wrapper'), { shiftKey: true });
    expect(screen.queryByTestId('bl-debug-overlay-modal')).toBeNull();
  });
  it('alt modifier honoured when configured', () => {
    render(
      <DebugOverlay modifier="alt" payload={{ data: 1 }}>
        <span data-testid="child">x</span>
      </DebugOverlay>,
    );
    fireEvent.click(screen.getByTestId('bl-debug-overlay-wrapper'), { shiftKey: true });
    expect(screen.queryByTestId('bl-debug-overlay-modal')).toBeNull();
    fireEvent.click(screen.getByTestId('bl-debug-overlay-wrapper'), { altKey: true });
    expect(screen.getByTestId('bl-debug-overlay-modal')).toBeDefined();
  });
});
