/**
 * @pulse-libs/core — Component Tests (clean suite)
 * @vitest-environment jsdom
 *
 * Acessa elementos exclusivamente via render().container.querySelector/querySelectorAll
 * para evitar conflitos com o accessibility tree vazio do jsdom.
 */
import { vi, beforeEach } from 'vitest';
import { render, fireEvent } from '@testing-library/react';
import {
  Button, Input, Alert, Card, CardHeader, CardTitle, CardBody,
} from '../src/components/index';

// ─ mocks globais ────────────────────────────────────────────────
beforeEach(() => {});
Object.defineProperty(global, 'matchMedia', {
  value: vi.fn().mockReturnValue({
    matches: false, media: '',
    addEventListener: vi.fn(), removeEventListener: vi.fn(),
  }),
  configurable: true,
});

// ═══════════════════════════════════════════════════════════════
//  Button
// ═══════════════════════════════════════════════════════════════
describe('Button', () => {
  it('renderiza <button> e está habilitado', () => {
    const b = render(<Button>Clique</Button>).container.querySelector('button')!;
    expect(b.textContent).toBe('Clique');
    expect(b.disabled).toBe(false);
  });

  it('é <button type="button"> por default', () => {
    expect(render(<Button />).container.querySelector('button')!.tagName.toLowerCase()).toBe('button');
  });

  it('variant primary → bg-indigo-600', () => {
    expect(render(<Button />).container.querySelector('button')!.classList.contains('bg-indigo-600')).toBe(true);
  });

  it('variant danger → bg-red-600', () => {
    expect(render(<Button variant="danger" />).container.querySelector('button')!.classList.contains('bg-red-600')).toBe(true);
  });

  it('variant secondary → border + bg-white', () => {
    const b = render(<Button variant="secondary" />).container.querySelector('button')!;
    expect(b.classList.contains('border')).toBe(true);
    expect(b.classList.contains('bg-white')).toBe(true);
  });

  it('variant ghost → text-gray-600 + hover sem bg', () => {
    const b = render(<Button variant="ghost" />).container.querySelector('button')!;
    expect(b.classList.contains('text-gray-600')).toBe(true);
    expect(b.classList.contains('hover:bg-gray-100')).toBe(true);
  });

  it('variant success → bg-emerald-600', () => {
    expect(render(<Button variant="success" />).container.querySelector('button')!.classList.contains('bg-emerald-600')).toBe(true);
  });

  it('size sm → px-2.5 py-1 text-xs', () => {
    const cls = render(<Button size="sm" />).container.querySelector('button')!.className;
    expect(cls).toContain('px-2.5');
    expect(cls).toContain('py-1');
    expect(cls).toContain('text-xs');
  });

  it('size md → px-4 py-2 text-sm', () => {
    const cls = render(<Button />).container.querySelector('button')!.className;
    expect(cls).toContain('px-4');
    expect(cls).toContain('py-2');
    expect(cls).toContain('text-sm');
  });

  it('size lg → px-6 py-3 text-base', () => {
    const cls = render(<Button size="lg" />).container.querySelector('button')!.className;
    expect(cls).toContain('px-6');
    expect(cls).toContain('py-3');
    expect(cls).toContain('text-base');
  });

  it('disabled → HTMLButtonElement.disabled = true', () => {
    expect(render(<Button disabled />).container.querySelector('button')!.disabled).toBe(true);
  });

  it('loading → disabled + SVG spinner', () => {
    const { container } = render(<Button loading />);
    const b = container.querySelector('button')!;
    expect(b.disabled).toBe(true);
    expect(container.querySelector('svg.animate-spin')).toBeTruthy();
  });

  it('leftIcon renderizado', () => {
    const { container } = render(<Button leftIcon={<span data-testid="li">←</span>} />);
    expect(container.querySelector('#li') || container.querySelector('[data-testid="li"]')).toBeTruthy();
  });

  it('rightIcon ausente durante loading', () => {
    const ri = render(
      <Button loading rightIcon={<span data-testid="ri">→</span>} />
    ).container.querySelector('[data-testid="ri"]');
    expect(ri).toBeNull();
  });

  it('rightIcon visível quando loading=false', () => {
    const ri = render(
      <Button rightIcon={<span data-testid="ri">→</span>} onClick={() => {}} />
    ).container.querySelector('[data-testid="ri"]');
    expect(ri).toBeTruthy();
  });

  it('chama onClick ao clicar', () => {
    const fn = vi.fn();
    render(<Button onClick={fn}>Click</Button>);
    fireEvent.click(render(<Button onClick={fn}>Click</Button>).container.querySelector('button')!);
    expect(fn).toHaveBeenCalledTimes(1);
  });

  it('NÃO chama onClick quando disabled', () => {
    const fn = vi.fn();
    const b = render(<Button disabled onClick={fn} />).container.querySelector('button')!;
    fireEvent.click(b); // <-- chama diretamente no botão
    expect(fn).not.toHaveBeenCalled();
  });

  it('NÃO chama onClick quando loading', () => {
    const fn = vi.fn();
    const b = render(<Button loading onClick={fn} />).container.querySelector('button')!;
    fireEvent.click(b);
    expect(fn).not.toHaveBeenCalled();
  });

  it('mescla className custom', () => {
    expect(
      render(<Button className="min-w-[200px]" />).container.querySelector('button')!.classList.contains('min-w-[200px]')
    ).toBe(true);
  });

  it('propaga aria-label e data-*', () => {
    const b = render(<Button aria-label="Salvar" data-action="salvar" />).container.querySelector('button')!;
    expect(b.getAttribute('aria-label')).toBe('Salvar');
    expect(b.getAttribute('data-action')).toBe('salvar');
  });
});

