import { describe, it, expect, vi } from 'vitest';
import { render, fireEvent } from '@testing-library/react';
import { createStore, Provider } from 'jotai';
import React from 'react';
import Graph from 'graphology';
import { Node } from '../../components/Node';
import type { GraphNodeAttributes, GraphEdgeAttributes, UINodeState, DBGraphNode } from '../../core/types';
import { graphAtom, graphOptions, currentGraphIdAtom } from '../../core/graph-store';
import { addNodeToLocalGraphAtom } from '../../core/graph-mutations';
import { CanvasStyleProvider } from '../../providers/CanvasStyleProvider';
import { GestureProvider } from '../../gestures/GestureProvider';

function makeDbNode(id: string, x = 0, y = 0): DBGraphNode {
	return {
		id,
		graph_id: 'test-graph',
		label: `Node ${id}`,
		node_type: 'default',
		configuration: null,
		ui_properties: { x, y, width: 200, height: 100, size: 15, zIndex: 0 },
		data: null,
		created_at: '2024-01-01T00:00:00Z',
		updated_at: '2024-01-01T00:00:00Z',
	};
}

function makeNodeState(id: string, label = 'Test Node'): UINodeState {
	return {
		id,
		x: 0,
		y: 0,
		size: 15,
		width: 200,
		height: 100,
		color: '#ccc',
		label,
		zIndex: 0,
		dbData: {
			id,
			graph_id: 'test-graph',
			label,
			node_type: 'default',
			configuration: null,
			ui_properties: null,
			data: null,
			created_at: '2024-01-01',
			updated_at: '2024-01-01',
		},
		position: { x: 0, y: 0 },
		isDragging: false,
	};
}

function setupStore() {
	const store = createStore();
	store.set(graphAtom, new Graph<GraphNodeAttributes, GraphEdgeAttributes>(graphOptions));
	store.set(currentGraphIdAtom, 'test-graph');
	return store;
}

function renderWithStore(store: ReturnType<typeof createStore>, ui: React.ReactElement) {
	return render(
		<Provider store={store}>
			<CanvasStyleProvider>
				<GestureProvider>{ui}</GestureProvider>
			</CanvasStyleProvider>
		</Provider>,
	);
}

describe('Node', () => {
	it('renders with data-draggable-node attribute', () => {
		const store = setupStore();
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));

		const { container } = renderWithStore(
			store,
			<Node
				nodeData={makeNodeState('n1')}
				renderContent={({ node }) => <span>{node.label}</span>}
			/>,
		);

		expect(container.querySelector('[data-draggable-node="true"]')).toBeInTheDocument();
	});

	it('renders custom content via renderContent prop', () => {
		const store = setupStore();
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));

		const { getByText } = renderWithStore(
			store,
			<Node
				nodeData={makeNodeState('n1', 'My Node')}
				renderContent={({ node }) => <span>{node.label}</span>}
			/>,
		);

		expect(getByText('My Node')).toBeInTheDocument();
	});

	it('sets data-node-id attribute', () => {
		const store = setupStore();
		store.set(addNodeToLocalGraphAtom, makeDbNode('node-123'));

		const { container } = renderWithStore(
			store,
			<Node
				nodeData={makeNodeState('node-123')}
				renderContent={() => <span>content</span>}
			/>,
		);

		expect(container.querySelector('[data-node-id="node-123"]')).toBeInTheDocument();
	});

	it('has grab cursor by default', () => {
		const store = setupStore();
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));

		const { container } = renderWithStore(
			store,
			<Node
				nodeData={makeNodeState('n1')}
				renderContent={() => <span>content</span>}
			/>,
		);

		const node = container.querySelector('[data-draggable-node="true"]') as HTMLElement;
		expect(node.style.cursor).toBe('grab');
	});

	it('passes isSelected=false and isDragging=false to renderContent by default', () => {
		const store = setupStore();
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));
		const renderContent = vi.fn(() => <span>content</span>);

		renderWithStore(
			store,
			<Node
				nodeData={makeNodeState('n1')}
				renderContent={renderContent}
			/>,
		);

		expect(renderContent).toHaveBeenCalledWith(
			expect.objectContaining({ isSelected: false, isDragging: false }),
		);
	});

	it('prevents default on context menu (gesture v2 handles right-click)', () => {
		const store = setupStore();
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));

		const { container } = renderWithStore(
			store,
			<Node
				nodeData={makeNodeState('n1')}
				renderContent={() => <span>content</span>}
			/>,
		);

		const node = container.querySelector('[data-draggable-node="true"]') as HTMLElement;
		const event = new MouseEvent('contextmenu', { bubbles: true, cancelable: true });
		const prevented = !node.dispatchEvent(event);
		// The gesture pipeline handles context menu — it should be prevented
		expect(prevented).toBe(true);
	});

	it('applies node dimensions from state', () => {
		const store = setupStore();
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));

		const { container } = renderWithStore(
			store,
			<Node
				nodeData={makeNodeState('n1')}
				renderContent={() => <span>content</span>}
			/>,
		);

		const node = container.querySelector('[data-draggable-node="true"]') as HTMLElement;
		expect(node.style.width).toBe('200px');
		expect(node.style.height).toBe('100px');
	});

	it('wraps content with wrapper component', () => {
		const store = setupStore();
		store.set(addNodeToLocalGraphAtom, makeDbNode('n1'));

		const Wrapper = ({ children, nodeData }: { children: React.ReactNode; nodeData: UINodeState }) => (
			<div data-testid="wrapper" data-label={nodeData.label}>
				{children}
			</div>
		);

		const { getByTestId } = renderWithStore(
			store,
			<Node
				nodeData={makeNodeState('n1', 'Wrapped')}
				renderContent={() => <span>inner</span>}
				wrapper={Wrapper}
			/>,
		);

		const wrapper = getByTestId('wrapper');
		expect(wrapper).toBeInTheDocument();
		expect(wrapper.getAttribute('data-label')).toBe('Wrapped');
	});
});
