/**
 * useAvailableContext Hook Tests
 *
 * Tests context variable computation including:
 * - Trigger output field processing
 * - Action output field processing
 * - Graph traversal for preceding nodes
 * - Node ordering by flow position
 */
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useAvailableContext } from './useAvailableContext';
import { Node, Edge } from '@xyflow/react';
import { WorkflowNodeData } from '../types/reactflow';
import { Trigger } from '../types/trigger';
import { Action } from '../types/action';

describe('useAvailableContext', () => {
  // ==========================================================================
  // Test Data
  // ==========================================================================

  const mockTrigger: Trigger = {
    type: 'wordpress/user_registration',
    label: 'User Registration',
    integration: 'wordpress',
    description: 'Triggered when a user registers',
    available_fields: [
      { name: 'user_id', type: 'integer', description: 'User ID', example: '123' },
      {
        name: 'user_email',
        type: 'string',
        description: 'User email address',
        example: 'user@example.com',
      },
      { name: 'user_login', type: 'string', description: 'Username', example: 'johndoe' },
    ],
    is_available: true,
    availability_reason: '',
  };

  const mockSendEmailAction: Action = {
    type: 'wordpress/send_email',
    label: 'Send Email',
    integration: 'wordpress',
    description: 'Sends an email',
    schema: {},
    output_fields: [
      { name: 'email_id', type: 'string', description: 'Sent email ID', example: 'email-123' },
      {
        name: 'sent_at',
        type: 'string',
        description: 'Timestamp when sent',
        example: '2024-01-01T00:00:00Z',
      },
    ],
    is_available: true,
    availability_reason: '',
    is_connected: true,
    requires_connection: false,
  };

  const mockCreatePostAction: Action = {
    type: 'wordpress/create_post',
    label: 'Create Post',
    integration: 'wordpress',
    description: 'Creates a WordPress post',
    schema: {},
    output_fields: [
      { name: 'post_id', type: 'integer', description: 'Created post ID', example: '456' },
      {
        name: 'post_url',
        type: 'string',
        description: 'Post URL',
        example: 'https://example.com/post',
      },
    ],
    is_available: true,
    availability_reason: '',
    is_connected: true,
    requires_connection: false,
  };

  const createTriggerNode = (
    overrides: Partial<WorkflowNodeData> = {},
  ): Node<WorkflowNodeData> => ({
    id: 'node-trigger',
    type: 'trigger',
    position: { x: 250, y: 80 },
    data: {
      name: 'User Registration',
      icon: 'Zap',
      actionType: 'wordpress/user_registration',
      status: 'valid',
      config: {},
      ...overrides,
    },
  });

  const createActionNode = (
    id: string,
    actionType: string,
    overrides: Partial<WorkflowNodeData> = {},
  ): Node<WorkflowNodeData> => ({
    id,
    type: 'action',
    position: { x: 250, y: 240 },
    data: {
      name: 'Action',
      icon: 'Zap',
      actionType,
      status: 'valid',
      config: {},
      ...overrides,
    },
  });

  // ==========================================================================
  // Basic Functionality Tests
  // ==========================================================================

  describe('basic functionality', () => {
    it('returns empty groups when no preceding nodes', () => {
      const nodes = [createTriggerNode()];
      const edges: Edge[] = [];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-trigger',
          triggers: [mockTrigger],
          actions: [],
        }),
      );

      expect(result.current.groups).toHaveLength(0);
    });

    it('returns empty groups for first action after trigger', () => {
      const nodes = [createTriggerNode(), createActionNode('node-action-1', 'wordpress/send_email')];
      const edges: Edge[] = [{ id: 'e1', source: 'node-trigger', target: 'node-action-1' }];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-1',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction],
        }),
      );

      // Should have trigger context
      expect(result.current.groups).toHaveLength(1);
      expect(result.current.groups[0].namespace).toBe('Trigger');
    });
  });

  // ==========================================================================
  // Trigger Processing Tests
  // ==========================================================================

  describe('trigger processing', () => {
    it('includes trigger output fields when trigger precedes current node', () => {
      const nodes = [createTriggerNode(), createActionNode('node-action-1', 'wordpress/send_email')];
      const edges: Edge[] = [{ id: 'e1', source: 'node-trigger', target: 'node-action-1' }];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-1',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction],
        }),
      );

      const triggerGroup = result.current.groups.find((g) => g.namespace === 'Trigger');
      expect(triggerGroup).toBeDefined();
      expect(triggerGroup!.variables).toHaveLength(3);
      expect(triggerGroup!.variables[0].path).toBe('Trigger.user_id');
      expect(triggerGroup!.variables[1].path).toBe('Trigger.user_email');
    });

    it('formats trigger field names as title case labels', () => {
      const nodes = [createTriggerNode(), createActionNode('node-action-1', 'wordpress/send_email')];
      const edges: Edge[] = [{ id: 'e1', source: 'node-trigger', target: 'node-action-1' }];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-1',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction],
        }),
      );

      const triggerGroup = result.current.groups.find((g) => g.namespace === 'Trigger');
      expect(triggerGroup!.variables[0].label).toBe('User Id');
      expect(triggerGroup!.variables[1].label).toBe('User Email');
    });

    it('includes trigger source name from trigger label', () => {
      const nodes = [createTriggerNode(), createActionNode('node-action-1', 'wordpress/send_email')];
      const edges: Edge[] = [{ id: 'e1', source: 'node-trigger', target: 'node-action-1' }];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-1',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction],
        }),
      );

      const triggerGroup = result.current.groups.find((g) => g.namespace === 'Trigger');
      expect(triggerGroup!.sourceName).toBe('User Registration');
      expect(triggerGroup!.sourceType).toBe('trigger');
    });

    it('does not include trigger when not matched', () => {
      const nodes = [
        // Use a name that doesn't convert to a matching type
        createTriggerNode({ actionType: 'unknown_trigger', name: 'Custom Trigger' }),
        createActionNode('node-action-1', 'wordpress/send_email'),
      ];
      const edges: Edge[] = [{ id: 'e1', source: 'node-trigger', target: 'node-action-1' }];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-1',
          triggers: [mockTrigger], // Does not match 'unknown_trigger' or 'custom_trigger'
          actions: [mockSendEmailAction],
        }),
      );

      const triggerGroup = result.current.groups.find((g) => g.namespace === 'Trigger');
      expect(triggerGroup).toBeUndefined();
    });
  });

  // ==========================================================================
  // Action Processing Tests
  // ==========================================================================

  describe('action processing', () => {
    it('includes action output fields when action precedes current node', () => {
      const nodes = [
        createTriggerNode(),
        createActionNode('node-action-1', 'wordpress/send_email', { label: 'send_email' }),
        createActionNode('node-action-2', 'wordpress/create_post', { label: 'create_post' }),
      ];
      const edges: Edge[] = [
        { id: 'e1', source: 'node-trigger', target: 'node-action-1' },
        { id: 'e2', source: 'node-action-1', target: 'node-action-2' },
      ];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-2',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction, mockCreatePostAction],
        }),
      );

      // Should have trigger + send_email action
      expect(result.current.groups).toHaveLength(2);

      const sendEmailGroup = result.current.groups.find((g) => g.namespace === 'send_email');
      expect(sendEmailGroup).toBeDefined();
      expect(sendEmailGroup!.variables).toHaveLength(2);
      expect(sendEmailGroup!.variables[0].path).toBe('send_email.email_id');
    });

    it('uses sanitized label as namespace', () => {
      const nodes = [
        createTriggerNode(),
        createActionNode('node-action-1', 'wordpress/send_email', { label: 'My Email Action' }),
        createActionNode('node-action-2', 'wordpress/create_post'),
      ];
      const edges: Edge[] = [
        { id: 'e1', source: 'node-trigger', target: 'node-action-1' },
        { id: 'e2', source: 'node-action-1', target: 'node-action-2' },
      ];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-2',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction, mockCreatePostAction],
        }),
      );

      // Sanitized namespace from "My Email Action" -> "my_email_action"
      const emailGroup = result.current.groups.find((g) => g.namespace === 'my_email_action');
      expect(emailGroup).toBeDefined();
    });

    it('does not include action output if action has no output_fields', () => {
      const actionWithoutOutput: Action = {
        ...mockSendEmailAction,
        output_fields: [],
      };

      const nodes = [
        createTriggerNode(),
        createActionNode('node-action-1', 'wordpress/send_email', { label: 'send_email' }),
        createActionNode('node-action-2', 'wordpress/create_post'),
      ];
      const edges: Edge[] = [
        { id: 'e1', source: 'node-trigger', target: 'node-action-1' },
        { id: 'e2', source: 'node-action-1', target: 'node-action-2' },
      ];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-2',
          triggers: [mockTrigger],
          actions: [actionWithoutOutput, mockCreatePostAction],
        }),
      );

      // Should only have trigger group
      expect(result.current.groups).toHaveLength(1);
      expect(result.current.groups[0].namespace).toBe('Trigger');
    });
  });

  // ==========================================================================
  // Graph Traversal Tests
  // ==========================================================================

  describe('graph traversal', () => {
    it('finds all preceding nodes through edge traversal', () => {
      const nodes = [
        createTriggerNode(),
        createActionNode('node-action-1', 'wordpress/send_email', { label: 'send_email' }),
        createActionNode('node-action-2', 'wordpress/create_post', { label: 'create_post' }),
        createActionNode('node-action-3', 'wordpress/send_email', { label: 'notify' }),
      ];
      const edges: Edge[] = [
        { id: 'e1', source: 'node-trigger', target: 'node-action-1' },
        { id: 'e2', source: 'node-action-1', target: 'node-action-2' },
        { id: 'e3', source: 'node-action-2', target: 'node-action-3' },
      ];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-3',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction, mockCreatePostAction],
        }),
      );

      // Should have: Trigger, send_email, create_post
      expect(result.current.groups).toHaveLength(3);
    });

    it('only includes nodes that precede current node', () => {
      const nodes = [
        createTriggerNode(),
        createActionNode('node-action-1', 'wordpress/send_email', { label: 'send_email' }),
        createActionNode('node-action-2', 'wordpress/create_post', { label: 'create_post' }),
      ];
      const edges: Edge[] = [
        { id: 'e1', source: 'node-trigger', target: 'node-action-1' },
        { id: 'e2', source: 'node-action-1', target: 'node-action-2' },
      ];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-1',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction, mockCreatePostAction],
        }),
      );

      // Should only have Trigger (node-action-2 comes after)
      expect(result.current.groups).toHaveLength(1);
      expect(result.current.groups[0].namespace).toBe('Trigger');
    });

    it('excludes disconnected nodes', () => {
      const nodes = [
        createTriggerNode(),
        createActionNode('node-action-1', 'wordpress/send_email', { label: 'send_email' }),
        createActionNode('node-disconnected', 'wordpress/create_post', { label: 'disconnected' }),
      ];
      const edges: Edge[] = [
        { id: 'e1', source: 'node-trigger', target: 'node-action-1' },
        // node-disconnected has no edges
      ];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-1',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction, mockCreatePostAction],
        }),
      );

      // Only Trigger precedes node-action-1
      expect(result.current.groups).toHaveLength(1);
    });
  });

  // ==========================================================================
  // Node Ordering Tests
  // ==========================================================================

  describe('node ordering', () => {
    it('sorts action nodes by flow order', () => {
      const nodes = [
        createTriggerNode(),
        createActionNode('node-action-2', 'wordpress/create_post', { label: 'create_post' }),
        createActionNode('node-action-1', 'wordpress/send_email', { label: 'send_email' }),
        createActionNode('node-action-3', 'wordpress/send_email', { label: 'notify' }),
      ];
      const edges: Edge[] = [
        { id: 'e1', source: 'node-trigger', target: 'node-action-1' },
        { id: 'e2', source: 'node-action-1', target: 'node-action-2' },
        { id: 'e3', source: 'node-action-2', target: 'node-action-3' },
      ];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-3',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction, mockCreatePostAction],
        }),
      );

      // Order should be: Trigger, send_email (action-1), create_post (action-2)
      expect(result.current.groups[0].namespace).toBe('Trigger');
      expect(result.current.groups[1].namespace).toBe('send_email');
      expect(result.current.groups[2].namespace).toBe('create_post');
    });
  });

  // ==========================================================================
  // Edge Cases
  // ==========================================================================

  describe('edge cases', () => {
    it('handles empty nodes array', () => {
      const { result } = renderHook(() =>
        useAvailableContext({
          nodes: [],
          edges: [],
          currentNodeId: 'nonexistent',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction],
        }),
      );

      expect(result.current.groups).toHaveLength(0);
    });

    it('handles workflow with no trigger', () => {
      const nodes = [createActionNode('node-action-1', 'wordpress/send_email')];
      const edges: Edge[] = [];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-1',
          triggers: [mockTrigger],
          actions: [mockSendEmailAction],
        }),
      );

      expect(result.current.groups).toHaveLength(0);
    });

    it('handles trigger with no available_fields', () => {
      const triggerNoFields: Trigger = {
        ...mockTrigger,
        available_fields: [],
      };

      const nodes = [createTriggerNode(), createActionNode('node-action-1', 'wordpress/send_email')];
      const edges: Edge[] = [{ id: 'e1', source: 'node-trigger', target: 'node-action-1' }];

      const { result } = renderHook(() =>
        useAvailableContext({
          nodes,
          edges,
          currentNodeId: 'node-action-1',
          triggers: [triggerNoFields],
          actions: [mockSendEmailAction],
        }),
      );

      // No trigger group because no available_fields
      expect(result.current.groups).toHaveLength(0);
    });

    it('memoizes result based on dependencies', () => {
      // Use stable references for props
      const nodes = [createTriggerNode(), createActionNode('node-action-1', 'wordpress/send_email')];
      const edges: Edge[] = [{ id: 'e1', source: 'node-trigger', target: 'node-action-1' }];
      const triggers = [mockTrigger];
      const actions = [mockSendEmailAction];

      const { result, rerender } = renderHook(
        (props) =>
          useAvailableContext({
            nodes: props.nodes,
            edges: props.edges,
            currentNodeId: props.currentNodeId,
            triggers: props.triggers,
            actions: props.actions,
          }),
        {
          initialProps: {
            nodes,
            edges,
            currentNodeId: 'node-action-1',
            triggers,
            actions,
          },
        },
      );

      const firstResult = result.current;

      // Rerender with same props (same object references)
      rerender({
        nodes,
        edges,
        currentNodeId: 'node-action-1',
        triggers,
        actions,
      });

      // Should be the same object (memoized)
      expect(result.current).toBe(firstResult);
    });
  });
});
