"""
Dependency Security Tests

Tests for dependency vulnerabilities, outdated packages,
and supply chain security.

Test Coverage:
- TC-9.1: Known Vulnerability Scan
- TC-9.2: Outdated Dependencies
- TC-9.3: License Compliance
"""

import pytest
import subprocess
import json
from pathlib import Path


class TestDependencyVulnerabilities:
    """Test for known vulnerabilities in dependencies."""
    
    def test_no_critical_vulnerabilities(self):
        """TC-9.1.1: Scan for critical vulnerabilities."""
        try:
            # Run pip-audit to check for known vulnerabilities
            result = subprocess.run(
                ["pip-audit", "--format", "json", "--desc"],
                capture_output=True,
                text=True,
                timeout=60
            )
            
            if result.returncode == 0:
                # No vulnerabilities found
                return
            
            # Parse output
            if result.stdout:
                try:
                    vulns = json.loads(result.stdout)
                    
                    # Check for critical vulnerabilities
                    critical_vulns = [
                        v for v in vulns.get("vulnerabilities", [])
                        if "critical" in str(v).lower()
                    ]
                    
                    assert len(critical_vulns) == 0, \
                        f"Critical vulnerabilities found: {critical_vulns}"
                except json.JSONDecodeError:
                    # If pip-audit not installed, skip test
                    pytest.skip("pip-audit not installed")
            
        except FileNotFoundError:
            pytest.skip("pip-audit not installed")
        except subprocess.TimeoutExpired:
            pytest.fail("pip-audit timed out")
    
    def test_no_high_vulnerabilities(self):
        """TC-9.1.1: Check for high severity vulnerabilities."""
        try:
            result = subprocess.run(
                ["pip-audit", "--format", "json"],
                capture_output=True,
                text=True,
                timeout=60
            )
            
            if result.returncode == 0:
                return
            
            if result.stdout:
                try:
                    vulns = json.loads(result.stdout)
                    
                    high_vulns = [
                        v for v in vulns.get("vulnerabilities", [])
                        if "high" in str(v).lower()
                    ]
                    
                    # Allow some high vulns if they're being addressed
                    # But warn about them
                    if high_vulns:
                        pytest.skip(f"High vulnerabilities found (may be acceptable): {len(high_vulns)}")
                        
                except json.JSONDecodeError:
                    pytest.skip("Could not parse pip-audit output")
        
        except FileNotFoundError:
            pytest.skip("pip-audit not installed")
        except subprocess.TimeoutExpired:
            pytest.fail("pip-audit timed out")


class TestOutdatedDependencies:
    """Test for outdated dependencies."""
    
    def test_dependencies_reasonably_current(self):
        """TC-9.1.2: Check that dependencies are not severely outdated."""
        try:
            # Check for outdated packages
            result = subprocess.run(
                ["pip", "list", "--outdated", "--format", "json"],
                capture_output=True,
                text=True,
                timeout=30
            )
            
            if result.stdout:
                outdated = json.loads(result.stdout)
                
                # Check for severely outdated packages (>1 year old)
                # This is a rough heuristic
                if len(outdated) > 20:  # Arbitrary threshold
                    pytest.skip(f"Many outdated packages: {len(outdated)}")
                
        except FileNotFoundError:
            pytest.skip("pip not available")
        except subprocess.TimeoutExpired:
            pytest.skip("pip list timed out - skipping in CI environment")
        except json.JSONDecodeError:
            pytest.skip("Could not parse pip output")


class TestDependencyPinning:
    """Test that dependencies are properly pinned."""
    
    def test_dependencies_pinned_in_pyproject(self):
        """Verify dependencies have version constraints."""
        pyproject = Path("pyproject.toml")
        
        if not pyproject.exists():
            pytest.skip("pyproject.toml not found")
        
        content = pyproject.read_text()
        
        # Check that dependencies have version specifiers
        # Look for dependencies section
        if "dependencies" in content:
            # Should have version specifiers like >=, ==, ~=
            # This is a basic check
            assert ">=" in content or "==" in content or "~=" in content, \
                "Dependencies should have version constraints"


