"""Tests for policy configuration loading."""

import json
import tempfile
from pathlib import Path

import pytest

from rpipe.capability import OperationType
from rpipe.policy_config import PolicyConfigLoader, PolicyConfigError


class TestPolicyConfigLoaderBasic:
    """Test basic policy configuration loading."""

    def test_load_from_json(self):
        """Load policies from JSON file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(
                {
                    "policies": {
                        "test_policy": {
                            "capabilities": [
                                {"operation": "read_file", "resource": "/var/log/**/*"},
                                {"operation": "execute_binary", "resource": "cat|grep"},
                                {"operation": "write_stdout", "resource": None},
                            ]
                        }
                    }
                },
                f,
            )
            f.flush()

            try:
                policies = PolicyConfigLoader.load_from_file(f.name)
                assert "test_policy" in policies
                assert len(policies["test_policy"].capabilities) == 3
            finally:
                Path(f.name).unlink()

    def test_load_from_yaml(self):
        """Load policies from YAML file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write(
                """
policies:
  test_policy:
    capabilities:
      - operation: read_file
        resource: /var/log/**/*
      - operation: execute_binary
        resource: cat|grep
      - operation: write_stdout
        resource: null
"""
            )
            f.flush()

            try:
                pytest.importorskip("yaml")
                policies = PolicyConfigLoader.load_from_file(f.name)
                assert "test_policy" in policies
                assert len(policies["test_policy"].capabilities) == 3
            finally:
                Path(f.name).unlink()

    def test_yaml_not_installed(self):
        """Error when YAML requested but pyyaml not installed."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("policies: {}")
            f.flush()

            try:
                # Mock missing yaml module by patching importorskip behavior
                # This test will be skipped if pyyaml is installed
                pytest.importorskip("yaml", minversion=None)
                # If we get here, yaml is installed, so skip this test
                pytest.skip("pyyaml is installed")
            finally:
                Path(f.name).unlink()

    def test_file_not_found(self):
        """Error when policy file not found."""
        with pytest.raises(PolicyConfigError, match="not found"):
            PolicyConfigLoader.load_from_file("/nonexistent/path.json")

    def test_unsupported_format(self):
        """Error on unsupported file format."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
            f.write("invalid")
            f.flush()

            try:
                with pytest.raises(PolicyConfigError, match="Unsupported file format"):
                    PolicyConfigLoader.load_from_file(f.name)
            finally:
                Path(f.name).unlink()


