"""Tests for capability-based policy validation integration."""

import pytest

from rpipe.capability import (
    Capability,
    CapabilitySet,
    OperationType,
    PolicyError,
)
from rpipe.command_parser import UnsupportedSyntaxError, ParseError
from rpipe.policy_capabilities import validate_command_against_capabilities


class TestPolicyCapabilitiesValidation:
    """Test main validation function."""

    @pytest.fixture
    def read_only_policy(self):
        """Read-only policy for testing."""
        return CapabilitySet([
            Capability(OperationType.READ_FILE, "/var/**/*"),  # Read files in /var and subdirs
            Capability(OperationType.LIST_DIR, "/var/**/*"),  # Can list /var and subdirs
            Capability(OperationType.EXECUTE_BINARY, "cat|grep|ls|tail|head|echo"),  # Added echo for testing
            Capability(OperationType.WRITE_STDOUT, None),
            Capability(OperationType.WRITE_STDERR, None),
        ])

    def test_benign_cat_command_allowed(self, read_only_policy):
        """Simple cat command should be allowed."""
        validate_command_against_capabilities("cat /var/log/syslog", read_only_policy)
        # No exception raised

    def test_benign_grep_command_allowed(self, read_only_policy):
        """Grep command should be allowed."""
        validate_command_against_capabilities("grep error /var/log/syslog", read_only_policy)

    def test_benign_ls_command_allowed(self, read_only_policy):
        """ls command should be allowed."""
        validate_command_against_capabilities("ls -la /var", read_only_policy)

    def test_benign_tail_command_allowed(self, read_only_policy):
        """tail command should be allowed."""
        validate_command_against_capabilities("tail -f /var/log/syslog", read_only_policy)

    def test_forbidden_binary_denied(self, read_only_policy):
        """Binary not in allowlist should be denied."""
        with pytest.raises(PolicyError, match="execute_binary.*not permitted"):
            validate_command_against_capabilities("rm /tmp/file", read_only_policy)

    def test_forbidden_file_read_denied(self, read_only_policy):
        """Reading unauthorized file should be denied."""
        with pytest.raises(PolicyError, match="read_file.*not permitted"):
            validate_command_against_capabilities("cat /etc/passwd", read_only_policy)

    def test_write_operation_denied(self, read_only_policy):
        """Write operations should be denied in read-only policy."""
        with pytest.raises(PolicyError, match="write_file.*not permitted"):
            validate_command_against_capabilities("echo hello > /tmp/file", read_only_policy)

    def test_multiple_input_files_all_checked(self, read_only_policy):
        """All input files should be checked."""
        # This should fail because /etc/shadow is not allowed
        with pytest.raises(PolicyError):
            validate_command_against_capabilities(
                "cat /var/log/syslog /etc/shadow",
                read_only_policy
            )

    def test_output_redirect_checked(self, read_only_policy):
        """Output redirects should be checked for write permission."""
        with pytest.raises(PolicyError):
            validate_command_against_capabilities(
                "cat /var/log/syslog > /tmp/output",
                read_only_policy
            )

    def test_unsupported_syntax_gives_clear_error(self, read_only_policy):
        """Unsupported syntax should give helpful error."""
        with pytest.raises(PolicyError) as exc_info:
            validate_command_against_capabilities(
                "cat /var/log/syslog | grep error",
                read_only_policy
            )
        error_msg = str(exc_info.value)
        assert "unsupported" in error_msg.lower()