class TestLicenseCompliance:
    """Test dependency license compliance."""
    
    @pytest.mark.skip(reason="License checking tool not yet integrated")
    def test_license_compatibility(self):
        """TC-9.1.3: Check license compatibility."""
        # Would use a tool like pip-licenses to check
        # Verify all dependencies have compatible licenses (MIT, Apache, BSD)
        # Flag GPL or restrictive licenses
        pass
    
    @pytest.mark.skip(reason="License checking tool not yet integrated")
    def test_no_restrictive_licenses(self):
        """Verify no GPL or other restrictive licenses."""
        # Check that no dependencies have restrictive licenses
        # that would affect Canvas MCP's MIT license
        pass


class TestSupplyChainSecurity:
    """Test supply chain security."""
    
    def test_dependencies_from_pypi(self):
        """Verify dependencies are from trusted PyPI."""
        # Check that all dependencies come from official PyPI
        # Not from custom indexes or git repos (unless necessary)
        pyproject = Path("pyproject.toml")
        
        if pyproject.exists():
            content = pyproject.read_text()
            
            # Check for git+https dependencies (supply chain risk)
            assert "git+https" not in content or "# trusted" in content, \
                "Git dependencies increase supply chain risk"
    
    def test_no_suspicious_dependencies(self):
        """Check for typosquatting or suspicious package names."""
        pyproject = Path("pyproject.toml")
        
        if pyproject.exists():
            content = pyproject.read_text()
            
            # Check for common typosquatting targets
            suspicious_patterns = [
                "requsets",  # requests typo
                "urlib",     # urllib typo
                "pythno",    # python typo
            ]
            
            for pattern in suspicious_patterns:
                assert pattern not in content.lower(), \
                    f"Suspicious package name found: {pattern}"


class TestDependencyIntegrity:
    """Test dependency integrity and verification."""
    
    def test_lockfile_exists(self):
        """Verify lockfile exists for reproducible builds."""
        # Check for requirements.txt or poetry.lock or similar
        lockfiles = [
            "requirements.txt",
            "poetry.lock",
            "Pipfile.lock",
        ]
        
        # At least one should exist for reproducible builds
        has_lockfile = any(Path(f).exists() for f in lockfiles)
        
        # Or using pip-tools with requirements.txt
        # Canvas MCP uses pyproject.toml with pinned versions
    
    @pytest.mark.skip(reason="Hash checking not yet implemented")
    def test_dependency_hashes(self):
        """Verify dependencies with cryptographic hashes."""
        # Would check that dependencies are verified with hashes
        # Using --require-hashes flag with pip
        pass


class TestDevelopmentDependencies:
    """Test development dependencies."""
    
    def test_dev_dependencies_separate(self):
        """Verify dev dependencies are separate from production."""
        pyproject = Path("pyproject.toml")
        
        if pyproject.exists():
            content = pyproject.read_text()
            
            # Should have separate dev dependencies
            assert "dev" in content or "optional" in content, \
                "Development dependencies should be separate"
    
    def test_no_dev_tools_in_production(self):
        """Verify development tools not required in production."""
        # Check that pytest, black, ruff, etc. are optional
        pyproject = Path("pyproject.toml")
        
        if pyproject.exists():
            content = pyproject.read_text()
            
            # Dev tools should be in optional-dependencies
            if "pytest" in content:
                # Should be in dev section
                assert "[project.optional-dependencies]" in content


class TestSecurityAdvisories:
    """Test for security advisories."""
    
    @pytest.mark.skip(reason="Security advisory monitoring not automated")
    def test_security_advisories_monitored(self):
        """Verify security advisories are monitored."""
        # Would check that GitHub Dependabot or similar is enabled
        # Or that there's a process for monitoring advisories
        pass


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