class TestPolicyConfigParsing:
    """Test policy configuration parsing."""

    def test_parse_builtin_reference(self):
        """Policy can reference built-in policy."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(
                {
                    "policies": {
                        "custom_read_only": "read_only",
                    }
                },
                f,
            )
            f.flush()

            try:
                policies = PolicyConfigLoader.load_from_file(f.name)
                assert "custom_read_only" in policies
            finally:
                Path(f.name).unlink()

    def test_invalid_builtin_reference(self):
        """Error on unknown built-in policy reference."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(
                {
                    "policies": {
                        "bad_policy": "nonexistent_builtin",
                    }
                },
                f,
            )
            f.flush()

            try:
                with pytest.raises(PolicyConfigError, match="Unknown built-in policy"):
                    PolicyConfigLoader.load_from_file(f.name)
            finally:
                Path(f.name).unlink()

    def test_multiple_policies(self):
        """Load multiple policies from one file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(
                {
                    "policies": {
                        "read_only": "read_only",
                        "write_capable": "write_capable",
                        "custom": {
                            "capabilities": [
                                {"operation": "read_file", "resource": "/tmp/**/*"},
                                {"operation": "write_stdout", "resource": None},
                            ]
                        },
                    }
                },
                f,
            )
            f.flush()

            try:
                policies = PolicyConfigLoader.load_from_file(f.name)
                assert len(policies) == 3
                assert "read_only" in policies
                assert "write_capable" in policies
                assert "custom" in policies
            finally:
                Path(f.name).unlink()

    def test_capability_operations(self):
        """Parse all capability operation types."""
        capabilities_data = []
        for op in OperationType:
            capabilities_data.append({
                "operation": op.name.lower(),
                "resource": "/test/*"
            })

        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(
                {
                    "policies": {
                        "all_ops": {
                            "capabilities": capabilities_data
                        }
                    }
                },
                f,
            )
            f.flush()

            try:
                policies = PolicyConfigLoader.load_from_file(f.name)
                policy = policies["all_ops"]
                assert len(policy.capabilities) == len(OperationType)
            finally:
                Path(f.name).unlink()


class TestPolicyConfigValidation:
    """Test configuration validation."""

    def test_missing_policies_key(self):
        """Error when 'policies' key missing."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump({"no_policies": {}}, f)
            f.flush()

            try:
                with pytest.raises(PolicyConfigError, match="'policies' key"):
                    PolicyConfigLoader.load_from_file(f.name)
            finally:
                Path(f.name).unlink()

    def test_not_a_dict(self):
        """Error when root is not a dict."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(["not", "a", "dict"], f)
            f.flush()

            try:
                with pytest.raises(PolicyConfigError, match="must be a dictionary"):
                    PolicyConfigLoader.load_from_file(f.name)
            finally:
                Path(f.name).unlink()

    def test_capabilities_not_list(self):
        """Error when capabilities is not a list."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(
                {
                    "policies": {
                        "bad": {
                            "capabilities": "not a list"
                        }
                    }
                },
                f,
            )
            f.flush()

            try:
                with pytest.raises(PolicyConfigError, match="must be a list"):
                    PolicyConfigLoader.load_from_file(f.name)
            finally:
                Path(f.name).unlink()

    def test_capability_missing_operation(self):
        """Error when capability missing operation."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(
                {
                    "policies": {
                        "bad": {
                            "capabilities": [
                                {"resource": "/test/*"}  # Missing operation
                            ]
                        }
                    }
                },
                f,
            )
            f.flush()

            try:
                with pytest.raises(PolicyConfigError, match="missing 'operation'"):
                    PolicyConfigLoader.load_from_file(f.name)
            finally:
                Path(f.name).unlink()

    def test_invalid_operation(self):
        """Error on invalid operation type."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(
                {
                    "policies": {
                        "bad": {
                            "capabilities": [
                                {"operation": "invalid_op", "resource": "/test/*"}
                            ]
                        }
                    }
                },
                f,
            )
            f.flush()

            try:
                with pytest.raises(PolicyConfigError, match="Invalid operation"):
                    PolicyConfigLoader.load_from_file(f.name)
            finally:
                Path(f.name).unlink()

    def test_empty_capabilities(self):
        """Error when policy has no capabilities."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(
                {
                    "policies": {
                        "empty": {
                            "capabilities": []
                        }
                    }
                },
                f,
            )
            f.flush()

            try:
                with pytest.raises(PolicyConfigError, match="no capabilities"):
                    PolicyConfigLoader.load_from_file(f.name)
            finally:
                Path(f.name).unlink()


class TestLoadPolicy:
    """Test load_policy() convenience function."""

    def test_load_builtin_policy(self):
        """Load built-in policy without file."""
        policy = PolicyConfigLoader.load_policy("read_only")
        assert policy is not None
        assert len(policy.capabilities) > 0

    def test_load_custom_policy(self):
        """Load custom policy from file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(
                {
                    "policies": {
                        "my_policy": {
                            "capabilities": [
                                {"operation": "read_file", "resource": "/home/**/*"},
                                {"operation": "write_stdout", "resource": None},
                            ]
                        }
                    }
                },
                f,
            )
            f.flush()

            try:
                policy = PolicyConfigLoader.load_policy("my_policy", f.name)
                assert len(policy.capabilities) == 2
            finally:
                Path(f.name).unlink()

    def test_policy_not_found(self):
        """Error when policy not found."""
        with pytest.raises(PolicyConfigError, match="not found"):
            PolicyConfigLoader.load_policy("nonexistent")

    def test_invalid_builtin_name(self):
        """Error on invalid built-in policy name."""
        with pytest.raises(PolicyConfigError, match="not found"):
            PolicyConfigLoader.load_policy("nonexistent_builtin")


class TestEnvironmentVariables:
    """Test environment variable expansion in paths."""

    def test_expand_env_in_path(self):
        """Expand environment variables in file path."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create a policy file
            policy_file = Path(tmpdir) / "policy.json"
            policy_file.write_text(
                json.dumps(
                    {
                        "policies": {
                            "test": {
                                "capabilities": [
                                    {"operation": "read_file", "resource": "/tmp/*"},
                                    {"operation": "write_stdout", "resource": None},
                                ]
                            }
                        }
                    }
                )
            )

            # Set environment variable
            import os

            old_value = os.environ.get("TEST_POLICY_DIR")
            try:
                os.environ["TEST_POLICY_DIR"] = tmpdir
                path_with_env = "${TEST_POLICY_DIR}/policy.json"
                policies = PolicyConfigLoader.load_from_file(path_with_env)
                assert "test" in policies
            finally:
                if old_value is None:
                    del os.environ["TEST_POLICY_DIR"]
                else:
                    os.environ["TEST_POLICY_DIR"] = old_value
