"""Tests for plugin security validation."""

import pytest
from pathlib import Path

from cleave.tui.backends.security import (
    PluginValidator,
    SecurityReport,
    validate_plugin,
    scan_plugin_directory,
)


@pytest.fixture
def safe_plugin(tmp_path):
    """Create a safe plugin."""
    plugin = tmp_path / "safe_plugin.py"
    plugin.write_text('''
"""A safe plugin."""
from cleave.tui.backends.base import Backend

class SafeBackend(Backend):
    pass

__plugin_name__ = "safe"
__plugin_version__ = "1.0.0"
__plugin_backend__ = SafeBackend
''')
    return plugin


@pytest.fixture
def plugin_with_network(tmp_path):
    """Create a plugin that uses network operations."""
    plugin = tmp_path / "network_plugin.py"
    plugin.write_text('''
"""Plugin with network operations."""
import httpx
from cleave.tui.backends.base import Backend

class NetworkBackend(Backend):
    async def query(self, prompt):
        async with httpx.AsyncClient() as client:
            response = await client.post("https://api.example.com", json={"prompt": prompt})
        yield Message(role="assistant", content=response.text)

__plugin_name__ = "network"
__plugin_version__ = "1.0.0"
__plugin_backend__ = NetworkBackend
''')
    return plugin


@pytest.fixture
def plugin_with_dangerous_imports(tmp_path):
    """Create a plugin with dangerous imports."""
    plugin = tmp_path / "dangerous_plugin.py"
    plugin.write_text('''
"""Plugin with dangerous imports."""
import subprocess
from cleave.tui.backends.base import Backend

class DangerousBackend(Backend):
    def connect(self):
        subprocess.run(["echo", "hello"])

__plugin_name__ = "dangerous"
__plugin_version__ = "1.0.0"
__plugin_backend__ = DangerousBackend
''')
    return plugin


@pytest.fixture
def plugin_with_eval(tmp_path):
    """Create a plugin that uses eval."""
    plugin = tmp_path / "eval_plugin.py"
    plugin.write_text('''
"""Plugin with eval."""
from cleave.tui.backends.base import Backend

class EvalBackend(Backend):
    def query(self, prompt):
        result = eval(prompt)
        return result

__plugin_name__ = "eval"
__plugin_version__ = "1.0.0"
__plugin_backend__ = EvalBackend
''')
    return plugin


@pytest.fixture
def plugin_missing_metadata(tmp_path):
    """Create a plugin missing required metadata."""
    plugin = tmp_path / "incomplete_plugin.py"
    plugin.write_text('''
"""Incomplete plugin."""
from cleave.tui.backends.base import Backend

class IncompleteBackend(Backend):
    pass

# Missing metadata
''')
    return plugin


@pytest.fixture
def plugin_with_file_ops(tmp_path):
    """Create a plugin with file operations."""
    plugin = tmp_path / "file_plugin.py"
    plugin.write_text('''
"""Plugin with file operations."""
from pathlib import Path
from cleave.tui.backends.base import Backend

class FileBackend(Backend):
    def connect(self):
        cache = Path.home() / ".cleave" / "cache.txt"
        cache.write_text("cached data")

__plugin_name__ = "file"
__plugin_version__ = "1.0.0"
__plugin_backend__ = FileBackend
''')
    return plugin


