import React from 'react';
import { render, screen, act, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

const mockDispatchCommand = jest.fn();
let capturedUpdateListener:
  | ((arg: { editorState: { read: (fn: () => void) => void } }) => void)
  | null = null;
const mockGetElementByKey = jest.fn();

const mockEditor = {
  dispatchCommand: mockDispatchCommand,
  registerUpdateListener: jest.fn((cb) => {
    capturedUpdateListener = cb;
    return () => {};
  }),
  getElementByKey: mockGetElementByKey,
};

const mockGetSelection = jest.fn();
const mockIsRangeSelection = jest.fn();

jest.mock('lexical', () => ({
  $getSelection: (...args: unknown[]) => mockGetSelection(...args),
  $isRangeSelection: (...args: unknown[]) => mockIsRangeSelection(...args),
}));

jest.mock('@lexical/link', () => ({
  TOGGLE_LINK_COMMAND: 'TOGGLE_LINK_COMMAND',
}));

jest.mock('@lexical/react/LexicalComposerContext', () => ({
  useLexicalComposerContext: () => [mockEditor],
}));

const mockFindLinkNode = jest.fn();
const mockIsValidUrl = jest.fn(() => true);

jest.mock('../link-utils', () => ({
  normalizeUrl: (url: string) => url.trim(),
  isValidUrl: (...args: unknown[]) => mockIsValidUrl(...args),
  INVALID_URL_MESSAGE: 'Invalid URL. Please enter a valid http, https, mailto, or tel URL.',
  $findLinkNode: (...args: unknown[]) => mockFindLinkNode(...args),
}));

import { FloatingLinkEditorPlugin } from '../FloatingLinkEditor';

// --- Helpers ---

function makeAnchorRef() {
  const div = document.createElement('div');
  div.getBoundingClientRect = () =>
    ({ top: 80, bottom: 400, left: 20, right: 600, width: 580, height: 320 }) as DOMRect;
  return { current: div } as React.RefObject<HTMLDivElement>;
}

function simulateLinkDetected(url: string, target: string | null = '_blank') {
  const mockLinkNode = {
    getURL: () => url,
    getTarget: () => target,
    getKey: () => 'link-key-1',
  };
  const mockNode = { getParent: () => mockLinkNode };

  mockGetSelection.mockReturnValue({ anchor: { getNode: () => mockNode } });
  mockIsRangeSelection.mockReturnValue(true);
  mockFindLinkNode.mockReturnValue(mockLinkNode);
  mockGetElementByKey.mockReturnValue({
    getBoundingClientRect: () =>
      ({ top: 100, bottom: 120, left: 50, right: 200, width: 150, height: 20 }) as DOMRect,
  });

  act(() => {
    capturedUpdateListener?.({ editorState: { read: (fn: () => void) => fn() } });
  });
}

function simulateNoLink() {
  mockGetSelection.mockReturnValue({ anchor: { getNode: () => ({ getParent: () => null }) } });
  mockIsRangeSelection.mockReturnValue(true);
  mockFindLinkNode.mockReturnValue(null);

  act(() => {
    capturedUpdateListener?.({ editorState: { read: (fn: () => void) => fn() } });
  });
}

function simulateNonRangeSelection() {
  mockGetSelection.mockReturnValue(null);
  mockIsRangeSelection.mockReturnValue(false);

  act(() => {
    capturedUpdateListener?.({ editorState: { read: (fn: () => void) => fn() } });
  });
}

// --- Tests ---

describe('FloatingLinkEditorPlugin', () => {
  let anchorRef: React.RefObject<HTMLDivElement>;

  beforeEach(() => {
    jest.clearAllMocks();
    capturedUpdateListener = null;
    mockIsValidUrl.mockReturnValue(true);
    anchorRef = makeAnchorRef();
  });

  it('renders nothing when no link is detected', () => {
    const { container } = render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
    expect(container.innerHTML).toBe('');
  });

  describe('Read-only mode', () => {
    it('shows URL, target toggle, Edit, and Remove when cursor is in a link', () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com', '_blank');

      expect(screen.getByText('https://example.com')).toBeInTheDocument();
      expect(screen.getByText('New tab')).toBeInTheDocument();
      expect(screen.getByText('Edit')).toBeInTheDocument();
      expect(screen.getByText('Remove')).toBeInTheDocument();
    });

    it('shows "Same tab" when target is null', () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com', null);

      expect(screen.getByText('Same tab')).toBeInTheDocument();
    });

    it('toggles target from _blank to null', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com', '_blank');

      await userEvent.click(screen.getByText('New tab'));

      expect(mockDispatchCommand).toHaveBeenCalledWith('TOGGLE_LINK_COMMAND', {
        url: 'https://example.com',
        target: null,
        rel: null,
      });
    });

    it('toggles target from null to _blank', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com', null);

      await userEvent.click(screen.getByText('Same tab'));

      expect(mockDispatchCommand).toHaveBeenCalledWith('TOGGLE_LINK_COMMAND', {
        url: 'https://example.com',
        target: '_blank',
        rel: 'noopener noreferrer',
      });
    });

    it('removes the link on Remove click', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com');

      await userEvent.click(screen.getByText('Remove'));

      expect(mockDispatchCommand).toHaveBeenCalledWith('TOGGLE_LINK_COMMAND', null);
    });

    it('hides popover when cursor moves away from link', () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com');
      expect(screen.getByText('https://example.com')).toBeInTheDocument();

      simulateNoLink();
      expect(screen.queryByText('https://example.com')).not.toBeInTheDocument();
    });
  });

  describe('Edit mode', () => {
    it('shows edit form pre-filled with current URL and target', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com', '_blank');

      await userEvent.click(screen.getByText('Edit'));

      expect(screen.getByPlaceholderText('Enter URL')).toHaveValue('https://example.com');
      expect(screen.getByRole('checkbox', { name: /open in new tab/i })).toBeChecked();
      expect(screen.getByText('Apply')).toBeInTheDocument();
      expect(screen.getByText('Cancel')).toBeInTheDocument();
    });

    it('pre-fills checkbox unchecked when target is null', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com', null);

      await userEvent.click(screen.getByText('Edit'));

      expect(screen.getByRole('checkbox', { name: /open in new tab/i })).not.toBeChecked();
    });

    it('dispatches updated URL on Apply', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com', '_blank');

      await userEvent.click(screen.getByText('Edit'));

      const input = screen.getByPlaceholderText('Enter URL');
      await userEvent.clear(input);
      await userEvent.type(input, 'https://new-url.com');
      await userEvent.click(screen.getByText('Apply'));

      expect(mockDispatchCommand).toHaveBeenCalledWith('TOGGLE_LINK_COMMAND', {
        url: 'https://new-url.com',
        target: '_blank',
        rel: 'noopener noreferrer',
      });
    });

    it('removes link when Apply is clicked with empty URL', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com');

      await userEvent.click(screen.getByText('Edit'));
      await userEvent.clear(screen.getByPlaceholderText('Enter URL'));
      await userEvent.click(screen.getByText('Apply'));

      expect(mockDispatchCommand).toHaveBeenCalledWith('TOGGLE_LINK_COMMAND', null);
    });

    it('shows validation error for invalid URL', async () => {
      mockIsValidUrl.mockReturnValue(false);

      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com');

      await userEvent.click(screen.getByText('Edit'));

      const input = screen.getByPlaceholderText('Enter URL');
      await userEvent.clear(input);
      await userEvent.type(input, 'javascript:alert(1)');
      await userEvent.click(screen.getByText('Apply'));

      expect(screen.getByText(/invalid url/i)).toBeInTheDocument();
      expect(mockDispatchCommand).not.toHaveBeenCalled();
    });

    it('clears validation error when URL input changes', async () => {
      mockIsValidUrl.mockReturnValue(false);

      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com');

      await userEvent.click(screen.getByText('Edit'));
      const input = screen.getByPlaceholderText('Enter URL');
      await userEvent.clear(input);
      await userEvent.type(input, 'bad');
      await userEvent.click(screen.getByText('Apply'));

      expect(screen.getByText(/invalid url/i)).toBeInTheDocument();

      mockIsValidUrl.mockReturnValue(true);
      await userEvent.type(input, 'x');
      expect(screen.queryByText(/invalid url/i)).not.toBeInTheDocument();
    });

    it('returns to read-only mode on Cancel', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com');

      await userEvent.click(screen.getByText('Edit'));
      expect(screen.getByPlaceholderText('Enter URL')).toBeInTheDocument();

      await userEvent.click(screen.getByText('Cancel'));

      expect(screen.queryByPlaceholderText('Enter URL')).not.toBeInTheDocument();
      expect(screen.getByText('https://example.com')).toBeInTheDocument();
    });

    it('applies on Enter key', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com', '_blank');

      await userEvent.click(screen.getByText('Edit'));

      const input = screen.getByPlaceholderText('Enter URL');
      await userEvent.clear(input);
      await userEvent.type(input, 'https://new-url.com');

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

      expect(mockDispatchCommand).toHaveBeenCalledWith('TOGGLE_LINK_COMMAND', {
        url: 'https://new-url.com',
        target: '_blank',
        rel: 'noopener noreferrer',
      });
    });

    it('cancels on Escape key', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com');

      await userEvent.click(screen.getByText('Edit'));
      const input = screen.getByPlaceholderText('Enter URL');

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

      expect(screen.queryByPlaceholderText('Enter URL')).not.toBeInTheDocument();
      expect(screen.getByText('https://example.com')).toBeInTheDocument();
    });
  });

  describe('Focus stability (isEditing ref)', () => {
    it('preserves edit form when focus leaves editor during editing', async () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com');

      await userEvent.click(screen.getByText('Edit'));
      expect(screen.getByPlaceholderText('Enter URL')).toBeInTheDocument();

      // Focus leaving the Lexical editor produces a non-range selection
      simulateNonRangeSelection();

      // Edit form should still be visible
      expect(screen.getByPlaceholderText('Enter URL')).toBeInTheDocument();
    });

    it('dismisses popover when focus leaves editor in read-only mode', () => {
      render(<FloatingLinkEditorPlugin anchorRef={anchorRef} />);
      simulateLinkDetected('https://example.com');
      expect(screen.getByText('https://example.com')).toBeInTheDocument();

      simulateNonRangeSelection();

      expect(screen.queryByText('https://example.com')).not.toBeInTheDocument();
    });
  });
});
