/**
 * useWorkflowPersistence Hook Tests
 *
 * Tests workflow load/save operations including:
 * - Loading workflows from API
 * - Parsing workflow config into ReactFlow nodes/edges
 * - Saving workflows (create and update)
 * - Error handling and retry logic
 */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useWorkflowPersistence } from './useWorkflowPersistence';

// Mock workflow service
vi.mock('../services/workflows', () => ({
  workflowService: {
    getWorkflow: vi.fn(),
    updateWorkflow: vi.fn(),
    createWorkflow: vi.fn(),
  },
}));

// Mock toast
const mockToast = vi.hoisted(() => ({
  success: vi.fn(),
  error: vi.fn(),
  info: vi.fn(),
}));

vi.mock('../lib/toast', () => ({
  toast: mockToast,
}));

// Mock workflow store - use vi.hoisted for variables used in vi.mock
const { mockWorkflowStoreState } = vi.hoisted(() => ({
  mockWorkflowStoreState: {
    name: 'Untitled Workflow',
    setName: vi.fn(),
    setActive: vi.fn(),
    setIsSaving: vi.fn(),
    setInitialName: vi.fn(),
    setId: vi.fn(),
    setPublishedVersionId: vi.fn(),
    setLastPublishedAt: vi.fn(),
    setHasUnpublishedChanges: vi.fn(),
  },
}));

vi.mock('../stores', () => ({
  useWorkflowSetters: () => ({
    setName: mockWorkflowStoreState.setName,
    setActive: mockWorkflowStoreState.setActive,
    setIsSaving: mockWorkflowStoreState.setIsSaving,
    setInitialName: mockWorkflowStoreState.setInitialName,
    setId: mockWorkflowStoreState.setId,
    setPublishedVersionId: mockWorkflowStoreState.setPublishedVersionId,
    setLastPublishedAt: mockWorkflowStoreState.setLastPublishedAt,
    setHasUnpublishedChanges: mockWorkflowStoreState.setHasUnpublishedChanges,
  }),
}));

// Mock flow builder helpers
vi.mock('../utils/flowBuilderHelpers', () => ({
  getActionDisplayName: vi.fn((type) => `Action: ${type}`),
  getAppInfo: vi.fn(() => ({ appName: 'WordPress', appService: 'wordpress' })),
  getConfigSummary: vi.fn(() => 'Config summary'),
  getActionIconName: vi.fn(() => 'action-icon'),
  getTriggerIconName: vi.fn(() => 'trigger-icon'),
  getTriggerDisplayName: vi.fn((type) => `Trigger: ${type}`),
  autoLayoutNodes: vi.fn((nodes) => nodes),
}));

// Import mocked modules for test manipulation
import { workflowService } from '../services/workflows';

