import { renderHook } from '@testing-library/react';
import { Tools, Constants, EToolResources } from 'librechat-data-provider';
import type { TEphemeralAgent } from 'librechat-data-provider';
import useAgentToolPermissions from '../useAgentToolPermissions';

// Mock dependencies
jest.mock('~/data-provider', () => ({
  useGetAgentByIdQuery: jest.fn(),
}));

jest.mock('~/Providers', () => ({
  useAgentsMapContext: jest.fn(),
}));

// Import mocked functions after mocking
import { useGetAgentByIdQuery } from '~/data-provider';
import { useAgentsMapContext } from '~/Providers';

type HookProps = {
  agentId?: string | null;
  ephemeralAgent?: TEphemeralAgent | null;
};

describe('useAgentToolPermissions', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('Ephemeral Agent Scenarios (without ephemeralAgent parameter)', () => {
    it('should return false for all tools when agentId is null and no ephemeralAgent provided', () => {
      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(null));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });

    it('should return false for all tools when agentId is undefined and no ephemeralAgent provided', () => {
      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(undefined));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });

    it('should return false for all tools when agentId is empty string and no ephemeralAgent provided', () => {
      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(''));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });

    it('should return false for all tools when agentId is EPHEMERAL_AGENT_ID and no ephemeralAgent provided', () => {
      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(Constants.EPHEMERAL_AGENT_ID));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });
  });

  describe('Ephemeral Agent with Tool Settings', () => {
    it('should return true for file_search when ephemeralAgent has file_search enabled', () => {
      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const ephemeralAgent = {
        [EToolResources.file_search]: true,
      };

      const { result } = renderHook(() => useAgentToolPermissions(null, ephemeralAgent));

      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });

    it('should return true for execute_code when ephemeralAgent has execute_code enabled', () => {
      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const ephemeralAgent = {
        [EToolResources.execute_code]: true,
      };

      const { result } = renderHook(() => useAgentToolPermissions(undefined, ephemeralAgent));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(true);
      expect(result.current.tools).toBeUndefined();
    });

    it('should return true for both tools when ephemeralAgent has both enabled', () => {
      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const ephemeralAgent = {
        [EToolResources.file_search]: true,
        [EToolResources.execute_code]: true,
      };

      const { result } = renderHook(() => useAgentToolPermissions('', ephemeralAgent));

      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(true);
      expect(result.current.tools).toBeUndefined();
    });

    it('should return false for tools when ephemeralAgent has them explicitly disabled', () => {
      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const ephemeralAgent = {
        [EToolResources.file_search]: false,
        [EToolResources.execute_code]: false,
      };

      const { result } = renderHook(() =>
        useAgentToolPermissions(Constants.EPHEMERAL_AGENT_ID, ephemeralAgent),
      );

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });

    it('should handle ephemeralAgent with ocr property without affecting other tools', () => {
      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const ephemeralAgent = {
        [EToolResources.file_search]: true,
      };

      const { result } = renderHook(() => useAgentToolPermissions(null, ephemeralAgent));

      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });

    it('should not affect regular agents when ephemeralAgent is provided', () => {
      const agentId = 'agent_regular';
      const mockAgent = {
        id: agentId,
        tools: [Tools.file_search],
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const ephemeralAgent = {
        [EToolResources.execute_code]: true,
      };

      const { result } = renderHook(() => useAgentToolPermissions(agentId, ephemeralAgent));

      // Should use regular agent's tools, not ephemeralAgent
      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toEqual([Tools.file_search]);
    });
  });

  describe('Regular Agent with Tools', () => {
    it('should allow file_search when agent has the tool', () => {
      const agentId = 'agent_123';
      const mockAgent = {
        id: agentId,
        tools: [Tools.file_search, 'other_tool'],
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toEqual([Tools.file_search, 'other_tool']);
    });

    it('should allow execute_code when agent has the tool', () => {
      const agentId = 'agent_456';
      const mockAgent = {
        id: agentId,
        tools: [Tools.execute_code, 'another_tool'],
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(true);
      expect(result.current.tools).toEqual([Tools.execute_code, 'another_tool']);
    });

    it('should allow both tools when agent has both', () => {
      const agentId = 'agent_789';
      const mockAgent = {
        id: agentId,
        tools: [Tools.file_search, Tools.execute_code, 'custom_tool'],
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(true);
      expect(result.current.tools).toEqual([Tools.file_search, Tools.execute_code, 'custom_tool']);
    });

    it('should disallow both tools when agent has neither', () => {
      const agentId = 'agent_no_tools';
      const mockAgent = {
        id: agentId,
        tools: ['custom_tool1', 'custom_tool2'],
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toEqual(['custom_tool1', 'custom_tool2']);
    });

    it('should handle agent with empty tools array', () => {
      const agentId = 'agent_empty_tools';
      const mockAgent = {
        id: agentId,
        tools: [],
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toEqual([]);
    });

    it('should handle agent with undefined tools', () => {
      const agentId = 'agent_undefined_tools';
      const mockAgent = {
        id: agentId,
        tools: undefined,
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });
  });

  describe('Agent Data from Query', () => {
    it('should prioritize agentData tools over selectedAgent tools', () => {
      const agentId = 'agent_with_query_data';
      const mockAgent = {
        id: agentId,
        tools: ['old_tool'],
      };
      const mockAgentData = {
        id: agentId,
        tools: [Tools.file_search, Tools.execute_code],
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: mockAgentData });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(true);
      expect(result.current.tools).toEqual([Tools.file_search, Tools.execute_code]);
    });

    it('should fallback to selectedAgent tools when agentData has no tools', () => {
      const agentId = 'agent_fallback';
      const mockAgent = {
        id: agentId,
        tools: [Tools.file_search],
      };
      const mockAgentData = {
        id: agentId,
        tools: undefined,
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: mockAgentData });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toEqual([Tools.file_search]);
    });
  });

  describe('Agent Not Found Scenarios', () => {
    it('should disallow all tools when agent is not found in map', () => {
      const agentId = 'agent_nonexistent';

      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });

    it('should disallow all tools when agentsMap is null', () => {
      const agentId = 'agent_with_null_map';

      (useAgentsMapContext as jest.Mock).mockReturnValue(null);
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });

    it('should disallow all tools when agentsMap is undefined', () => {
      const agentId = 'agent_with_undefined_map';

      (useAgentsMapContext as jest.Mock).mockReturnValue(undefined);
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });
  });

  describe('Memoization and Performance', () => {
    it('should memoize results when inputs do not change', () => {
      const agentId = 'agent_memoized';
      const mockAgent = {
        id: agentId,
        tools: [Tools.file_search],
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result, rerender } = renderHook(() => useAgentToolPermissions(agentId));

      const firstResult = result.current;

      // Rerender without changing inputs
      rerender();

      const secondResult = result.current;

      // The hook returns a new object each time, but the values should be equal
      expect(firstResult.fileSearchAllowedByAgent).toBe(secondResult.fileSearchAllowedByAgent);
      expect(firstResult.codeAllowedByAgent).toBe(secondResult.codeAllowedByAgent);
      // Tools array reference should be the same since it comes from useMemo
      expect(firstResult.tools).toBe(secondResult.tools);

      // Verify the actual values are correct
      expect(secondResult.fileSearchAllowedByAgent).toBe(true);
      expect(secondResult.codeAllowedByAgent).toBe(false);
      expect(secondResult.tools).toEqual([Tools.file_search]);
    });

    it('should recompute when agentId changes', () => {
      const agentId1 = 'agent_1';
      const agentId2 = 'agent_2';
      const mockAgents = {
        [agentId1]: { id: agentId1, tools: [Tools.file_search] },
        [agentId2]: { id: agentId2, tools: [Tools.execute_code] },
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue(mockAgents);
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result, rerender } = renderHook(({ agentId }) => useAgentToolPermissions(agentId), {
        initialProps: { agentId: agentId1 },
      });

      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(false);

      // Change agentId
      rerender({ agentId: agentId2 });

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(true);
    });

    it('should handle switching between ephemeral and regular agents', () => {
      const regularAgentId = 'agent_regular';
      const mockAgent = {
        id: regularAgentId,
        tools: [],
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [regularAgentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const ephemeralAgent = {
        [EToolResources.file_search]: true,
        [EToolResources.execute_code]: true,
      };

      const { result, rerender } = renderHook(
        ({ agentId, ephemeralAgent }) => useAgentToolPermissions(agentId, ephemeralAgent),
        { initialProps: { agentId: null, ephemeralAgent } as HookProps },
      );

      // Start with ephemeral agent (null) with tools enabled
      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(true);

      // Switch to regular agent
      rerender({ agentId: regularAgentId, ephemeralAgent });
      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);

      // Switch back to ephemeral
      rerender({ agentId: '', ephemeralAgent });
      expect(result.current.fileSearchAllowedByAgent).toBe(true);
      expect(result.current.codeAllowedByAgent).toBe(true);

      // Switch to ephemeral without tools
      rerender({ agentId: null, ephemeralAgent: undefined });
      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
    });
  });

  describe('Edge Cases', () => {
    it('should handle agents with null tools gracefully', () => {
      const agentId = 'agent_null_tools';
      const mockAgent = {
        id: agentId,
        tools: null as any,
      };

      (useAgentsMapContext as jest.Mock).mockReturnValue({
        [agentId]: mockAgent,
      });
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeNull();
    });

    it('should handle whitespace-only agentId as ephemeral', () => {
      // Note: Based on the current implementation, only empty string is treated as ephemeral
      // Whitespace-only strings would be treated as regular agent IDs
      const whitespaceId = '   ';

      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({ data: undefined });

      const { result } = renderHook(() => useAgentToolPermissions(whitespaceId));

      // Whitespace ID is not considered ephemeral in current implementation
      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
    });

    it('should handle query loading state', () => {
      const agentId = 'agent_loading';

      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({
        data: undefined,
        isLoading: true,
        error: null,
      });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      // During loading, should return false for non-ephemeral agents
      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });

    it('should handle query error state', () => {
      const agentId = 'agent_error';

      (useAgentsMapContext as jest.Mock).mockReturnValue({});
      (useGetAgentByIdQuery as jest.Mock).mockReturnValue({
        data: undefined,
        isLoading: false,
        error: new Error('Failed to fetch agent'),
      });

      const { result } = renderHook(() => useAgentToolPermissions(agentId));

      // On error, should return false for non-ephemeral agents
      expect(result.current.fileSearchAllowedByAgent).toBe(false);
      expect(result.current.codeAllowedByAgent).toBe(false);
      expect(result.current.tools).toBeUndefined();
    });
  });
});