class TestPolicyCapabilitiesWriteablePolicy:
    """Test with write-capable policy."""

    @pytest.fixture
    def write_capable_policy(self):
        """Write-capable policy for testing."""
        return CapabilitySet([
            Capability(OperationType.READ_FILE, "/var/log/*"),
            Capability(OperationType.WRITE_FILE, "/tmp/*"),
            Capability(OperationType.EXECUTE_BINARY, "cat|grep|echo|sed"),
            Capability(OperationType.WRITE_STDOUT, None),
        ])

    def test_write_to_allowed_path_allowed(self, write_capable_policy):
        """Writing to allowed path should succeed."""
        validate_command_against_capabilities(
            "echo hello > /tmp/output",
            write_capable_policy
        )

    def test_write_to_forbidden_path_denied(self, write_capable_policy):
        """Writing to forbidden path should be denied."""
        with pytest.raises(PolicyError, match="write_file.*not permitted"):
            validate_command_against_capabilities(
                "echo hello > /var/log/newlog",
                write_capable_policy
            )

    def test_read_and_write_both_checked(self, write_capable_policy):
        """Both read and write operations should be checked."""
        # This should succeed (read from /var/log/*, write to /tmp/*)
        validate_command_against_capabilities(
            "cat /var/log/syslog > /tmp/syslog.txt",
            write_capable_policy
        )

    def test_sed_inplace_edit_if_supported(self, write_capable_policy):
        """If sed is in allowlist, sed commands should work."""
        # This has read + write, both should be checked
        validate_command_against_capabilities(
            "sed 's/error/ERROR/' /var/log/syslog > /tmp/output",
            write_capable_policy
        )


class TestPolicyCapabilitiesRestrictivePolicy:
    """Test with very restrictive policy."""

    @pytest.fixture
    def restrictive_policy(self):
        """Very restrictive policy for testing."""
        return CapabilitySet([
            Capability(OperationType.EXECUTE_BINARY, "echo|date|hostname"),
            Capability(OperationType.WRITE_STDOUT, None),
        ])

    def test_safe_commands_allowed(self, restrictive_policy):
        """Safe system info commands should be allowed."""
        validate_command_against_capabilities("echo hello", restrictive_policy)
        validate_command_against_capabilities("date", restrictive_policy)
        validate_command_against_capabilities("hostname", restrictive_policy)

    def test_file_operations_denied(self, restrictive_policy):
        """File operations should be denied."""
        with pytest.raises(PolicyError):
            validate_command_against_capabilities("cat /var/log/syslog", restrictive_policy)

    def test_cat_command_denied(self, restrictive_policy):
        """cat command should be denied."""
        with pytest.raises(PolicyError, match="execute_binary.*cat"):
            validate_command_against_capabilities("cat /var/log/syslog", restrictive_policy)


class TestPolicyCapabilitiesGlobPatterns:
    """Test glob pattern matching in policies."""

    def test_wildcard_allows_many_files(self):
        """Wildcard pattern should allow many files."""
        policy = CapabilitySet([
            Capability(OperationType.READ_FILE, "/var/log/*"),
            Capability(OperationType.EXECUTE_BINARY, "cat"),
            Capability(OperationType.WRITE_STDOUT, None),
        ])

        # All these should succeed
        validate_command_against_capabilities("cat /var/log/syslog", policy)
        validate_command_against_capabilities("cat /var/log/auth.log", policy)
        validate_command_against_capabilities("cat /var/log/kernel.log", policy)

    def test_wildcard_does_not_allow_subdirs(self):
        """Single wildcard should not allow subdirectories."""
        policy = CapabilitySet([
            Capability(OperationType.READ_FILE, "/var/log/*"),
            Capability(OperationType.EXECUTE_BINARY, "cat"),
            Capability(OperationType.WRITE_STDOUT, None),
        ])

        # This should fail
        with pytest.raises(PolicyError):
            validate_command_against_capabilities(
                "cat /var/log/apache2/access.log",
                policy
            )

    def test_recursive_wildcard_allows_subdirs(self):
        """Double wildcard should allow subdirectories."""
        policy = CapabilitySet([
            Capability(OperationType.READ_FILE, "/var/log/**/*"),
            Capability(OperationType.EXECUTE_BINARY, "cat"),
            Capability(OperationType.WRITE_STDOUT, None),
        ])

        # All these should succeed
        validate_command_against_capabilities("cat /var/log/syslog", policy)
        validate_command_against_capabilities("cat /var/log/apache2/access.log", policy)

    def test_binary_alternatives_with_pipe(self):
        """Binary names separated by | should work as alternatives."""
        policy = CapabilitySet([
            Capability(OperationType.EXECUTE_BINARY, "cat|grep|ls"),
            Capability(OperationType.READ_FILE, "/var/log/**/*"),  # Files in /var/log (includes directory)
            Capability(OperationType.WRITE_STDOUT, None),
        ])

        # All these should succeed
        validate_command_against_capabilities("cat /var/log/syslog", policy)
        validate_command_against_capabilities("grep error /var/log/syslog", policy)
        validate_command_against_capabilities("ls /var/log", policy)

        # This should fail
        with pytest.raises(PolicyError):
            validate_command_against_capabilities("head /var/log/syslog", policy)