class TestPluginValidator:
    """Tests for PluginValidator."""

    def test_safe_plugin(self, safe_plugin):
        """Test validation of a safe plugin."""
        validator = PluginValidator()
        report = validator.validate_plugin(safe_plugin)

        assert report.safe is True
        assert len(report.errors) == 0
        assert len(report.warnings) == 0

    def test_plugin_with_network(self, plugin_with_network):
        """Test plugin with network operations gets info check."""
        validator = PluginValidator()
        report = validator.validate_plugin(plugin_with_network)

        assert report.safe is True  # Network is allowed, just info
        info_checks = [c for c in report.checks if c.severity == "info"]
        assert any("network" in c.message.lower() for c in info_checks)

    def test_plugin_with_dangerous_imports(self, plugin_with_dangerous_imports):
        """Test plugin with dangerous imports gets warning."""
        validator = PluginValidator()
        report = validator.validate_plugin(plugin_with_dangerous_imports)

        assert report.safe is True  # Warnings don't fail validation
        assert len(report.warnings) > 0
        assert any("subprocess" in w for w in report.warnings)

    def test_plugin_with_eval(self, plugin_with_eval):
        """Test plugin with eval gets error."""
        validator = PluginValidator()
        report = validator.validate_plugin(plugin_with_eval)

        assert report.safe is False
        assert len(report.errors) > 0
        assert any("eval" in e.lower() for e in report.errors)

    def test_plugin_missing_metadata(self, plugin_missing_metadata):
        """Test plugin missing metadata gets errors."""
        validator = PluginValidator()
        report = validator.validate_plugin(plugin_missing_metadata)

        assert report.safe is False
        assert len(report.errors) >= 3  # Missing 3 required metadata fields
        assert any("__plugin_name__" in e for e in report.errors)
        assert any("__plugin_version__" in e for e in report.errors)
        assert any("__plugin_backend__" in e for e in report.errors)

    def test_plugin_with_file_ops(self, plugin_with_file_ops):
        """Test plugin with file operations gets info check."""
        validator = PluginValidator()
        report = validator.validate_plugin(plugin_with_file_ops)

        assert report.safe is True
        info_checks = [c for c in report.checks if c.severity == "info"]
        assert any("file" in c.message.lower() for c in info_checks)

    def test_nonexistent_file(self, tmp_path):
        """Test validation of non-existent file."""
        validator = PluginValidator()
        report = validator.validate_plugin(tmp_path / "nonexistent.py")

        assert report.safe is False
        assert len(report.errors) > 0

    def test_syntax_error(self, tmp_path):
        """Test plugin with syntax error."""
        plugin = tmp_path / "broken.py"
        plugin.write_text("def broken( syntax error")

        validator = PluginValidator()
        report = validator.validate_plugin(plugin)

        assert report.safe is False
        assert len(report.errors) > 0
        assert any("syntax" in e.lower() for e in report.errors)


class TestValidatePlugin:
    """Tests for validate_plugin function."""

    def test_validate_safe_plugin(self, safe_plugin):
        """Test validating a safe plugin."""
        report = validate_plugin(safe_plugin)
        assert isinstance(report, SecurityReport)
        assert report.safe is True

    def test_validate_dangerous_plugin(self, plugin_with_eval):
        """Test validating a dangerous plugin."""
        report = validate_plugin(plugin_with_eval)
        assert isinstance(report, SecurityReport)
        assert report.safe is False


class TestScanDirectory:
    """Tests for scan_plugin_directory."""

    def test_scan_empty_directory(self, tmp_path):
        """Test scanning empty directory."""
        reports = scan_plugin_directory(tmp_path)
        assert len(reports) == 0

    def test_scan_nonexistent_directory(self, tmp_path):
        """Test scanning non-existent directory."""
        reports = scan_plugin_directory(tmp_path / "nonexistent")
        assert len(reports) == 0

    def test_scan_directory_with_plugins(self, tmp_path, safe_plugin, plugin_with_network):
        """Test scanning directory with multiple plugins."""
        # Move plugins to same directory
        plugin_dir = tmp_path / "plugins"
        plugin_dir.mkdir()

        (plugin_dir / "safe.py").write_text(safe_plugin.read_text())
        (plugin_dir / "network.py").write_text(plugin_with_network.read_text())

        reports = scan_plugin_directory(plugin_dir)

        assert len(reports) == 2
        assert "safe" in reports
        assert "network" in reports
        assert reports["safe"].safe is True
        assert reports["network"].safe is True

    def test_scan_ignores_init_files(self, tmp_path):
        """Test that __init__.py files are ignored."""
        plugin_dir = tmp_path / "plugins"
        plugin_dir.mkdir()
        (plugin_dir / "__init__.py").write_text("# Init file")

        reports = scan_plugin_directory(plugin_dir)
        assert len(reports) == 0

    def test_scan_mixed_plugins(self, tmp_path, safe_plugin, plugin_with_eval):
        """Test scanning directory with safe and unsafe plugins."""
        plugin_dir = tmp_path / "plugins"
        plugin_dir.mkdir()

        (plugin_dir / "safe.py").write_text(safe_plugin.read_text())
        (plugin_dir / "unsafe.py").write_text(plugin_with_eval.read_text())

        reports = scan_plugin_directory(plugin_dir)

        assert len(reports) == 2
        assert reports["safe"].safe is True
        assert reports["unsafe"].safe is False


class TestSecurityReport:
    """Tests for SecurityReport."""

    def test_has_warnings(self, plugin_with_dangerous_imports):
        """Test has_warnings property."""
        report = validate_plugin(plugin_with_dangerous_imports)
        assert report.has_warnings is True

    def test_has_errors(self, plugin_with_eval):
        """Test has_errors property."""
        report = validate_plugin(plugin_with_eval)
        assert report.has_errors is True

    def test_no_warnings_or_errors(self, safe_plugin):
        """Test plugin with no warnings or errors."""
        report = validate_plugin(safe_plugin)
        assert report.has_warnings is False
        assert report.has_errors is False
