/**
 * @license
 * Copyright 2025 Qwen
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { SubagentValidator } from './validation.js';
import { type SubagentConfig, SubagentError } from './types.js';

describe('SubagentValidator', () => {
  let validator: SubagentValidator;

  beforeEach(() => {
    validator = new SubagentValidator();
  });

  describe('validateName', () => {
    it('should accept valid names', () => {
      const validNames = [
        'test-agent',
        'code_reviewer',
        'agent123',
        'my-helper',
      ];

      for (const name of validNames) {
        const result = validator.validateName(name);
        expect(result.isValid).toBe(true);
        expect(result.errors).toHaveLength(0);
      }
    });

    it('should reject empty or whitespace names', () => {
      const invalidNames = ['', '   ', '\t', '\n'];

      for (const name of invalidNames) {
        const result = validator.validateName(name);
        expect(result.isValid).toBe(false);
        expect(result.errors).toContain('Name is required and cannot be empty');
      }
    });

    it('should reject names that are too short', () => {
      const result = validator.validateName('a');
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain(
        'Name must be at least 2 characters long',
      );
    });

    it('should reject names that are too long', () => {
      const longName = 'a'.repeat(51);
      const result = validator.validateName(longName);
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Name must be 50 characters or less');
    });

    it('should reject names with invalid characters', () => {
      const invalidNames = ['test@agent', 'agent.name', 'test agent', 'agent!'];

      for (const name of invalidNames) {
        const result = validator.validateName(name);
        expect(result.isValid).toBe(false);
        expect(result.errors).toContain(
          'Name can only contain letters, numbers, hyphens, and underscores',
        );
      }
    });

    it('should reject names starting with special characters', () => {
      const invalidNames = ['-agent', '_agent'];

      for (const name of invalidNames) {
        const result = validator.validateName(name);
        expect(result.isValid).toBe(false);
        expect(result.errors).toContain(
          'Name cannot start with a hyphen or underscore',
        );
      }
    });

    it('should reject names ending with special characters', () => {
      const invalidNames = ['agent-', 'agent_'];

      for (const name of invalidNames) {
        const result = validator.validateName(name);
        expect(result.isValid).toBe(false);
        expect(result.errors).toContain(
          'Name cannot end with a hyphen or underscore',
        );
      }
    });

    it('should reject reserved names', () => {
      const reservedNames = [
        'self',
        'system',
        'user',
        'model',
        'tool',
        'config',
        'default',
      ];

      for (const name of reservedNames) {
        const result = validator.validateName(name);
        expect(result.isValid).toBe(false);
        expect(result.errors).toContain(
          `"${name}" is a reserved name and cannot be used`,
        );
      }
    });

    it('should warn about naming conventions', () => {
      const result = validator.validateName('TestAgent');
      expect(result.isValid).toBe(true);
      expect(result.warnings).toContain(
        'Consider using lowercase names for consistency',
      );
    });

    it('should warn about mixed separators', () => {
      const result = validator.validateName('test-agent_helper');
      expect(result.isValid).toBe(true);
      expect(result.warnings).toContain(
        'Consider using either hyphens or underscores consistently, not both',
      );
    });
  });

  describe('validateSystemPrompt', () => {
    it('should accept valid system prompts', () => {
      const validPrompts = [
        'You are a helpful assistant.',
        'You are a code reviewer. Analyze the provided code and suggest improvements.',
        'Help the user with ${task} by using available tools.',
      ];

      for (const prompt of validPrompts) {
        const result = validator.validateSystemPrompt(prompt);
        expect(result.isValid).toBe(true);
        expect(result.errors).toHaveLength(0);
      }
    });

    it('should reject empty prompts', () => {
      const invalidPrompts = ['', '   ', '\t\n'];

      for (const prompt of invalidPrompts) {
        const result = validator.validateSystemPrompt(prompt);
        expect(result.isValid).toBe(false);
        expect(result.errors).toContain(
          'System prompt is required and cannot be empty',
        );
      }
    });

    it('should reject prompts that are too short', () => {
      const result = validator.validateSystemPrompt('Short');
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain(
        'System prompt must be at least 10 characters long',
      );
    });

    it('should reject prompts that are too long', () => {
      const longPrompt = 'a'.repeat(10001);
      const result = validator.validateSystemPrompt(longPrompt);
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain(
        'System prompt is too long (>10,000 characters)',
      );
    });

    it('should warn about long prompts', () => {
      const longPrompt = 'a'.repeat(5001);
      const result = validator.validateSystemPrompt(longPrompt);
      expect(result.isValid).toBe(true);
      expect(result.warnings).toContain(
        'System prompt is quite long (>5,000 characters), consider shortening',
      );
    });
  });

  describe('validateTools', () => {
    it('should accept valid tool arrays', () => {
      const result = validator.validateTools(['read_file', 'write_file']);
      expect(result.isValid).toBe(true);
      expect(result.errors).toHaveLength(0);
    });

    it('should reject non-array inputs', () => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const result = validator.validateTools('not-an-array' as any);
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Tools must be an array of strings');
    });

    it('should warn about empty arrays', () => {
      const result = validator.validateTools([]);
      expect(result.isValid).toBe(true);
      expect(result.warnings).toContain(
        'Empty tools array - subagent will inherit all available tools',
      );
    });

    it('should warn about duplicate tools', () => {
      const result = validator.validateTools([
        'read_file',
        'read_file',
        'write_file',
      ]);
      expect(result.isValid).toBe(true);
      expect(result.warnings).toContain(
        'Duplicate tool names found in tools array',
      );
    });

    it('should reject non-string tool names', () => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const result = validator.validateTools([123, 'read_file'] as any);
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain(
        'Tool name must be a string, got: number',
      );
    });

    it('should reject empty tool names', () => {
      const result = validator.validateTools(['', 'read_file']);
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Tool name cannot be empty');
    });
  });

  describe('validateModelConfig', () => {
    it('should accept valid model configurations', () => {
      const validConfigs = [
        { model: 'gemini-1.5-pro', temp: 0.7, top_p: 0.9 },
        { temp: 0.5 },
        { top_p: 1.0 },
        {},
      ];

      for (const config of validConfigs) {
        const result = validator.validateModelConfig(config);
        expect(result.isValid).toBe(true);
        expect(result.errors).toHaveLength(0);
      }
    });

    it('should reject invalid model names', () => {
      const result = validator.validateModelConfig({ model: '' });
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Model name must be a non-empty string');
    });

    it('should reject invalid temperature values', () => {
      const invalidTemps = [-0.1, 2.1, 'not-a-number'];

      for (const temp of invalidTemps) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const result = validator.validateModelConfig({ temp: temp as any });
        expect(result.isValid).toBe(false);
      }
    });

    it('should warn about high temperature', () => {
      const result = validator.validateModelConfig({ temp: 1.5 });
      expect(result.isValid).toBe(true);
      expect(result.warnings).toContain(
        'High temperature (>1) may produce very creative but unpredictable results',
      );
    });

    it('should reject invalid top_p values', () => {
      const invalidTopP = [-0.1, 1.1, 'not-a-number'];

      for (const top_p of invalidTopP) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const result = validator.validateModelConfig({ top_p: top_p as any });
        expect(result.isValid).toBe(false);
      }
    });
  });

  describe('validateRunConfig', () => {
    it('should accept valid run configurations', () => {
      const validConfigs = [
        { max_time_minutes: 10, max_turns: 20 },
        { max_time_minutes: 5 },
        { max_turns: 10 },
        {},
      ];

      for (const config of validConfigs) {
        const result = validator.validateRunConfig(config);
        expect(result.isValid).toBe(true);
        expect(result.errors).toHaveLength(0);
      }
    });

    it('should reject invalid max_time_minutes', () => {
      const invalidTimes = [0, -1, 'not-a-number'];

      for (const time of invalidTimes) {
        const result = validator.validateRunConfig({
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          max_time_minutes: time as any,
        });
        expect(result.isValid).toBe(false);
      }
    });

    it('should warn about very long execution times', () => {
      const result = validator.validateRunConfig({ max_time_minutes: 120 });
      expect(result.isValid).toBe(true);
      expect(result.warnings).toContain(
        'Very long execution time (>60 minutes) may cause resource issues',
      );
    });

    it('should reject invalid max_turns', () => {
      const invalidTurns = [0, -1, 1.5, 'not-a-number'];

      for (const turns of invalidTurns) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const result = validator.validateRunConfig({ max_turns: turns as any });
        expect(result.isValid).toBe(false);
      }
    });

    it('should warn about high turn limits', () => {
      const result = validator.validateRunConfig({ max_turns: 150 });
      expect(result.isValid).toBe(true);
      expect(result.warnings).toContain(
        'Very high turn limit (>100) may cause long execution times',
      );
    });
  });

  describe('validateConfig', () => {
    const validConfig: SubagentConfig = {
      name: 'test-agent',
      description: 'A test subagent',
      systemPrompt: 'You are a helpful assistant.',
      level: 'project',
      filePath: '/path/to/test-agent.md',
    };

    it('should accept valid configurations', () => {
      const result = validator.validateConfig(validConfig);
      expect(result.isValid).toBe(true);
      expect(result.errors).toHaveLength(0);
    });

    it('should collect errors from all validation steps', () => {
      const invalidConfig: SubagentConfig = {
        name: '',
        description: '',
        systemPrompt: '',
        level: 'project',
        filePath: '/path/to/invalid.md',
      };

      const result = validator.validateConfig(invalidConfig);
      expect(result.isValid).toBe(false);
      expect(result.errors.length).toBeGreaterThan(0);
    });

    it('should collect warnings from all validation steps', () => {
      const configWithWarnings: SubagentConfig = {
        ...validConfig,
        name: 'TestAgent', // Will generate warning about case
        description: 'A'.repeat(501), // Will generate warning about long description
      };

      const result = validator.validateConfig(configWithWarnings);
      expect(result.isValid).toBe(true);
      expect(result.warnings.length).toBeGreaterThan(0);
    });
  });

  describe('validateOrThrow', () => {
    const validConfig: SubagentConfig = {
      name: 'test-agent',
      description: 'A test subagent',
      systemPrompt: 'You are a helpful assistant.',
      level: 'project',
      filePath: '/path/to/test-agent.md',
    };

    it('should not throw for valid configurations', () => {
      expect(() => validator.validateOrThrow(validConfig)).not.toThrow();
    });

    it('should throw SubagentError for invalid configurations', () => {
      const invalidConfig: SubagentConfig = {
        ...validConfig,
        name: '',
      };

      expect(() => validator.validateOrThrow(invalidConfig)).toThrow(
        SubagentError,
      );
      expect(() => validator.validateOrThrow(invalidConfig)).toThrow(
        /Validation failed/,
      );
    });

    it('should include subagent name in error', () => {
      const invalidConfig: SubagentConfig = {
        ...validConfig,
        name: '',
      };

      try {
        validator.validateOrThrow(invalidConfig, 'custom-name');
        expect.fail('Should have thrown');
      } catch (error) {
        expect(error).toBeInstanceOf(SubagentError);
        expect((error as SubagentError).subagentName).toBe('custom-name');
      }
    });
  });
});
