"""Tests for PIIScanner."""

import pytest

from trusys.types import ScanContext, ScanType

# Check if presidio is available
try:
    from presidio_analyzer import AnalyzerEngine
    PRESIDIO_AVAILABLE = True
except ImportError:
    PRESIDIO_AVAILABLE = False

# Skip all tests if presidio is not installed
pytestmark = pytest.mark.skipif(
    not PRESIDIO_AVAILABLE,
    reason="presidio-analyzer not installed. Install with: pip install presidio-analyzer && python -m spacy download en_core_web_lg"
)


class TestPIIScanner:
    """Tests for PIIScanner."""

    def test_detects_phone_number(self):
        """Test that phone numbers are detected."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(
            config={"entities": ["PHONE_NUMBER"]},
            on_fail="ignore",
        )
        context = ScanContext(scan_type=ScanType.INPUT, prompt="My phone number is 212-555-5555")
        result = scanner.scan({"prompt": "My phone number is 212-555-5555"}, context)
        
        assert result.passed is False
        assert len(result.violations) >= 1
        assert any("PHONE_NUMBER" in v.rule for v in result.violations)

    def test_detects_email_address(self):
        """Test that email addresses are detected."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(
            config={"entities": ["EMAIL_ADDRESS"]},
            on_fail="ignore",
        )
        context = ScanContext(scan_type=ScanType.INPUT, prompt="Contact me at john.doe@example.com")
        result = scanner.scan({"prompt": "Contact me at john.doe@example.com"}, context)
        
        assert result.passed is False
        assert len(result.violations) >= 1
        assert any("EMAIL_ADDRESS" in v.rule for v in result.violations)

    def test_no_pii_passes(self):
        """Test that content without PII passes."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(
            config={"entities": ["PHONE_NUMBER", "EMAIL_ADDRESS"]},
        )
        context = ScanContext(scan_type=ScanType.INPUT, prompt="This is a clean message with no PII")
        result = scanner.scan({"prompt": "This is a clean message with no PII"}, context)
        
        assert result.passed is True
        assert len(result.violations) == 0

    def test_multiple_entities(self):
        """Test detection of multiple entity types."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(
            config={"entities": ["PHONE_NUMBER", "EMAIL_ADDRESS"]},
            on_fail="ignore",
        )
        context = ScanContext(
            scan_type=ScanType.INPUT,
            prompt="Call me at 555-123-4567 or email test@example.com"
        )
        result = scanner.scan(
            {"prompt": "Call me at 555-123-4567 or email test@example.com"},
            context
        )
        
        assert result.passed is False
        assert len(result.violations) >= 2

    def test_default_entities(self):
        """Test that default entities work without explicit configuration."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(on_fail="ignore")
        context = ScanContext(scan_type=ScanType.INPUT, prompt="My SSN is 123-45-6789")
        result = scanner.scan({"prompt": "My SSN is 123-45-6789"}, context)
        
        # Should detect SSN with default entities
        assert result.passed is False

    def test_violation_spans(self):
        """Test that violations include span information."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(
            config={"entities": ["EMAIL_ADDRESS"]},
            on_fail="ignore",
        )
        text = "Contact: user@example.com"
        context = ScanContext(scan_type=ScanType.INPUT, prompt=text)
        result = scanner.scan({"prompt": text}, context)
        
        assert result.passed is False
        assert result.violations[0].span is not None
        start, end = result.violations[0].span
        assert "user@example.com" in text[start:end]

    def test_violation_metadata(self):
        """Test that violations include entity type and confidence in metadata."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(
            config={"entities": ["PHONE_NUMBER"]},
            on_fail="ignore",
        )
        context = ScanContext(scan_type=ScanType.INPUT, prompt="Call 212-555-5555")
        result = scanner.scan({"prompt": "Call 212-555-5555"}, context)
        
        assert result.violations[0].metadata.get("entity_type") == "PHONE_NUMBER"
        assert "confidence" in result.violations[0].metadata
        assert result.violations[0].metadata.get("confidence") >= 0.5

    def test_suggested_fix_masks_pii(self):
        """Test that suggested_fix masks PII with entity type markers."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(
            config={"entities": ["EMAIL_ADDRESS"]},
            on_fail="ignore",
        )
        context = ScanContext(scan_type=ScanType.INPUT, prompt="Email: test@example.com")
        result = scanner.scan({"prompt": "Email: test@example.com"}, context)
        
        assert result.passed is False
        assert result.suggested_fix is not None
        assert "[EMAIL_ADDRESS]" in result.suggested_fix
        assert "test@example.com" not in result.suggested_fix

    def test_score_threshold(self):
        """Test that score_threshold filters low-confidence results."""
        from trusys.scanners.builtin import PIIScanner
        
        # High threshold - might filter some results
        scanner_high = PIIScanner(
            config={"entities": ["PERSON"], "score_threshold": 0.9},
            on_fail="ignore",
        )
        
        # Low threshold - should catch more
        scanner_low = PIIScanner(
            config={"entities": ["PERSON"], "score_threshold": 0.3},
            on_fail="ignore",
        )
        
        context = ScanContext(scan_type=ScanType.INPUT, prompt="John mentioned it yesterday")
        result_high = scanner_high.scan({"prompt": "John mentioned it yesterday"}, context)
        result_low = scanner_low.scan({"prompt": "John mentioned it yesterday"}, context)
        
        # Lower threshold should detect at least as many as higher threshold
        assert len(result_low.violations) >= len(result_high.violations)

    def test_output_scan_type(self):
        """Test scanning output (response) content."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(
            config={"entities": ["PHONE_NUMBER"]},
            on_fail="ignore",
        )
        context = ScanContext(
            scan_type=ScanType.OUTPUT,
            prompt="What's John's number?",
            response="His phone number is 555-123-4567"
        )
        result = scanner.scan(
            {"prompt": "What's John's number?", "response": "His phone number is 555-123-4567"},
            context
        )
        
        assert result.passed is False
        assert result.scan_type == ScanType.OUTPUT

    def test_empty_content_passes(self):
        """Test that empty content passes without errors."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(config={"entities": ["PHONE_NUMBER"]})
        context = ScanContext(scan_type=ScanType.INPUT, prompt="")
        result = scanner.scan({"prompt": ""}, context)
        
        assert result.passed is True
        assert len(result.violations) == 0

    def test_invalid_entities_list(self):
        """Test that non-list entities raises ValueError."""
        from trusys.scanners.builtin import PIIScanner
        
        with pytest.raises(ValueError) as exc_info:
            PIIScanner(config={"entities": "PHONE_NUMBER"})
        assert "entities" in str(exc_info.value).lower()

    def test_empty_entities_list(self):
        """Test that empty entities list raises ValueError."""
        from trusys.scanners.builtin import PIIScanner
        
        with pytest.raises(ValueError) as exc_info:
            PIIScanner(config={"entities": []})
        assert "entities" in str(exc_info.value).lower()

    def test_invalid_score_threshold(self):
        """Test that invalid score_threshold raises ValueError."""
        from trusys.scanners.builtin import PIIScanner
        
        with pytest.raises(ValueError) as exc_info:
            PIIScanner(config={"score_threshold": 1.5})
        assert "score_threshold" in str(exc_info.value).lower()
        
        with pytest.raises(ValueError) as exc_info:
            PIIScanner(config={"score_threshold": -0.1})
        assert "score_threshold" in str(exc_info.value).lower()

    def test_get_config(self):
        """Test that get_config includes PII-specific parameters."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(
            config={
                "entities": ["PHONE_NUMBER", "EMAIL_ADDRESS"],
                "language": "en",
                "score_threshold": 0.7,
            }
        )
        config = scanner.get_config()
        
        assert config["entities"] == ["PHONE_NUMBER", "EMAIL_ADDRESS"]
        assert config["language"] == "en"
        assert config["score_threshold"] == 0.7

    def test_metadata_includes_pii_found(self):
        """Test that result metadata includes pii_found details."""
        from trusys.scanners.builtin import PIIScanner
        
        scanner = PIIScanner(
            config={"entities": ["PHONE_NUMBER"]},
            on_fail="ignore",
        )
        context = ScanContext(scan_type=ScanType.INPUT, prompt="Call 212-555-5555")
        result = scanner.scan({"prompt": "Call 212-555-5555"}, context)
        
        assert "pii_found" in result.metadata
        assert len(result.metadata["pii_found"]) >= 1
        assert result.metadata["pii_found"][0]["entity_type"] == "PHONE_NUMBER"
        assert "start" in result.metadata["pii_found"][0]
        assert "end" in result.metadata["pii_found"][0]
        assert "score" in result.metadata["pii_found"][0]


class TestPIIScannerWithGuard:
    """Tests for PIIScanner integrated with TruGuard."""

    def test_with_input_guard(self):
        """Test PII scanner with input guard."""
        from trusys import TruGuard
        from trusys.scanners.builtin import PIIScanner
        
        guard = TruGuard(type="input")
        guard.add_scanners([
            PIIScanner(
                config={"entities": ["PHONE_NUMBER", "EMAIL_ADDRESS"]},
                on_fail="ignore",
            )
        ])
        
        result = guard.scan(content={"prompt": "My email is test@example.com"})
        assert result.passed is False
        
        result = guard.scan(content={"prompt": "Hello, how are you?"})
        assert result.passed is True

    def test_with_output_guard(self):
        """Test PII scanner with output guard."""
        from trusys import TruGuard
        from trusys.scanners.builtin import PIIScanner
        
        guard = TruGuard(type="output")
        guard.add_scanners([
            PIIScanner(
                config={"entities": ["PHONE_NUMBER"]},
                on_fail="ignore",
            )
        ])
        
        result = guard.scan(content={
            "prompt": "What's the phone number?",
            "response": "The number is 555-123-4567"
        })
        assert result.passed is False
        
        result = guard.scan(content={
            "prompt": "What's the phone number?",
            "response": "I don't have that information"
        })
        assert result.passed is True
