/**
 * Unit tests for GraphContext
 */

import { renderHook, act } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { GraphProvider, useGraph } from '../../src/contexts/GraphContext';
import type { GraphData, GraphNode, GraphLink } from '../../src/types/graph';

// ============================================================================
// Test Utilities
// ============================================================================

function createWrapper() {
  return function Wrapper({ children }: { children: React.ReactNode }) {
    return <GraphProvider>{children}</GraphProvider>;
  };
}

const sampleGraphData: GraphData = {
  nodes: [
    { id: 'node1', label: 'Node 1', type: 'Resource', uri: 'http://example.org/node1' },
    { id: 'node2', label: 'Node 2', type: 'Class', uri: 'http://example.org/node2' },
  ],
  links: [
    {
      source: 'node1',
      target: 'node2',
      predicate: 'relatesTo',
      isBidirectional: false,
    },
  ],
};

const sampleNode: GraphNode = sampleGraphData.nodes[0];
const sampleLink: GraphLink = sampleGraphData.links[0];

// ============================================================================
// Tests
// ============================================================================

describe('GraphContext', () => {
  describe('Provider and Hook', () => {
    it('should throw error when useGraph is used outside provider', () => {
      expect(() => {
        renderHook(() => useGraph());
      }).toThrow('useGraph must be used within GraphProvider');
    });
    
    it('should provide initial state', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      expect(result.current.state.graphData).toBe(null);
      expect(result.current.state.isLoading).toBe(false);
      expect(result.current.state.error).toBe(null);
      expect(result.current.state.selectedNode).toBe(null);
      expect(result.current.state.selectedLink).toBe(null);
      expect(result.current.state.filters).toEqual({
        nodeTypes: [],
        predicates: [],
        searchQuery: '',
      });
      expect(result.current.state.config).toEqual({
        showLabels: true,
        nodeSize: 8,
        linkWidth: 1.5,
        chargeStrength: -300,
        linkDistance: 100,
      });
    });
  });
  
  describe('Graph Data Management', () => {
    it('should set graph data', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      act(() => {
        result.current.actions.setGraphData(sampleGraphData);
      });
      
      expect(result.current.state.graphData).toEqual(sampleGraphData);
      expect(result.current.state.isLoading).toBe(false);
      expect(result.current.state.error).toBe(null);
    });
    
    it('should set loading state', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      act(() => {
        result.current.actions.setLoading(true);
      });
      
      expect(result.current.state.isLoading).toBe(true);
      
      act(() => {
        result.current.actions.setLoading(false);
      });
      
      expect(result.current.state.isLoading).toBe(false);
    });
    
    it('should set error', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      act(() => {
        result.current.actions.setError('Test error');
      });
      
      expect(result.current.state.error).toBe('Test error');
      expect(result.current.state.isLoading).toBe(false);
      
      act(() => {
        result.current.actions.setError(null);
      });
      
      expect(result.current.state.error).toBe(null);
    });
  });
  
  describe('Selection Management', () => {
    it('should select node', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      act(() => {
        result.current.actions.selectNode(sampleNode);
      });
      
      expect(result.current.state.selectedNode).toEqual(sampleNode);
      expect(result.current.state.selectedLink).toBe(null); // Link should be cleared
    });
    
    it('should select link', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      // First select a node
      act(() => {
        result.current.actions.selectNode(sampleNode);
      });
      
      expect(result.current.state.selectedNode).toEqual(sampleNode);
      
      // Then select a link (should clear node)
      act(() => {
        result.current.actions.selectLink(sampleLink);
      });
      
      expect(result.current.state.selectedLink).toEqual(sampleLink);
      expect(result.current.state.selectedNode).toBe(null); // Node should be cleared
    });
    
    it('should clear selection', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      // Select node and link
      act(() => {
        result.current.actions.selectNode(sampleNode);
        result.current.actions.selectLink(sampleLink);
      });
      
      // Clear selection
      act(() => {
        result.current.actions.clearSelection();
      });
      
      expect(result.current.state.selectedNode).toBe(null);
      expect(result.current.state.selectedLink).toBe(null);
    });
  });
  
  describe('Filter Management', () => {
    it('should update filters partially', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      act(() => {
        result.current.actions.updateFilters({ nodeTypes: ['Resource', 'Class'] });
      });
      
      expect(result.current.state.filters.nodeTypes).toEqual(['Resource', 'Class']);
      expect(result.current.state.filters.predicates).toEqual([]); // Should remain unchanged
      expect(result.current.state.filters.searchQuery).toBe(''); // Should remain unchanged
    });
    
    it('should update multiple filters at once', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      act(() => {
        result.current.actions.updateFilters({
          nodeTypes: ['Resource'],
          predicates: ['relatesTo'],
          searchQuery: 'test',
        });
      });
      
      expect(result.current.state.filters).toEqual({
        nodeTypes: ['Resource'],
        predicates: ['relatesTo'],
        searchQuery: 'test',
      });
    });
    
    it('should reset filters', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      // Set some filters
      act(() => {
        result.current.actions.updateFilters({
          nodeTypes: ['Resource'],
          predicates: ['relatesTo'],
          searchQuery: 'test',
        });
      });
      
      expect(result.current.state.filters).not.toEqual({
        nodeTypes: [],
        predicates: [],
        searchQuery: '',
      });
      
      // Reset filters
      act(() => {
        result.current.actions.resetFilters();
      });
      
      expect(result.current.state.filters).toEqual({
        nodeTypes: [],
        predicates: [],
        searchQuery: '',
      });
    });
  });
  
  describe('Visualization Config Management', () => {
    it('should update config partially', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      act(() => {
        result.current.actions.updateConfig({ nodeSize: 12 });
      });
      
      expect(result.current.state.config.nodeSize).toBe(12);
      expect(result.current.state.config.showLabels).toBe(true); // Should remain unchanged
    });
    
    it('should update multiple config options at once', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      act(() => {
        result.current.actions.updateConfig({
          showLabels: false,
          nodeSize: 10,
          linkWidth: 2,
        });
      });
      
      expect(result.current.state.config.showLabels).toBe(false);
      expect(result.current.state.config.nodeSize).toBe(10);
      expect(result.current.state.config.linkWidth).toBe(2);
      expect(result.current.state.config.chargeStrength).toBe(-300); // Should remain unchanged
    });
  });
  
  describe('Clear All', () => {
    it('should reset to initial state', () => {
      const { result } = renderHook(() => useGraph(), {
        wrapper: createWrapper(),
      });
      
      // Set various state
      act(() => {
        result.current.actions.setGraphData(sampleGraphData);
        result.current.actions.selectNode(sampleNode);
        result.current.actions.updateFilters({ searchQuery: 'test' });
        result.current.actions.updateConfig({ nodeSize: 10 });
      });
      
      // Verify state was set
      expect(result.current.state.graphData).not.toBe(null);
      expect(result.current.state.selectedNode).not.toBe(null);
      expect(result.current.state.filters.searchQuery).toBe('test');
      expect(result.current.state.config.nodeSize).toBe(10);
      
      // Clear all
      act(() => {
        result.current.actions.clearAll();
      });
      
      // Verify reset to initial state
      expect(result.current.state.graphData).toBe(null);
      expect(result.current.state.isLoading).toBe(false);
      expect(result.current.state.error).toBe(null);
      expect(result.current.state.selectedNode).toBe(null);
      expect(result.current.state.selectedLink).toBe(null);
      expect(result.current.state.filters).toEqual({
        nodeTypes: [],
        predicates: [],
        searchQuery: '',
      });
      expect(result.current.state.config).toEqual({
        showLabels: true,
        nodeSize: 8,
        linkWidth: 1.5,
        chargeStrength: -300,
        linkDistance: 100,
      });
    });
  });
});