describe('useWorkflowPersistence', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Reset store state mocks
    Object.values(mockWorkflowStoreState).forEach((fn) => {
      if (typeof fn === 'function') fn.mockReset?.();
    });
    // Reset toast mocks
    Object.values(mockToast).forEach((fn) => fn.mockReset?.());
  });

  // ==========================================================================
  // Initial State Tests
  // ==========================================================================

  describe('initial state', () => {
    it('starts with loading false when no workflowId provided', () => {
      const { result } = renderHook(() => useWorkflowPersistence());
      expect(result.current.loading).toBe(false);
      expect(result.current.loadError).toBeNull();
    });

    it('starts with loading true when workflowId is provided', () => {
      const { result } = renderHook(() => useWorkflowPersistence('123'));
      expect(result.current.loading).toBe(true);
    });
  });

  // ==========================================================================
  // loadWorkflow Tests
  // ==========================================================================

  describe('loadWorkflow', () => {
    const mockWorkflowResponse = {
      id: 1,
      name: 'Test Workflow',
      description: '',
      trigger_type: 'wordpress/user_registration',
      status: 'active' as const,
      created_at: '2024-01-01T00:00:00Z',
      updated_at: '2024-01-01T00:00:00Z',
      published_version_id: null,
      last_published_at: null,
      draft_config: {
        trigger: {
          type: 'wordpress/user_registration',
          config: {},
          integration: 'wordpress',
        },
        actions: {
          'node-action-1': {
            id: 'node-action-1',
            type: 'wordpress/send_email',
            config: { to: '{{Trigger.user_email}}' },
            label: 'send_email',
            integration: 'wordpress',
          },
        },
        layout: {
          nodes: [
            { id: 'node-trigger', position: { x: 250, y: 80 } },
            { id: 'node-action-1', position: { x: 250, y: 240 } },
          ],
          edges: [{ id: 'e1', source: 'node-trigger', target: 'node-action-1' }],
        },
      },
    };

    it('loads workflow successfully', async () => {
      vi.mocked(workflowService.getWorkflow).mockResolvedValue(mockWorkflowResponse);

      const { result } = renderHook(() => useWorkflowPersistence());

      let loadResult: any;
      await act(async () => {
        loadResult = await result.current.loadWorkflow('1');
      });

      expect(loadResult).not.toBeNull();
      expect(loadResult.nodes).toHaveLength(2);
      expect(loadResult.edges).toHaveLength(1);
      expect(result.current.loading).toBe(false);
      expect(result.current.loadError).toBeNull();
    });

    it('updates store with workflow metadata', async () => {
      vi.mocked(workflowService.getWorkflow).mockResolvedValue(mockWorkflowResponse);

      const { result } = renderHook(() => useWorkflowPersistence());

      await act(async () => {
        await result.current.loadWorkflow('1');
      });

      expect(mockWorkflowStoreState.setId).toHaveBeenCalledWith(1);
      expect(mockWorkflowStoreState.setName).toHaveBeenCalledWith('Test Workflow');
      expect(mockWorkflowStoreState.setInitialName).toHaveBeenCalledWith('Test Workflow');
      expect(mockWorkflowStoreState.setActive).toHaveBeenCalledWith(true);
    });

    it('creates trigger node with correct data', async () => {
      vi.mocked(workflowService.getWorkflow).mockResolvedValue(mockWorkflowResponse);

      const { result } = renderHook(() => useWorkflowPersistence());

      let loadResult: any;
      await act(async () => {
        loadResult = await result.current.loadWorkflow('1');
      });

      const triggerNode = loadResult.nodes.find((n: any) => n.type === 'trigger');
      expect(triggerNode).toBeDefined();
      expect(triggerNode.id).toBe('node-trigger');
      expect(triggerNode.data.actionType).toBe('wordpress/user_registration');
      expect(triggerNode.data.status).toBe('valid');
    });

    it('creates action node with correct data', async () => {
      vi.mocked(workflowService.getWorkflow).mockResolvedValue(mockWorkflowResponse);

      const { result } = renderHook(() => useWorkflowPersistence());

      let loadResult: any;
      await act(async () => {
        loadResult = await result.current.loadWorkflow('1');
      });

      const actionNode = loadResult.nodes.find((n: any) => n.type === 'action');
      expect(actionNode).toBeDefined();
      expect(actionNode.id).toBe('node-action-1');
      expect(actionNode.data.actionType).toBe('wordpress/send_email');
      expect(actionNode.data.config).toEqual({ to: '{{Trigger.user_email}}' });
    });

    it('uses layout positions from config', async () => {
      vi.mocked(workflowService.getWorkflow).mockResolvedValue(mockWorkflowResponse);

      const { result } = renderHook(() => useWorkflowPersistence());

      let loadResult: any;
      await act(async () => {
        loadResult = await result.current.loadWorkflow('1');
      });

      const triggerNode = loadResult.nodes.find((n: any) => n.id === 'node-trigger');
      expect(triggerNode.position).toEqual({ x: 250, y: 80 });
    });

    it('creates edges from layout config', async () => {
      vi.mocked(workflowService.getWorkflow).mockResolvedValue(mockWorkflowResponse);

      const { result } = renderHook(() => useWorkflowPersistence());

      let loadResult: any;
      await act(async () => {
        loadResult = await result.current.loadWorkflow('1');
      });

      expect(loadResult.edges).toHaveLength(1);
      expect(loadResult.edges[0]).toMatchObject({
        id: 'e1',
        source: 'node-trigger',
        target: 'node-action-1',
        type: 'smoothstep',
      });
    });

    it('handles workflow with filters', async () => {
      const workflowWithFilters = {
        ...mockWorkflowResponse,
        draft_config: {
          ...mockWorkflowResponse.draft_config,
          trigger: {
            ...mockWorkflowResponse.draft_config.trigger,
            filters: {
              logic: 'AND',
              conditions: [{ field: 'role', operator: 'equals', value: 'subscriber' }],
            },
          },
        },
      };
      vi.mocked(workflowService.getWorkflow).mockResolvedValue(workflowWithFilters);

      const { result } = renderHook(() => useWorkflowPersistence());

      let loadResult: any;
      await act(async () => {
        loadResult = await result.current.loadWorkflow('1');
      });

      const triggerNode = loadResult.nodes.find((n: any) => n.type === 'trigger');
      expect(triggerNode.data.config.filters).toBeDefined();
      expect(triggerNode.data.config.filters.logic).toBe('AND');
    });

    it('sets loadError on API failure', async () => {
      vi.mocked(workflowService.getWorkflow).mockRejectedValue(new Error('Network error'));

      const { result } = renderHook(() => useWorkflowPersistence());

      await act(async () => {
        await result.current.loadWorkflow('1');
      });

      expect(result.current.loading).toBe(false);
      expect(result.current.loadError).toBe('Network error');
    });

    it('returns null on load failure', async () => {
      vi.mocked(workflowService.getWorkflow).mockRejectedValue(new Error('Not found'));

      const { result } = renderHook(() => useWorkflowPersistence());

      let loadResult: any;
      await act(async () => {
        loadResult = await result.current.loadWorkflow('1');
      });

      expect(loadResult).toBeNull();
    });

    it('uses default name when workflow has no name', async () => {
      const workflowNoName = { ...mockWorkflowResponse, name: null as unknown as string };
      vi.mocked(workflowService.getWorkflow).mockResolvedValue(workflowNoName);

      const { result } = renderHook(() => useWorkflowPersistence());

      await act(async () => {
        await result.current.loadWorkflow('1');
      });

      expect(mockWorkflowStoreState.setName).toHaveBeenCalledWith('Untitled Workflow');
    });

    it('auto-generates edges when layout has no edges', async () => {
      const workflowNoEdges = {
        ...mockWorkflowResponse,
        draft_config: {
          ...mockWorkflowResponse.draft_config,
          layout: {
            nodes: mockWorkflowResponse.draft_config.layout.nodes,
            edges: [],
          },
        },
      };
      vi.mocked(workflowService.getWorkflow).mockResolvedValue(workflowNoEdges);

      const { result } = renderHook(() => useWorkflowPersistence());

      let loadResult: any;
      await act(async () => {
        loadResult = await result.current.loadWorkflow('1');
      });

      // Should auto-generate edges between sequential nodes
      expect(loadResult.edges).toHaveLength(1);
      expect(loadResult.edges[0].source).toBe('node-trigger');
      expect(loadResult.edges[0].target).toBe('node-action-1');
    });
  });

  // ==========================================================================
  // retryLoad Tests
  // ==========================================================================

  describe('retryLoad', () => {
    it('retries loading a workflow', async () => {
      const mockWorkflow = {
        id: 1,
        name: 'Test',
        description: '',
        trigger_type: 'wordpress/user_registration',
        status: 'inactive' as const,
        created_at: '2024-01-01T00:00:00Z',
        updated_at: '2024-01-01T00:00:00Z',
        published_version_id: null,
        last_published_at: null,
        draft_config: {
          trigger: { type: 'wordpress/user_registration', config: {}, integration: 'wordpress' },
          actions: {
            'node-action-1': { id: 'node-action-1', type: 'wordpress/send_email', config: {}, integration: 'wordpress' },
          },
          layout: { nodes: [], edges: [] },
        },
      };
      vi.mocked(workflowService.getWorkflow).mockResolvedValue(mockWorkflow);

      const { result } = renderHook(() => useWorkflowPersistence());

      await act(async () => {
        await result.current.retryLoad('1');
      });

      expect(workflowService.getWorkflow).toHaveBeenCalledWith(1);
    });

    it('clears previous error on retry', async () => {
      vi.mocked(workflowService.getWorkflow)
        .mockRejectedValueOnce(new Error('First error'))
        .mockResolvedValueOnce({
          id: 1,
          name: 'Test',
          description: '',
          trigger_type: 'wordpress/user_registration',
          status: 'inactive' as const,
          created_at: '2024-01-01T00:00:00Z',
          updated_at: '2024-01-01T00:00:00Z',
          published_version_id: null,
          last_published_at: null,
          draft_config: {
            trigger: { type: 'wordpress/user_registration', config: {}, integration: 'wordpress' },
            actions: {
              'node-action-1': { id: 'node-action-1', type: 'wordpress/send_email', config: {}, integration: 'wordpress' },
            },
            layout: { nodes: [], edges: [] },
          },
        });

      const { result } = renderHook(() => useWorkflowPersistence());

      // First load fails
      await act(async () => {
        await result.current.loadWorkflow('1');
      });
      expect(result.current.loadError).toBe('First error');

      // Retry succeeds
      await act(async () => {
        await result.current.retryLoad('1');
      });
      expect(result.current.loadError).toBeNull();
    });
  });

  // ==========================================================================
  // clearLoadError Tests
  // ==========================================================================

  describe('clearLoadError', () => {
    it('clears the load error', async () => {
      vi.mocked(workflowService.getWorkflow).mockRejectedValue(new Error('Error'));

      const { result } = renderHook(() => useWorkflowPersistence());

      await act(async () => {
        await result.current.loadWorkflow('1');
      });
      expect(result.current.loadError).toBe('Error');

      act(() => {
        result.current.clearLoadError();
      });
      expect(result.current.loadError).toBeNull();
    });
  });

  // ==========================================================================
  // saveWorkflow Tests
  // ==========================================================================

  describe('saveWorkflow', () => {
    const mockNodes = [
      {
        id: 'node-trigger',
        type: 'trigger',
        position: { x: 250, y: 80 },
        data: {
          name: 'User Registration',
          actionType: 'wordpress/user_registration',
          config: {},
          appService: 'wordpress',
        },
      },
      {
        id: 'node-action-1',
        type: 'action',
        position: { x: 250, y: 240 },
        data: {
          name: 'Send Email',
          actionType: 'wordpress/send_email',
          config: { to: 'test@example.com' },
          label: 'send_email',
          appService: 'wordpress',
        },
      },
    ];

    const mockEdges = [{ id: 'e1', source: 'node-trigger', target: 'node-action-1' }];

    it('updates existing workflow when workflowId is provided', async () => {
      vi.mocked(workflowService.updateWorkflow).mockResolvedValue({ id: 123 } as any);

      const { result } = renderHook(() => useWorkflowPersistence('123'));

      let success: boolean;
      await act(async () => {
        success = await result.current.saveWorkflow({
          nodes: mockNodes as any,
          edges: mockEdges as any,
          workflowName: 'My Workflow',
        });
      });

      expect(success!).toBe(true);
      expect(workflowService.updateWorkflow).toHaveBeenCalledWith(
        123,
        expect.objectContaining({
          name: 'My Workflow',
          trigger_type: 'wordpress/user_registration',
        }),
      );
    });

    it('creates new workflow when no workflowId', async () => {
      vi.mocked(workflowService.createWorkflow).mockResolvedValue({ id: 999 } as any);

      const { result } = renderHook(() => useWorkflowPersistence());

      let success: boolean;
      await act(async () => {
        success = await result.current.saveWorkflow({
          nodes: mockNodes as any,
          edges: mockEdges as any,
          workflowName: 'New Workflow',
        });
      });

      expect(success!).toBe(true);
      expect(workflowService.createWorkflow).toHaveBeenCalled();
    });

    it('calls onWorkflowCreated callback with new ID', async () => {
      vi.mocked(workflowService.createWorkflow).mockResolvedValue({ id: 999 } as any);
      const onCreated = vi.fn();

      const { result } = renderHook(() => useWorkflowPersistence(undefined, onCreated));

      await act(async () => {
        await result.current.saveWorkflow({
          nodes: mockNodes as any,
          edges: mockEdges as any,
          workflowName: 'New Workflow',
        });
      });

      expect(onCreated).toHaveBeenCalledWith(999);
      expect(mockWorkflowStoreState.setId).toHaveBeenCalledWith(999);
    });

    it('sets isSaving during save operation', async () => {
      vi.mocked(workflowService.updateWorkflow).mockResolvedValue({ id: 1 } as any);

      const { result } = renderHook(() => useWorkflowPersistence('1'));

      await act(async () => {
        await result.current.saveWorkflow({
          nodes: mockNodes as any,
          edges: mockEdges as any,
          workflowName: 'Test',
        });
      });

      expect(mockWorkflowStoreState.setIsSaving).toHaveBeenCalledWith(true);
      expect(mockWorkflowStoreState.setIsSaving).toHaveBeenCalledWith(false);
    });

    it('fails when no trigger node', async () => {
      const nodesWithoutTrigger = [mockNodes[1]];

      const { result } = renderHook(() => useWorkflowPersistence('1'));

      let success: boolean;
      await act(async () => {
        success = await result.current.saveWorkflow({
          nodes: nodesWithoutTrigger as any,
          edges: [],
          workflowName: 'Test',
        });
      });

      expect(success!).toBe(false);
      expect(mockToast.error).toHaveBeenCalledWith('Workflow must have a trigger');
    });

    it('fails when no action nodes', async () => {
      const nodesWithoutAction = [mockNodes[0]];

      const { result } = renderHook(() => useWorkflowPersistence('1'));

      let success: boolean;
      await act(async () => {
        success = await result.current.saveWorkflow({
          nodes: nodesWithoutAction as any,
          edges: [],
          workflowName: 'Test',
        });
      });

      expect(success!).toBe(false);
      expect(mockToast.error).toHaveBeenCalledWith('Workflow must have at least one action');
    });

    it('handles API error on save', async () => {
      vi.mocked(workflowService.updateWorkflow).mockRejectedValue(new Error('Save failed'));

      const { result } = renderHook(() => useWorkflowPersistence('1'));

      let success: boolean;
      await act(async () => {
        success = await result.current.saveWorkflow({
          nodes: mockNodes as any,
          edges: mockEdges as any,
          workflowName: 'Test',
        });
      });

      expect(success!).toBe(false);
      expect(mockToast.error).toHaveBeenCalledWith('Save failed');
      expect(mockWorkflowStoreState.setIsSaving).toHaveBeenLastCalledWith(false);
    });

    it('includes layout data in saved config', async () => {
      vi.mocked(workflowService.updateWorkflow).mockResolvedValue({ id: 1 } as any);

      const { result } = renderHook(() => useWorkflowPersistence('1'));

      await act(async () => {
        await result.current.saveWorkflow({
          nodes: mockNodes as any,
          edges: mockEdges as any,
          workflowName: 'Test',
        });
      });

      expect(workflowService.updateWorkflow).toHaveBeenCalledWith(
        1,
        expect.objectContaining({
          config: expect.objectContaining({
            layout: expect.objectContaining({
              nodes: expect.arrayContaining([
                expect.objectContaining({ id: 'node-trigger', position: { x: 250, y: 80 } }),
              ]),
              edges: expect.arrayContaining([
                expect.objectContaining({
                  id: 'e1',
                  source: 'node-trigger',
                  target: 'node-action-1',
                }),
              ]),
            }),
          }),
        }),
      );
    });

    it('saves trigger with filters when present', async () => {
      vi.mocked(workflowService.updateWorkflow).mockResolvedValue({ id: 1 } as any);

      const nodesWithFilters = [
        {
          ...mockNodes[0],
          data: {
            ...mockNodes[0].data,
            config: {
              filters: { logic: 'AND', conditions: [] },
            },
          },
        },
        mockNodes[1],
      ];

      const { result } = renderHook(() => useWorkflowPersistence('1'));

      await act(async () => {
        await result.current.saveWorkflow({
          nodes: nodesWithFilters as any,
          edges: mockEdges as any,
          workflowName: 'Test',
        });
      });

      expect(workflowService.updateWorkflow).toHaveBeenCalledWith(
        1,
        expect.objectContaining({
          config: expect.objectContaining({
            trigger: expect.objectContaining({
              filters: { logic: 'AND', conditions: [] },
            }),
          }),
        }),
      );
    });

    it('uses default name when workflowName is empty', async () => {
      vi.mocked(workflowService.updateWorkflow).mockResolvedValue({ id: 1 } as any);

      const { result } = renderHook(() => useWorkflowPersistence('1'));

      await act(async () => {
        await result.current.saveWorkflow({
          nodes: mockNodes as any,
          edges: mockEdges as any,
          workflowName: '',
        });
      });

      expect(workflowService.updateWorkflow).toHaveBeenCalledWith(
        1,
        expect.objectContaining({
          name: 'Untitled Workflow',
        }),
      );
    });
  });
});