// ═══════════════════════════════════════════════════════════════
//  Input
// ═══════════════════════════════════════════════════════════════
describe('Input', () => {
  it('renderiza <input> quando placeholder dado', () => {
    expect(render(<Input placeholder="x" />).container.querySelector('input')).toBeTruthy();
  });

  it('renderiza <label> quando label é fornecido', () => {
    const c = render(<Input label="Nome" />).container;
    expect(c.querySelector('label')).toBeTruthy();
    expect(c.textContent).toContain('Nome');
  });

  it('label conectado ao input via htmlFor = id gerado do label', () => {
    const c = render(<Input label="Senha" />).container;
    const lbl = c.querySelector('label') as HTMLLabelElement;
    const inp = c.querySelector('input') as HTMLInputElement;
    expect(inp.id).toBe(lbl.htmlFor);
  });

  it('renderiza hint quando fornecido sem erro', () => {
    expect(render(<Input hint="dica" />).container.textContent).toContain('dica');
  });

  it('erro SHOW, hint HIDE quando ambos fornecidos', () => {
    const c = render(<Input error="err" hint="dica" />).container;
    expect(c.textContent).toContain('err');
    const spans = c.querySelectorAll('span');
    const temDica = Array.from(spans).some((s) => s.textContent === 'dica');
    expect(temDica).toBe(false);
  });

  it('span vazio quando nenhum hint nem erro', () => {
    expect(render(<Input />).container.querySelectorAll('span').length).toBe(0);
  });

  it('id explícito não é sobrescrito pelo label', () => {
    const inp = render(<Input id="custom" label="X" />).container.querySelector('input') as HTMLInputElement;
    expect(inp.id).toBe('custom');
  });

  it('type atributo passado para <input>', () => {
    expect(render(<Input type="tel" />).container.querySelector('input')!.getAttribute('type')).toBe('tel');
  });

  it('aciona onChange', () => {
    const fn = vi.fn();
    const inp = render(<Input onChange={fn} />).container.querySelector('input')!;
    Object.defineProperty(inp, 'value', { writable: true, value: 'abc' }); // <-- jsdom não altera value naturalmente
    fireEvent.change(inp);
    expect(fn).toHaveBeenCalledTimes(1);
  });

  it('aciona onFocus e onBlur', () => {
    const fFn = vi.fn(), bFn = vi.fn();
    const inp = render(<Input onFocus={fFn} onBlur={bFn} />).container.querySelector('input')!;
    fireEvent.focus(inp);
    fireEvent.blur(inp);
    expect(fFn).toHaveBeenCalledTimes(1);
    expect(bFn).toHaveBeenCalledTimes(1);
  });

  it('mescla className custom', () => {
    expect(
      render(<Input className="text-xs" />).container.querySelector('input')!.classList.contains('text-xs')
    ).toBe(true);
  });

  it('erro aplica border-red-500 + focus:ring-red-500', () => {
    const inp = render(<Input error="x" />).container.querySelector('input')!;
    expect(inp.classList.contains('border-red-500')).toBe(true);
    expect(inp.classList.contains('focus:ring-red-500')).toBe(true);
  });
});

// ═══════════════════════════════════════════════════════════════
//  Alert
// ═══════════════════════════════════════════════════════════════
describe('Alert', () => {
  it('renderiza children no DOM', () => {
    expect(render(<Alert>Msg</Alert>).container.textContent).toContain('Msg');
  });

  it('renderiza titulo + children', () => {
    const c = render(<Alert title="T">B</Alert>).container;
    expect(c.textContent).toContain('T');
    expect(c.textContent).toContain('B');
  });

  function alertDiv(): HTMLElement {
    return render(<Alert />).container.firstChild as HTMLElement;
  }

  it('variant info (default) → bg-blue-50 border-blue-200 text-blue-800', () => {
    const cls = alertDiv().className;
    expect(cls).toContain('bg-blue-50');
    expect(cls).toContain('border-blue-200');
    expect(cls).toContain('text-blue-800');
  });

  it('variant success → verde', () => {
    const cls = render(<Alert variant="success" />).container.firstChild!.className;
    expect(cls).toContain('bg-emerald-50');
    expect(cls).toContain('border-emerald-200');
  });

  it('variant warning → amarelo', () => {
    expect(render(<Alert variant="warning" />).container.firstChild!.className).toContain('bg-amber-50');
  });

  it('variant error → vermelho', () => {
    expect(render(<Alert variant="error" />).container.firstChild!.className).toContain('bg-red-50');
  });

  it('botão × aparece quando onClose fornecido', () => {
    // O botão close é um <button> filho direto do alert
    const btns = render(<Alert onClose={() => {}} />).container.querySelectorAll('button');
    expect(btns.length).toBeGreaterThan(0);
  });

  it('chama onClose ao clicar o botão ×', () => {
    const fn = vi.fn();
    const btns = render(<Alert onClose={fn} />).container.querySelectorAll('button');
    fireEvent.click(btns[0]);
    expect(fn).toHaveBeenCalledTimes(1);
  });

  it('NÃO mostra botão × quando onClose ausente', () => {
    expect(render(<Alert />).container.querySelectorAll('button').length).toBe(0);
  });

  it('emoji de variante aparece no DOM', () => {
    expect(render(<Alert variant="error" />).container.textContent).toContain('❌');
    expect(render(<Alert variant="success" />).container.textContent).toContain('✅');
    expect(render(<Alert variant="warning" />).container.textContent).toContain('⚠️');
    expect(render(<Alert variant="info" />).container.textContent).toContain('ℹ️');
  });
});

