#!/usr/bin/env python3
"""
Test suite for the config command functionality.
Tests all handle methods and input possibilities for the config command.
"""

import os
import sys
import pytest
from unittest.mock import patch, Mock, MagicMock

# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 
                                '..', '..', 'src'))

from cybersentry.repl.commands.config import (
    ConfigCommand, ENV_VARS, get_env_var_value, set_env_var
)
from cybersentry.repl.commands.base import Command


class TestConfigCommand:
    """Test cases for ConfigCommand."""
    
    @pytest.fixture(autouse=True)
    def setup_and_cleanup(self):
        """Setup and cleanup for each test."""
        # Set up test environment
        os.environ['cybersentry_TELEMETRY'] = 'false'
        os.environ['cybersentry_TRACING'] = 'false'
        
        # Store original values of environment variables we'll modify
        self.original_env_vars = {}
        for var_info in ENV_VARS.values():
            var_name = var_info["name"]
            if var_name in os.environ:
                self.original_env_vars[var_name] = os.environ[var_name]
        
        yield
        
        # Restore original environment variables
        for var_info in ENV_VARS.values():
            var_name = var_info["name"]
            if var_name in self.original_env_vars:
                os.environ[var_name] = self.original_env_vars[var_name]
            elif var_name in os.environ:
                del os.environ[var_name]
    
    @pytest.fixture
    def config_command(self):
        """Create a ConfigCommand instance for testing."""
        return ConfigCommand()
    
    def test_command_initialization(self, config_command):
        """Test that ConfigCommand initializes correctly."""
        assert config_command.name == "/config"
        assert config_command.description == "Display and configure environment variables"
        assert config_command.cybe4sent1neles == ["/cfg"]
        
        # Check subcommands are registered
        expected_subcommands = ["list", "set", "get"]
        assert set(config_command.get_subcommands()) == set(expected_subcommands)
    
    def test_env_vars_structure(self):
        """Test that ENV_VARS has the expected structure."""
        assert isinstance(ENV_VARS, dict)
        assert len(ENV_VARS) > 0
        
        # Check each env var has required fields
        for num, var_info in ENV_VARS.items():
            assert isinstance(num, int)
            assert "name" in var_info
            assert "description" in var_info
            assert "default" in var_info
            
            # Check name is a string
            assert isinstance(var_info["name"], str)
            assert isinstance(var_info["description"], str)
            # default can be None or string
            assert var_info["default"] is None or isinstance(var_info["default"], str)
    
    def test_get_env_var_value_with_set_value(self):
        """Test getting environment variable value when it's set."""
        # Set a test value
        os.environ["cybersentry_MODEL"] = "test-model"

        result = get_env_var_value("cybersentry_MODEL")
        assert result == "test-model"
    
    def test_get_env_var_value_with_default(self):
        """Test getting environment variable value when it's not set (returns default)."""
        # Make sure the variable is not set
        if "cybersentry_MODEL" in os.environ:
            del os.environ["cybersentry_MODEL"]

        result = get_env_var_value("cybersentry_MODEL")
        # Should return the default value from ENV_VARS
        expected_default = None
        for var_info in ENV_VARS.values():
            if var_info["name"] == "cybersentry_MODEL":
                expected_default = var_info["default"]
                break
        
        assert result == (expected_default or "Not set")
    
    def test_get_env_var_value_unknown_variable(self):
        """Test getting value for unknown environment variable."""
        result = get_env_var_value("UNKNOWN_VARIABLE")
        assert result == "Unknown variable"
    
    def test_set_env_var(self):
        """Test setting an environment variable."""
        result = set_env_var("TEST_VAR", "test_value")
        assert result is True
        assert os.environ.get("TEST_VAR") == "test_value"
        
        # Cleanup
        if "TEST_VAR" in os.environ:
            del os.environ["TEST_VAR"]
    
    def test_handle_list(self, config_command):
        """Test listing all environment variables."""
        result = config_command.handle_list([])
        assert result is True
    
    def test_handle_no_args(self, config_command):
        """Test handling when no arguments provided (should default to list)."""
        result = config_command.handle_no_args()
        assert result is True
    
    def test_handle_get_valid_number(self, config_command):
        """Test getting a variable by valid number."""
        # Test with variable number 6 (Cybersentry_MODEL)
        result = config_command.handle_get(["6"])
        assert result is True
    
    def test_handle_get_invalid_number(self, config_command):
        """Test getting a variable by invalid number."""
        result = config_command.handle_get(["999"])
        assert result is False
    
    def test_handle_get_non_integer(self, config_command):
        """Test getting a variable with non-integer number."""
        result = config_command.handle_get(["not_a_number"])
        assert result is False
    
    def test_handle_get_no_args(self, config_command):
        """Test get command with no arguments."""
        result = config_command.handle_get([])
        assert result is False
    
    def test_handle_set_valid_number_and_value(self, config_command):
        """Test setting a variable by valid number and value."""
        # Test with variable number 6 (Cybersentry_MODEL)
        result = config_command.handle_set(["6", "new-test-model"])
        assert result is True
        assert os.environ.get("cybersentry_MODEL") == "new-test-model"
    
    def test_handle_set_invalid_number(self, config_command):
        """Test setting a variable by invalid number."""
        result = config_command.handle_set(["999", "some_value"])
        assert result is False
    
    def test_handle_set_non_integer(self, config_command):
        """Test setting a variable with non-integer number."""
        result = config_command.handle_set(["not_a_number", "some_value"])
        assert result is False
    
    def test_handle_set_no_args(self, config_command):
        """Test set command with no arguments."""
        result = config_command.handle_set([])
        assert result is False
    
    def test_handle_set_insufficient_args(self, config_command):
        """Test set command with insufficient arguments."""
        result = config_command.handle_set(["6"])  # Missing value
        assert result is False
    
    def test_handle_set_with_spaces_in_value(self, config_command):
        """Test setting a variable with value containing spaces."""
        result = config_command.handle_set(["6", "model with spaces"])
        assert result is True
        assert os.environ.get("cybersentry_MODEL") == "model with spaces"
    
    def test_handle_set_empty_value(self, config_command):
        """Test setting a variable with empty value."""
        result = config_command.handle_set(["6", ""])
        assert result is True
        assert os.environ.get("cybersentry_MODEL") == ""
    
    def test_command_base_functionality(self, config_command):
        """Test that the command inherits from base Command properly."""
        assert isinstance(config_command, Command)
        assert config_command.name == "/config"
        assert "/cfg" in config_command.cybe4sent1neles
    
    def test_handle_main_command_routing(self, config_command):
        """Test that main handle method routes to correct subcommands."""
        # Test routing to list (no args defaults to list)
        result1 = config_command.handle([])
        assert result1 is True
        
        # Test routing to list explicitly
        result2 = config_command.handle(["list"])
        assert result2 is True
        
        # Test routing to get
        result3 = config_command.handle(["get", "6"])
        assert result3 is True
        
        # Test routing to set
        result4 = config_command.handle(["set", "6", "test-value"])
        assert result4 is True
        assert os.environ.get("cybersentry_MODEL") == "test-value"
    
    def test_handle_unknown_subcommand(self, config_command):
        """Test handling of unknown subcommands."""
        result = config_command.handle(["unknown_subcommand"])
        assert result is False
    
    def test_specific_env_vars_exist(self):
        """Test that specific important environment variables are defined."""
        important_vars = [
            "cybersentry_MODEL", "cybersentry_DEBUG", "cybersentry_BRIEF", "cybersentry_MAX_TURNS",
            "cybersentry_TRACING", "cybersentry_AGENT_TYPE", "CTF_NAME", "CTF_CHALLENGE"
        ]
        
        defined_var_names = [var_info["name"] for var_info in ENV_VARS.values()]
        
        for var_name in important_vars:
            assert var_name in defined_var_names, f"{var_name} should be defined in ENV_VARS"
    
    def test_env_var_defaults_are_reasonable(self):
        """Test that environment variable defaults are reasonable."""
        # Check that important variables have reasonable defaults
        for var_info in ENV_VARS.values():
            var_name = var_info["name"]
            default = var_info["default"]
            
            # Boolean-like variables should have "true"/"false" defaults or numeric values
            # Exclude interval variables as they have numeric values
            boolean_keywords = ["debug", "brief", "tracing", "memory", "online", "offline", "inside"]
            if (any(keyword in var_name.lower() for keyword in boolean_keywords) and 
                "interval" not in var_name.lower()):
                if default is not None:
                    # Accept boolean strings or numeric values (for debug levels)
                    valid_values = ["true", "false", "0", "1", "2"]
                    assert default.lower() in valid_values, f"{var_name} should have boolean-like or numeric default"
            
            # Numeric variables should have numeric defaults
            if any(keyword in var_name.lower() for keyword in ["turns", "limit", "interval"]):
                if default is not None:
                    # Should be numeric or "inf"
                    try:
                        float(default)
                    except ValueError:
                        assert default == "inf", f"{var_name} should have numeric default or 'inf'"