class TestPolicyCapabilitiesErrorMessages:
    """Test error message quality."""

    def test_error_names_operation(self):
        """Error message should name the operation."""
        policy = CapabilitySet([
            Capability(OperationType.READ_FILE, "/var/log/*"),
        ])

        with pytest.raises(PolicyError) as exc_info:
            validate_command_against_capabilities("rm /tmp/file", policy)

        error_msg = str(exc_info.value)
        assert "execute_binary" in error_msg or "rm" in error_msg

    def test_error_names_resource(self):
        """Error message should name the resource."""
        policy = CapabilitySet([
            Capability(OperationType.READ_FILE, "/var/log/*"),
        ])

        with pytest.raises(PolicyError) as exc_info:
            validate_command_against_capabilities("cat /etc/passwd", policy)

        error_msg = str(exc_info.value)
        assert "/etc/passwd" in error_msg or "read_file" in error_msg

    def test_error_suggests_available_capabilities(self):
        """Error message should suggest what's available."""
        policy = CapabilitySet([
            Capability(OperationType.READ_FILE, "/var/log/*"),
            Capability(OperationType.EXECUTE_BINARY, "cat|grep"),
        ])

        with pytest.raises(PolicyError) as exc_info:
            validate_command_against_capabilities("rm /tmp/file", policy)

        error_msg = str(exc_info.value)
        # Should mention available capabilities
        assert "read_file" in error_msg or "Available" in error_msg or "capabilities" in error_msg.lower()


class TestPolicyCapabilitiesEdgeCases:
    """Test edge cases and special scenarios."""

    def test_command_with_no_input_files(self):
        """Command with no file arguments should work."""
        policy = CapabilitySet([
            Capability(OperationType.EXECUTE_BINARY, "echo"),
            Capability(OperationType.WRITE_STDOUT, None),
        ])

        validate_command_against_capabilities("echo hello world", policy)

    def test_command_with_multiple_arguments(self):
        """Command with many arguments should work."""
        policy = CapabilitySet([
            Capability(OperationType.EXECUTE_BINARY, "grep"),
            Capability(OperationType.READ_FILE, "/var/log/*"),
            Capability(OperationType.WRITE_STDOUT, None),
        ])

        validate_command_against_capabilities(
            "grep -i error /var/log/syslog",
            policy
        )

    def test_empty_policy_denies_everything(self):
        """Empty policy should deny all operations."""
        policy = CapabilitySet([])

        with pytest.raises(PolicyError):
            validate_command_against_capabilities("echo hello", policy)

    def test_policy_with_no_patterns_allows_all(self):
        """Policy with capabilities with no patterns should allow all."""
        policy = CapabilitySet([
            Capability(OperationType.EXECUTE_BINARY, None),
            Capability(OperationType.READ_FILE, None),
            Capability(OperationType.WRITE_FILE, None),
            Capability(OperationType.WRITE_STDOUT, None),
        ])

        # Should allow anything
        validate_command_against_capabilities("cat /any/file > /output/file", policy)
        validate_command_against_capabilities("rm /etc/passwd", policy)