// ═══════════════════════════════════════════════════════════════
//  Card
// ═══════════════════════════════════════════════════════════════
describe('Card', () => {
  it('renderiza div.rounde-xl', () => {
    const el = render(<Card>X</Card>).container.firstChild as HTMLElement;
    expect(el.classList.contains('rounded-xl')).toBe(true);
    expect(el.textContent).toContain('X');
  });

  it('classes base completas', () => {
    const cls = render(<Card />).container.firstChild!.className;
    ['rounded-xl', 'border', 'border-gray-200', 'bg-white', 'shadow-sm'].forEach((c) => {
      expect(cls).toContain(c);
    });
  });

  it('mescla className custom', () => {
    expect(
      render(<Card className="mc" />).container.firstChild!.className
    ).toContain('mc');
  });

  it('aceita id + onClick', () => {
    const fn = vi.fn();
    const el = render(<Card id="c1" onClick={fn}>C</Card>).container.querySelector('#c1')!;
    expect(el).toBeTruthy();
    fireEvent.click(el);
    expect(fn).toHaveBeenCalled();
  });

  it('aninhamento sem perda', () => {
    const c = render(
      <Card>
        <CardHeader><CardTitle>T</CardTitle></CardHeader>
        <CardBody>B</CardBody>
      </Card>
    ).container;
    expect(c.textContent).toContain('T');
    expect(c.textContent).toContain('B');
  });
});

describe('CardHeader', () => {
  it('estilos px-5 py-4 border-b border-gray-100', () => {
    const cls = render(<CardHeader>H</CardHeader>).container.firstChild!.className;
    ['px-5', 'py-4', 'border-b', 'border-gray-100'].forEach((c) => {
      expect(cls).toContain(c);
    });
  });
});

describe('CardTitle', () => {
  it('renderiza como <h3>', () => {
    expect(render(<CardTitle>T</CardTitle>).container.querySelector('h3')).toBeTruthy();
  });

  it('texto igual ao passado', () => {
    expect(render(<CardTitle>Título</CardTitle>).container.querySelector('h3')!.textContent).toBe('Título');
  });

  it('estilos do título', () => {
    const cls = render(<CardTitle>T</CardTitle>).container.querySelector('h3')!.className;
    expect(cls).toContain('font-semibold');
    expect(cls).toContain('text-gray-900');
    expect(cls).toContain('text-base');
  });
});

describe('CardBody', () => {
  it('renderiza children', () => {
    expect(render(<CardBody>B</CardBody>).container.textContent).toContain('B');
  });

  it('estilos px-5 py-4', () => {
    const cls = render(<CardBody>B</CardBody>).container.querySelector('div')!.className;
    expect(cls).toContain('px-5');
    expect(cls).toContain('py-4');
  });

  it('mescla className custom', () => {
    expect(
      render(<CardBody className="bs" />).container.querySelector('div')!.className
    ).toContain('bs');
  });
});

describe('Card — stress composição', () => {
  it('Header + Title + Body na mesma árvore', () => {
    const c = render(
      <Card>
        <CardHeader><CardTitle>Título</CardTitle></CardHeader>
        <CardBody>Corpo</CardBody>
      </Card>
    ).container;
    expect(c.querySelector('.shadow-sm')).toBeTruthy();
    expect(c.textContent).toContain('Título');
    expect(c.textContent).toContain('Corpo');
  });

  it('100 linhas estrutura se mantém', () => {
    const lines = Array.from({ length: 100 }, (_, i) => `linha ${i + 1}`).join('\n');
    const c = render(
      <Card>
        <CardBody style={{ overflow: 'auto' }}>{lines}</CardBody>
      </Card>
    ).container;
    expect(c.querySelector('.shadow-sm')).toBeTruthy();
    expect(c.querySelector('.border')).toBeTruthy();
    expect(c.textContent).toContain('linha 1');
    expect(c.textContent).toContain('linha 100');
  });
});
