import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import React from 'react';
import { DataProvider, useData, useTriggers, useActions, useIntegrations } from './DataContext';
import { clearAsyncCache } from '../hooks/useAsync';

// Wrapper with DataProvider
const wrapper = ({ children }: { children: React.ReactNode }) => (
  <DataProvider>{children}</DataProvider>
);

describe('DataContext', () => {
  beforeEach(() => {
    // Clear cache before each test to ensure fresh data
    clearAsyncCache();
  });

  describe('useData', () => {
    it('should start in loading state', () => {
      const { result } = renderHook(() => useData(), { wrapper });

      expect(result.current.isLoading).toBe(true);
    });

    it('should load triggers, actions, and integrations', async () => {
      const { result } = renderHook(() => useData(), { wrapper });

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      expect(result.current.triggers).toBeDefined();
      expect(Array.isArray(result.current.triggers)).toBe(true);
      expect(result.current.actions).toBeDefined();
      expect(Array.isArray(result.current.actions)).toBe(true);
      expect(result.current.integrations).toBeDefined();
      expect(Array.isArray(result.current.integrations)).toBe(true);
    });

    it('should have no errors on successful load', async () => {
      const { result } = renderHook(() => useData(), { wrapper });

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      expect(result.current.triggersError).toBeNull();
      expect(result.current.actionsError).toBeNull();
      expect(result.current.integrationsError).toBeNull();
    });

    it('should warn when used outside provider', () => {
      const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

      // Render without wrapper
      renderHook(() => useData());

      expect(consoleSpy).toHaveBeenCalledWith(
        expect.stringContaining('useData() called outside of DataProvider'),
      );

      consoleSpy.mockRestore();
    });

    it('should provide refetch functions', async () => {
      const { result } = renderHook(() => useData(), { wrapper });

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      expect(typeof result.current.refetchTriggers).toBe('function');
      expect(typeof result.current.refetchActions).toBe('function');
      expect(typeof result.current.refetchIntegrations).toBe('function');
      expect(typeof result.current.refetchAll).toBe('function');
    });
  });

  describe('useTriggers', () => {
    it('should provide triggers data', async () => {
      const { result } = renderHook(() => useTriggers(), { wrapper });

      await waitFor(() => {
        expect(result.current.loading).toBe(false);
      });

      expect(result.current.triggers).toBeDefined();
      expect(Array.isArray(result.current.triggers)).toBe(true);
      expect(result.current.error).toBeNull();
      expect(typeof result.current.refetch).toBe('function');
    });

    it('should return mock trigger data', async () => {
      const { result } = renderHook(() => useTriggers(), { wrapper });

      await waitFor(() => {
        expect(result.current.loading).toBe(false);
      });

      // Check we have triggers from MSW mock
      expect(result.current.triggers.length).toBeGreaterThan(0);
      expect(result.current.triggers[0]).toHaveProperty('type');
      expect(result.current.triggers[0]).toHaveProperty('label');
    });
  });

  describe('useActions', () => {
    it('should provide actions data', async () => {
      const { result } = renderHook(() => useActions(), { wrapper });

      await waitFor(() => {
        expect(result.current.loading).toBe(false);
      });

      expect(result.current.actions).toBeDefined();
      expect(Array.isArray(result.current.actions)).toBe(true);
      expect(result.current.error).toBeNull();
      expect(typeof result.current.refetch).toBe('function');
    });

    it('should return mock action data', async () => {
      const { result } = renderHook(() => useActions(), { wrapper });

      await waitFor(() => {
        expect(result.current.loading).toBe(false);
      });

      // Check we have actions from MSW mock
      expect(result.current.actions.length).toBeGreaterThan(0);
      expect(result.current.actions[0]).toHaveProperty('type');
      expect(result.current.actions[0]).toHaveProperty('label');
      expect(result.current.actions[0]).toHaveProperty('schema');
    });
  });

  describe('useIntegrations', () => {
    it('should provide integrations data', async () => {
      const { result } = renderHook(() => useIntegrations(), { wrapper });

      await waitFor(() => {
        expect(result.current.loading).toBe(false);
      });

      expect(result.current.integrations).toBeDefined();
      expect(Array.isArray(result.current.integrations)).toBe(true);
      expect(result.current.error).toBeNull();
      expect(typeof result.current.refetch).toBe('function');
    });

    it('should return mock integration data', async () => {
      const { result } = renderHook(() => useIntegrations(), { wrapper });

      await waitFor(() => {
        expect(result.current.loading).toBe(false);
      });

      // Check we have integrations from MSW mock
      expect(result.current.integrations.length).toBeGreaterThan(0);
      expect(result.current.integrations[0]).toHaveProperty('id');
      expect(result.current.integrations[0]).toHaveProperty('name');
    });
  });

  describe('refetchAll', () => {
    it('should refetch all data without error', async () => {
      const { result } = renderHook(() => useData(), { wrapper });

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      // Clear cache and refetch
      clearAsyncCache();

      await result.current.refetchAll();

      // Should complete without error
      expect(result.current.triggersError).toBeNull();
      expect(result.current.actionsError).toBeNull();
      expect(result.current.integrationsError).toBeNull();
    });
  });

  describe('combined loading state', () => {
    it('should be loading when any data is loading', () => {
      const { result } = renderHook(() => useData(), { wrapper });

      // Initially should be loading
      expect(result.current.isLoading).toBe(true);
    });

    it('should not be loading when all data loaded', async () => {
      const { result } = renderHook(() => useData(), { wrapper });

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      expect(result.current.triggersLoading).toBe(false);
      expect(result.current.actionsLoading).toBe(false);
      expect(result.current.integrationsLoading).toBe(false);
    });
  });

  describe('default values', () => {
    it('should return empty arrays before data loads', () => {
      const { result } = renderHook(() => useData(), { wrapper });

      // While loading, should return empty arrays (not undefined)
      expect(result.current.triggers).toEqual([]);
      expect(result.current.actions).toEqual([]);
      expect(result.current.integrations).toEqual([]);
    });
  });

  describe('data caching', () => {
    it('should use cached data for subsequent renders', async () => {
      // First render
      const { result: result1 } = renderHook(() => useData(), { wrapper });

      await waitFor(() => {
        expect(result1.current.isLoading).toBe(false);
      });

      const firstTriggers = result1.current.triggers;

      // Second render (should use cache)
      const { result: result2 } = renderHook(() => useData(), { wrapper });

      // Should have data immediately from cache
      await waitFor(() => {
        expect(result2.current.isLoading).toBe(false);
      });

      expect(result2.current.triggers).toEqual(firstTriggers);
    });
  });
});
