import { render, screen, fireEvent } from '@testing-library/react';
import { describe, test, expect, vi } from 'vitest';
import { useState } from 'react';
import { userEvent } from '@testing-library/user-event';
import TagInput from './TagInput.js';

describe('TagInput', () => {
  test('renders with default props', () => {
    render(<TagInput tags={[]} setTags={vi.fn()} />);

    const container = screen.getByRole('textbox');
    expect(container).toBeInTheDocument();
    expect(container).toHaveClass('tag-input__input');
  });

  test('renders with custom placeholder', () => {
    render(<TagInput placeholder="Enter tags..." tags={[]} setTags={vi.fn()} />);

    expect(screen.getByPlaceholderText('Enter tags...')).toBeInTheDocument();
  });

  test('renders with custom aria-label', () => {
    render(<TagInput aria-label="Tag input field" tags={[]} setTags={vi.fn()} />);

    expect(screen.getByLabelText('Tag input field')).toBeInTheDocument();
  });

  test('applies correct CSS classes', () => {
    render(<TagInput tags={[]} setTags={vi.fn()} />);

    // eslint-disable-next-line testing-library/no-node-access -- verifying CSS class structure
    expect(document.querySelector('.tag-input')).toBeInTheDocument();
    // eslint-disable-next-line testing-library/no-node-access -- verifying CSS class structure
    expect(document.querySelector('.tag-input__tags')).toBeInTheDocument();
  });

  describe('Adding tags', () => {
    test('adds tag when Enter is pressed', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: 'test-tag' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      expect(screen.getByText('test-tag')).toBeInTheDocument();
      expect(input).toHaveValue('');
    });

    test('adds tag when comma is pressed', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: 'test-tag' } });
      fireEvent.keyDown(input, { key: ',', code: 'Comma' });

      expect(screen.getByText('test-tag')).toBeInTheDocument();
      expect(input).toHaveValue('');
    });

    test('adds tag on blur', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: 'test-tag' } });
      fireEvent.blur(input);

      expect(screen.getByText('test-tag')).toBeInTheDocument();
    });

    test('trims whitespace from tags', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: '  test-tag  ' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      expect(screen.getByText('test-tag')).toBeInTheDocument();
    });

    test('does not add empty tags', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: '   ' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      expect(screen.queryByText('   ')).not.toBeInTheDocument();
    });

    test('does not add duplicate tags', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');

      // Add first tag
      fireEvent.change(input, { target: { value: 'test-tag' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      // Try to add same tag again
      fireEvent.change(input, { target: { value: 'test-tag' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      const tags = screen.getAllByText('test-tag');
      expect(tags).toHaveLength(1);
      expect(screen.getByRole('alert')).toBeInTheDocument();
      expect(screen.getByText('Duplicate tags are not allowed')).toBeInTheDocument();

      fireEvent.change(input, { target: { value: 'test-tag2' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
      expect(screen.queryByText('Duplicate tags are not allowed')).not.toBeInTheDocument();
    });

    test('respects maxTags limit', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} maxKeywords={2} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');

      // Add first tag
      fireEvent.change(input, { target: { value: 'tag1' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      // Add second tag
      fireEvent.change(input, { target: { value: 'tag2' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      // Try to add third tag
      fireEvent.change(input, { target: { value: 'tag3' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      expect(screen.getByText('tag1')).toBeInTheDocument();
      expect(screen.getByText('tag2')).toBeInTheDocument();
      expect(screen.queryByText('tag3')).not.toBeInTheDocument();
    });

    test('hides input when maxTags limit is reached', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} maxKeywords={1} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: 'tag1' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
    });
  });

  describe('Removing tags', () => {
    test('removes tag when remove button is clicked', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: 'test-tag' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      const removeButton = screen.getByLabelText('Remove test-tag');
      fireEvent.click(removeButton);

      expect(screen.queryByText('test-tag')).not.toBeInTheDocument();
    });

    test('removes last tag when Backspace is pressed on empty input', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');

      // Add two tags
      fireEvent.change(input, { target: { value: 'tag1' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
      fireEvent.change(input, { target: { value: 'tag2' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      // Press backspace on empty input
      fireEvent.keyDown(input, { key: 'Backspace', code: 'Backspace' });

      expect(screen.getByText('tag1')).toBeInTheDocument();
      expect(screen.queryByText('tag2')).not.toBeInTheDocument();
    });

    test('does not remove tag when Backspace is pressed on non-empty input', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');

      // Add a tag
      fireEvent.change(input, { target: { value: 'tag1' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      // Type something in input and press backspace
      fireEvent.change(input, { target: { value: 'text' } });
      fireEvent.keyDown(input, { key: 'Backspace', code: 'Backspace' });

      expect(screen.getByText('tag1')).toBeInTheDocument();
    });

    test('shows input again after removing tags when below maxTags', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} maxKeywords={1} />;
      };
      render(<TagWrapper />);

      // Add tag to reach limit
      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: 'tag1' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      // Input should be hidden
      expect(screen.queryByRole('textbox')).not.toBeInTheDocument();

      // Remove tag
      const removeButton = screen.getByLabelText('Remove tag1');
      fireEvent.click(removeButton);

      // Input should be visible again
      expect(screen.getByRole('textbox')).toBeInTheDocument();
    });
  });

  describe('Placeholder behavior', () => {
    test('shows placeholder only when no tags exist', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} placeholder="Enter tags..." />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      expect(input).toHaveAttribute('placeholder', 'Enter tags...');

      // Add a tag
      fireEvent.change(input, { target: { value: 'tag1' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      // Placeholder should be empty now
      const inputAfterTag = screen.getByRole('textbox');
      expect(inputAfterTag).toHaveAttribute('placeholder', '');
    });

    test('does not show placeholder when tags exist', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} placeholder="Enter tags..." />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');

      // Add a tag
      fireEvent.change(input, { target: { value: 'existing-tag' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      // Verify tag exists and placeholder is not present
      expect(screen.getByText('existing-tag')).toBeInTheDocument();
      expect(input).toHaveAttribute('placeholder', '');
      expect(input).not.toHaveAttribute('placeholder', 'Enter tags...');
    });
  });

  describe('Container interaction', () => {
    test('focuses input when container is clicked', async () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      // eslint-disable-next-line testing-library/no-node-access -- testing label click-to-focus behavior
      const container = document.querySelector('.tag-input') as Element;
      const input = screen.getByRole('textbox');

      await userEvent.click(container);

      expect(input).toHaveFocus();
    });
  });

  describe('State management', () => {
    test('calls setTags when tags are added', () => {
      const mockSetState = vi.fn();
      render(<TagInput tags={[]} setTags={mockSetState} />);

      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: 'test-tag' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      expect(mockSetState).toHaveBeenNthCalledWith(1, ['test-tag']);
    });

    test('calls setTags when tags are removed', () => {
      const setTagsSpy = vi.fn();
      const TestWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);

        const wrappedSetTags = (newTags: string[]) => {
          setTagsSpy(newTags); // Spy on the call
          setTags(newTags); // Actually update state
        };

        return <TagInput tags={tags} setTags={wrappedSetTags} />;
      };
      render(<TestWrapper />);

      const input = screen.getByRole('textbox');

      // Add tag
      fireEvent.change(input, { target: { value: 'test-tag' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      // Remove tag
      const removeButton = screen.getByLabelText('Remove test-tag');
      fireEvent.click(removeButton);

      expect(setTagsSpy).toHaveBeenCalledTimes(2);
      expect(setTagsSpy).toHaveBeenNthCalledWith(2, []);
    });

    test('calls  with correct tag order', () => {
      const setTagsSpy = vi.fn();
      const TestWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);

        const wrappedSetTags = (newTags: string[]) => {
          setTagsSpy(newTags); // Spy on the call
          setTags(newTags); // Actually update state
        };

        return <TagInput tags={tags} setTags={wrappedSetTags} />;
      };
      render(<TestWrapper />);

      const input = screen.getByRole('textbox');

      // Add multiple tags
      fireEvent.change(input, { target: { value: 'tag1' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
      fireEvent.change(input, { target: { value: 'tag2' } });
      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      expect(setTagsSpy).toHaveBeenLastCalledWith(['tag1', 'tag2']);
    });
  });

  describe('Input value management', () => {
    test('clears input value after adding tag', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: 'test-tag' } });

      expect(input).toHaveValue('test-tag');

      fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });

      expect(input).toHaveValue('');
    });

    test('updates input value on change', () => {
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      fireEvent.change(input, { target: { value: 'typing...' } });

      expect(input).toHaveValue('typing...');
    });
  });

  describe('Edge cases', () => {
    test('handles multiple rapid tag additions', async () => {
      const user = userEvent.setup();
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');

      // Add multiple tags quickly
      for (const tagName of ['tag1', 'tag2', 'tag3']) {
        await user.type(input, tagName);
        await user.keyboard('{Enter}');
      }

      expect(screen.getByText('tag1')).toBeInTheDocument();
      expect(screen.getByText('tag2')).toBeInTheDocument();
      expect(screen.getByText('tag3')).toBeInTheDocument();
    });

    test('handles removing non-existent tag index gracefully', async () => {
      const user = userEvent.setup();
      const TagWrapper = () => {
        const [tags, setTags] = useState<string[]>([]);
        return <TagInput tags={tags} setTags={setTags} />;
      };
      render(<TagWrapper />);

      const input = screen.getByRole('textbox');
      await user.type(input, 'tag1');
      await user.keyboard('{Enter}');

      // This shouldn't break the component
      expect(async () => {
        // Simulate clicking remove on a tag that doesn't exist
        const removeButton = screen.getByLabelText('Remove tag1');
        await user.click(removeButton);
        // The button won't exist anymore, but the test should still pass
      }).not.toThrow();
    });
  });
});