@pytest.mark.integration
class TestConfigCommandIntegration:
    """Integration tests for config command functionality."""
    
    @pytest.fixture(autouse=True)
    def setup_integration(self):
        """Setup for integration tests."""
        # Store original values
        self.original_env_vars = {}
        for var_info in ENV_VARS.values():
            var_name = var_info["name"]
            if var_name in os.environ:
                self.original_env_vars[var_name] = os.environ[var_name]
        
        yield
        
        # Restore original values
        for var_info in ENV_VARS.values():
            var_name = var_info["name"]
            if var_name in self.original_env_vars:
                os.environ[var_name] = self.original_env_vars[var_name]
            elif var_name in os.environ:
                del os.environ[var_name]
    
    def test_full_config_workflow(self):
        """Test a complete workflow of listing, getting, and setting variables."""
        cmd = ConfigCommand()
        
        # List all variables
        result1 = cmd.handle(["list"])
        assert result1 is True
        
        # Get a specific variable (Cybersentry_MODEL - number 6)
        result2 = cmd.handle(["get", "6"])
        assert result2 is True
        
        # Set the variable to a new value
        result3 = cmd.handle(["set", "6", "integration-test-model"])
        assert result3 is True
        assert os.environ.get("cybersentry_MODEL") == "integration-test-model"
        
        # Get the variable again to verify it changed
        result4 = cmd.handle(["get", "6"])
        assert result4 is True
        
        # Set it back to default (if it had a default)
        for var_info in ENV_VARS.values():
            if var_info["name"] == "cybersentry_MODEL" and var_info["default"]:
                result5 = cmd.handle(["set", "6", var_info["default"]])
                assert result5 is True
                break
    
    def test_multiple_variable_modifications(self):
        """Test modifying multiple variables in sequence."""
        cmd = ConfigCommand()
        
        # Modify several variables
        modifications = [
            ("6", "test-model"),      # cybersentry_MODEL
            ("7", "2"),               # cybersentry_DEBUG
            ("8", "true"),            # cybersentry_BRIEF
        ]
        
        for var_num, value in modifications:
            result = cmd.handle(["set", var_num, value])
            assert result is True
            
            # Verify the change
            get_result = cmd.handle(["get", var_num])
            assert get_result is True
        
        # Verify all changes are still present
        for var_num, expected_value in modifications:
            # Find the variable name
            var_info = ENV_VARS[int(var_num)]
            var_name = var_info["name"]
            assert os.environ.get(var_name) == expected_value
    
    def test_edge_case_values(self):
        """Test setting variables with edge case values."""
        cmd = ConfigCommand()
        
        edge_cases = [
            ("6", ""),                    # Empty string
            ("6", "value with spaces"),   # Spaces
            ("6", "special!@#$%chars"),   # Special characters
            ("6", "very_long_value_that_exceeds_normal_length_expec.y.b.e.r.s.e.n.t.r.y.ns"),  # Long value
            ("7", "0"),                   # Zero
            ("7", "999"),                 # Large number
        ]
        
        for var_num, value in edge_cases:
            result = cmd.handle(["set", var_num, value])
            assert result is True
            
            # Find the variable name and verify
            var_info = ENV_VARS[int(var_num)]
            var_name = var_info["name"]
            assert os.environ.get(var_name) == value
    
    def test_all_env_vars_can_be_set_and_retrieved(self):
        """Test that all defined environment variables can be set and retrieved."""
        cmd = ConfigCommand()
        
        for var_num, var_info in ENV_VARS.items():
            var_name = var_info["name"]
            test_value = f"test_value_for_{var_name}"
            
            # Set the variable
            set_result = cmd.handle(["set", str(var_num), test_value])
            assert set_result is True, f"Failed to set {var_name}"
            
            # Get the variable
            get_result = cmd.handle(["get", str(var_num)])
            assert get_result is True, f"Failed to get {var_name}"
            
            # Verify the value was set correctly
            assert os.environ.get(var_name) == test_value


if __name__ == '__main__':
    pytest.main([__file__, "-v"]) 