// @lifecycle canonical - Unit tests for ToolDetectionService.
/**
 * ToolDetectionService Unit Tests
 *
 * Tests the smart tool detection service including:
 * - Explicit tool requests (tool:<id> pattern)
 * - Schema-based matching (trigger: schema_match)
 * - Always and never trigger types
 * - Strict mode matching
 */

import {
  ToolDetectionService,
  createToolDetectionService,
} from '../../../../src/modules/automation/detection/tool-detection-service.js';
import { DEFAULT_EXECUTION_CONFIG } from '../../../../src/modules/automation/types.js';
import type {
  LoadedScriptTool,
  ExecutionConfig,
} from '../../../../src/modules/automation/types.js';

describe('ToolDetectionService', () => {
  let service: ToolDetectionService;

  beforeEach(() => {
    service = createToolDetectionService({ debug: false });
  });

  const createMockTool = (overrides: Partial<LoadedScriptTool> = {}): LoadedScriptTool => ({
    id: 'analyze_csv',
    name: 'CSV Analyzer',
    description: 'Analyzes CSV files and returns statistics',
    scriptPath: 'script.py',
    runtime: 'python',
    inputSchema: {
      type: 'object',
      properties: {
        file: { type: 'string', description: 'Path to CSV file' },
        columns: { type: 'array', description: 'Columns to analyze' },
      },
      required: ['file'],
    },
    toolDir: '/tmp/tools/analyze_csv',
    absoluteScriptPath: '/tmp/tools/analyze_csv/script.py',
    promptId: 'data_analyzer',
    descriptionContent: 'Analyzes CSV files',
    ...overrides,
  });

  describe('detectTools', () => {
    it('should return empty array when no tools are provided', () => {
      // Note: detectTools signature is (input, args, availableTools)
      const result = service.detectTools('test', {}, []);
      expect(result).toHaveLength(0);
    });

    it('should detect tool by explicit tool request', () => {
      const tool = createMockTool();
      // Use explicit tool arg instead of name in input string
      const result = service.detectTools('', { tool: 'analyze_csv' }, [tool]);

      expect(result.length).toBeGreaterThan(0);
      expect(result[0].toolId).toBe('analyze_csv');
      expect(result[0].matchReason).toBe('name_match');
      expect(result[0].explicitRequest).toBe(true);
    });

    it('should detect tool by argument name matching schema', () => {
      const tool = createMockTool();
      const result = service.detectTools('', { file: 'data.csv' }, [tool]);

      expect(result.length).toBeGreaterThan(0);
      expect(result[0].toolId).toBe('analyze_csv');
      expect(result[0].matchReason).toBe('schema_match');
    });

    it('should assign higher priority to explicit requests over schema matches', () => {
      const tool = createMockTool();
      // Explicit tool request gets priority 1.0
      const explicitMatch = service.detectTools('', { tool: 'analyze_csv' }, [tool]);
      // Schema match gets priority 0.8-0.9
      const schemaMatch = service.detectTools('', { file: 'test.csv' }, [tool]);

      expect(explicitMatch.length).toBeGreaterThan(0);
      expect(schemaMatch.length).toBeGreaterThan(0);
      expect(explicitMatch[0].priority).toBeGreaterThanOrEqual(schemaMatch[0].priority);
    });

    it('should sort results by priority descending', () => {
      const tool1 = createMockTool({ id: 'tool_a' });
      const tool2 = createMockTool({ id: 'tool_b' });

      // Both tools have same schema, so both should match via schema_match
      const result = service.detectTools('', { file: 'test.csv' }, [tool1, tool2]);

      if (result.length >= 2) {
        expect(result[0].priority).toBeGreaterThanOrEqual(result[1].priority);
      }
    });

    it('should handle tools with empty input schema', () => {
      const tool = createMockTool({
        inputSchema: { type: 'object', properties: {} },
      });

      // Should not crash with empty schema - won't match without schema properties
      const result = service.detectTools('', { file: 'test.csv' }, [tool]);
      expect(Array.isArray(result)).toBe(true);
      // Empty schema means no match possible via schema_match
      expect(result).toHaveLength(0);
    });

    it('should detect multiple required parameters', () => {
      const tool = createMockTool({
        inputSchema: {
          type: 'object',
          properties: {
            file: { type: 'string' },
            format: { type: 'string' },
          },
          required: ['file', 'format'],
        },
      });

      const result = service.detectTools('', { file: 'data.csv', format: 'csv' }, [tool]);

      expect(result.length).toBeGreaterThan(0);
    });

    it('should skip disabled tools', () => {
      const tool = createMockTool({ enabled: false });
      const result = service.detectTools('analyze_csv', {}, [tool]);

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

  describe('factory function', () => {
    it('should create service with default config', () => {
      const service = createToolDetectionService();
      expect(service).toBeInstanceOf(ToolDetectionService);
    });

    it('should create service with custom config', () => {
      const service = createToolDetectionService({
        debug: true,
      });
      expect(service).toBeInstanceOf(ToolDetectionService);
    });
  });

  describe('execution config and trigger types', () => {
    const createToolWithExecution = (
      id: string,
      execution: Partial<ExecutionConfig>
    ): LoadedScriptTool => ({
      id,
      name: `Tool ${id}`,
      description: 'Test tool',
      scriptPath: 'script.py',
      runtime: 'python',
      inputSchema: {
        type: 'object',
        properties: { data: { type: 'string' } },
        required: ['data'],
      },
      toolDir: `/tmp/tools/${id}`,
      absoluteScriptPath: `/tmp/tools/${id}/script.py`,
      promptId: 'test_prompt',
      descriptionContent: 'Test',
      enabled: true,
      execution: { ...DEFAULT_EXECUTION_CONFIG, ...execution },
    });

    describe('trigger types', () => {
      it('should match always trigger regardless of args', () => {
        const tool = createToolWithExecution('always_tool', { trigger: 'always' });
        const result = service.detectTools('unrelated input', {}, [tool]);

        expect(result).toHaveLength(1);
        expect(result[0].toolId).toBe('always_tool');
        expect(result[0].priority).toBe(1.0);
      });

      it('should only match explicit trigger with tool:<id> arg', () => {
        const tool = createToolWithExecution('explicit_tool', { trigger: 'explicit' });

        // Without explicit arg - should not match
        const noMatch = service.detectTools('', { data: 'value' }, [tool]);
        expect(noMatch).toHaveLength(0);

        // With explicit arg - should match
        const withExplicit = service.detectTools('', { 'tool:explicit_tool': true }, [tool]);
        expect(withExplicit).toHaveLength(1);
        expect(withExplicit[0].toolId).toBe('explicit_tool');
      });

      it('should match schema_match trigger with matching args', () => {
        const tool = createToolWithExecution('schema_tool', { trigger: 'schema_match' });

        const result = service.detectTools('', { data: 'test value' }, [tool]);

        expect(result).toHaveLength(1);
        expect(result[0].toolId).toBe('schema_tool');
        expect(result[0].matchReason).toBe('schema_match');
      });
    });

    describe('strict mode matching', () => {
      it('should match with partial params when strict is false (default)', () => {
        const tool = createToolWithExecution('partial_tool', { strict: false });
        tool.inputSchema = {
          type: 'object',
          properties: {
            required1: { type: 'string' },
            required2: { type: 'string' },
          },
          required: ['required1', 'required2'],
        };

        // Only providing one of two required params
        const result = service.detectTools('', { required1: 'value' }, [tool]);

        // Non-strict mode should match with partial params
        expect(result).toHaveLength(1);
        expect(result[0].toolId).toBe('partial_tool');
        expect(result[0].priority).toBe(0.8); // Partial match priority
      });

      it('should not match with partial params when strict is true', () => {
        const tool = createToolWithExecution('strict_tool', { strict: true });
        tool.inputSchema = {
          type: 'object',
          properties: {
            required1: { type: 'string' },
            required2: { type: 'string' },
          },
          required: ['required1', 'required2'],
        };

        // Only providing one of two required params
        const result = service.detectTools('', { required1: 'value' }, [tool]);

        // Strict mode should NOT match with partial params
        expect(result).toHaveLength(0);
      });

      it('should match in strict mode when all required params present', () => {
        const tool = createToolWithExecution('strict_tool', { strict: true });
        tool.inputSchema = {
          type: 'object',
          properties: {
            required1: { type: 'string' },
            required2: { type: 'string' },
          },
          required: ['required1', 'required2'],
        };

        // Providing all required params
        const result = service.detectTools('', { required1: 'a', required2: 'b' }, [tool]);

        expect(result).toHaveLength(1);
        expect(result[0].toolId).toBe('strict_tool');
        expect(result[0].priority).toBe(0.9); // Full match priority
      });
    });

    describe('match result annotations', () => {
      it('should include requiresConfirmation in match result', () => {
        const tool = createToolWithExecution('confirm_tool', { confirm: true });
        // Use explicit tool request to trigger match
        const result = service.detectTools('', { tool: 'confirm_tool' }, [tool]);

        expect(result).toHaveLength(1);
        expect(result[0].requiresConfirmation).toBe(true);
      });

      it('should include explicitRequest flag in match result', () => {
        const tool = createToolWithExecution('test_tool', {});
        const result = service.detectTools('', { tool: 'test_tool' }, [tool]);

        expect(result[0].explicitRequest).toBe(true);
      });

      it('should set explicitRequest false for parameter matches', () => {
        const tool = createToolWithExecution('param_tool', {});
        tool.inputSchema = {
          type: 'object',
          properties: { value: { type: 'string' } },
          required: ['value'],
        };

        const result = service.detectTools('', { value: 'test' }, [tool]);

        expect(result[0].explicitRequest).toBe(false);
        // Default is now confirm: true (secure by default)
        expect(result[0].requiresConfirmation).toBe(true);
      });

      it('should allow explicit confirm: false override', () => {
        const tool = createToolWithExecution('no_confirm_tool', { confirm: false });
        tool.inputSchema = {
          type: 'object',
          properties: { value: { type: 'string' } },
          required: ['value'],
        };

        const result = service.detectTools('', { value: 'test' }, [tool]);

        expect(result[0].explicitRequest).toBe(false);
        expect(result[0].requiresConfirmation).toBe(false);
      });
    });

    describe('explicit tool extraction', () => {
      it('should extract tool from tool arg', () => {
        const tool = createToolWithExecution('my_tool', { trigger: 'explicit' });
        const result = service.detectTools('', { tool: 'my_tool' }, [tool]);

        expect(result).toHaveLength(1);
        expect(result[0].explicitRequest).toBe(true);
      });

      it('should extract tool from tool_id arg', () => {
        const tool = createToolWithExecution('my_tool', { trigger: 'explicit' });
        const result = service.detectTools('', { tool_id: 'my_tool' }, [tool]);

        expect(result).toHaveLength(1);
        expect(result[0].explicitRequest).toBe(true);
      });

      it('should extract tool from toolId arg (camelCase)', () => {
        const tool = createToolWithExecution('my_tool', { trigger: 'explicit' });
        const result = service.detectTools('', { toolId: 'my_tool' }, [tool]);

        expect(result).toHaveLength(1);
        expect(result[0].explicitRequest).toBe(true);
      });

      it('should handle comma-separated tool list', () => {
        const tool1 = createToolWithExecution('tool_a', { trigger: 'explicit' });
        const tool2 = createToolWithExecution('tool_b', { trigger: 'explicit' });

        const result = service.detectTools('', { tool: 'tool_a, tool_b' }, [tool1, tool2]);

        expect(result).toHaveLength(2);
        expect(result.map((m) => m.toolId).sort()).toEqual(['tool_a', 'tool_b']);
      });
    });
  });
});
