import { describe, it, expect } from 'vitest';
import { createStore } from 'jotai';
import Graph from 'graphology';
import type { GraphNodeAttributes, GraphEdgeAttributes, DBGraphNode, DBGraphEdge } from '../core/types';
import { graphAtom, graphOptions, currentGraphIdAtom } from '../core/graph-store';
import { addNodeToLocalGraphAtom, splitNodeAtom } from '../core/graph-mutations';
import { addEdgeToLocalGraphAtom } from '../core/graph-mutations-edges';

// --- Helpers ---

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

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 makeDbEdge(id: string, source: string, target: string): DBGraphEdge {
	return {
		id,
		graph_id: 'test-graph',
		source_node_id: source,
		target_node_id: target,
		edge_type: null,
		filter_condition: null,
		ui_properties: null,
		created_at: '2024-01-01T00:00:00Z',
		updated_at: '2024-01-01T00:00:00Z',
	};
}

function addNode(store: ReturnType<typeof createStore>, id: string, x = 0, y = 0) {
	store.set(addNodeToLocalGraphAtom, makeDbNode(id, x, y));
}

// --- Tests ---

describe('split-node', () => {
	it('creates a clone node at position2', () => {
		const store = freshStore();
		addNode(store, 'A', 100, 100);

		store.set(splitNodeAtom, {
			nodeId: 'A',
			position1: { x: 50, y: 100 },
			position2: { x: 250, y: 100 },
		});

		const graph = store.get(graphAtom);
		// Original should have 2 nodes now (A + clone)
		expect(graph.order).toBe(2);

		// Original moved to position1
		const aAttrs = graph.getNodeAttributes('A') as GraphNodeAttributes;
		expect(aAttrs.x).toBe(50);
		expect(aAttrs.y).toBe(100);

		// Clone exists at position2
		const cloneId = graph.nodes().find((id) => id !== 'A')!;
		expect(cloneId).toBeTruthy();
		const cloneAttrs = graph.getNodeAttributes(cloneId) as GraphNodeAttributes;
		expect(cloneAttrs.x).toBe(250);
		expect(cloneAttrs.y).toBe(100);
	});

	it('duplicates outgoing edges to the clone', () => {
		const store = freshStore();
		addNode(store, 'A', 100, 100);
		addNode(store, 'B', 300, 300);
		store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'A', 'B'));

		store.set(splitNodeAtom, {
			nodeId: 'A',
			position1: { x: 50, y: 100 },
			position2: { x: 250, y: 100 },
		});

		const graph = store.get(graphAtom);
		const cloneId = graph.nodes().find((id) => id !== 'A' && id !== 'B')!;

		// Original edge still exists
		expect(graph.hasEdge('e1')).toBe(true);

		// Clone also has an edge to B
		const cloneEdges = graph.edges(cloneId);
		expect(cloneEdges.length).toBe(1);
		expect(graph.target(cloneEdges[0])).toBe('B');
	});

	it('duplicates incoming edges to the clone', () => {
		const store = freshStore();
		addNode(store, 'A', 100, 100);
		addNode(store, 'B', 300, 300);
		store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'B', 'A'));

		store.set(splitNodeAtom, {
			nodeId: 'A',
			position1: { x: 50, y: 100 },
			position2: { x: 250, y: 100 },
		});

		const graph = store.get(graphAtom);
		const cloneId = graph.nodes().find((id) => id !== 'A' && id !== 'B')!;

		// Clone has an incoming edge from B
		const cloneEdges = graph.edges(cloneId);
		expect(cloneEdges.length).toBe(1);
		expect(graph.source(cloneEdges[0])).toBe('B');
	});

	it('duplicates both incoming and outgoing edges', () => {
		const store = freshStore();
		addNode(store, 'A', 100, 100);
		addNode(store, 'B', 300, 100);
		addNode(store, 'C', 100, 300);
		store.set(addEdgeToLocalGraphAtom, makeDbEdge('e1', 'A', 'B'));
		store.set(addEdgeToLocalGraphAtom, makeDbEdge('e2', 'C', 'A'));

		store.set(splitNodeAtom, {
			nodeId: 'A',
			position1: { x: 50, y: 100 },
			position2: { x: 250, y: 100 },
		});

		const graph = store.get(graphAtom);
		const cloneId = graph.nodes().find((id) => !['A', 'B', 'C'].includes(id))!;

		// Clone should have 2 edges: one to B and one from C
		const cloneEdges = graph.edges(cloneId);
		expect(cloneEdges.length).toBe(2);
	});

	it('no-ops for nonexistent node', () => {
		const store = freshStore();
		addNode(store, 'A', 100, 100);

		store.set(splitNodeAtom, {
			nodeId: 'nonexistent',
			position1: { x: 0, y: 0 },
			position2: { x: 100, y: 0 },
		});

		const graph = store.get(graphAtom);
		expect(graph.order).toBe(1);
	});
});
