import { describe, it, expect, beforeEach } 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 {
	exportGraph,
	importGraph,
	validateSnapshot,
	SNAPSHOT_VERSION,
	type CanvasSnapshot,
} from '../core/canvas-serializer';
import { zoomAtom, panAtom } from '../core/viewport-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 addNode(graph: Graph, id: string, x: number, y: number) {
	const attrs: GraphNodeAttributes = {
		x, y, width: 200, height: 100, size: 15, color: '#888',
		zIndex: 0, label: `Node ${id}`, dbData: makeDbNode(id, x, y),
	};
	graph.addNode(id, attrs);
}

function addEdge(graph: Graph, key: string, source: string, target: string) {
	const dbData: DBGraphEdge = {
		id: key, graph_id: 'test-graph', source_node_id: source, target_node_id: target,
		edge_type: 'default', filter_condition: null, ui_properties: null, data: null,
		created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z',
	};
	graph.addEdgeWithKey(key, source, target, { dbData });
}

describe('canvas-serializer roundtrip', () => {
	let store: ReturnType<typeof createStore>;

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

	it('empty graph round-trips', () => {
		const snapshot = exportGraph(store);
		expect(snapshot.nodes).toHaveLength(0);
		expect(snapshot.edges).toHaveLength(0);

		const store2 = createStore();
		store2.set(graphAtom, new Graph<GraphNodeAttributes, GraphEdgeAttributes>(graphOptions));
		importGraph(store2, snapshot);

		const graph2 = store2.get(graphAtom);
		expect(graph2.order).toBe(0);
		expect(graph2.size).toBe(0);
	});

	it('single node round-trips with position', () => {
		const graph = store.get(graphAtom);
		addNode(graph, 'n1', 50, 75);
		store.set(graphAtom, graph);

		const snapshot = exportGraph(store);
		expect(snapshot.nodes).toHaveLength(1);
		expect(snapshot.nodes[0].position).toEqual({ x: 50, y: 75 });

		const store2 = createStore();
		store2.set(graphAtom, new Graph<GraphNodeAttributes, GraphEdgeAttributes>(graphOptions));
		importGraph(store2, snapshot);

		const g2 = store2.get(graphAtom);
		expect(g2.order).toBe(1);
		const attrs = g2.getNodeAttributes('n1') as GraphNodeAttributes;
		expect(attrs.x).toBe(50);
		expect(attrs.y).toBe(75);
	});

	it('edges round-trip', () => {
		const graph = store.get(graphAtom);
		addNode(graph, 'n1', 0, 0);
		addNode(graph, 'n2', 200, 0);
		addEdge(graph, 'e1', 'n1', 'n2');
		store.set(graphAtom, graph);

		const snapshot = exportGraph(store);
		expect(snapshot.edges).toHaveLength(1);
		expect(snapshot.edges[0].sourceId).toBe('n1');
		expect(snapshot.edges[0].targetId).toBe('n2');

		const store2 = createStore();
		store2.set(graphAtom, new Graph<GraphNodeAttributes, GraphEdgeAttributes>(graphOptions));
		importGraph(store2, snapshot);

		const g2 = store2.get(graphAtom);
		expect(g2.size).toBe(1);
	});

	it('viewport round-trips', () => {
		store.set(zoomAtom, 2.5);
		store.set(panAtom, { x: 100, y: -50 });

		const snapshot = exportGraph(store);
		expect(snapshot.viewport.zoom).toBe(2.5);
		expect(snapshot.viewport.pan).toEqual({ x: 100, y: -50 });

		const store2 = createStore();
		store2.set(graphAtom, new Graph<GraphNodeAttributes, GraphEdgeAttributes>(graphOptions));
		importGraph(store2, snapshot);

		expect(store2.get(zoomAtom)).toBe(2.5);
		expect(store2.get(panAtom)).toEqual({ x: 100, y: -50 });
	});

	it('node labels preserved', () => {
		const graph = store.get(graphAtom);
		addNode(graph, 'n1', 0, 0);
		store.set(graphAtom, graph);

		const snapshot = exportGraph(store);
		expect(snapshot.nodes[0].label).toBe('Node n1');
	});

	it('SNAPSHOT_VERSION is 1', () => {
		expect(SNAPSHOT_VERSION).toBe(1);
	});

	it('validateSnapshot passes for valid snapshot', () => {
		const snapshot = exportGraph(store);
		const result = validateSnapshot(snapshot);
		expect(result.valid).toBe(true);
		expect(result.errors).toHaveLength(0);
	});

	it('validateSnapshot rejects non-object', () => {
		const result = validateSnapshot(null);
		expect(result.valid).toBe(false);
	});

	it('import with offset shifts positions', () => {
		const graph = store.get(graphAtom);
		addNode(graph, 'n1', 100, 200);
		store.set(graphAtom, graph);

		const snapshot = exportGraph(store);

		const store2 = createStore();
		store2.set(graphAtom, new Graph<GraphNodeAttributes, GraphEdgeAttributes>(graphOptions));
		importGraph(store2, snapshot, { offsetPosition: { x: 50, y: -25 } });

		const g2 = store2.get(graphAtom);
		const attrs = g2.getNodeAttributes('n1') as GraphNodeAttributes;
		expect(attrs.x).toBe(150);
		expect(attrs.y).toBe(175);
	});
});
