"""Tests for log forging/CRLF injection prevention.

AC Round 6 requirement: Verify SDK sanitizes user input before logging
to prevent log forging attacks via newline characters or ANSI escape codes.
"""

import pytest
import logging
from mca_sdk import MCAClient, ValidationError


class TestLogInjectionPrevention:
    """Test SDK prevents log forging via malicious input."""

    @pytest.mark.error_scenario
    def test_metric_name_with_newline_sanitized(self, caplog):
        """Test metric names containing newlines are sanitized/rejected.

        AC Round 6: Prevents log forging like:
        metric.name: valid_metric\\nFAKE ERROR: System compromised
        """
        caplog.set_level(logging.DEBUG)

        client = MCAClient(
            service_name="log-injection-test",
            model_id="mdl-log-001",
            team_name="test-team",
        )

        # Attempt to inject newline in metric attribute
        malicious_value = "valid_value\nFAKE ERROR: System compromised"

        try:
            client.record_prediction(latency=0.1, malicious_attribute=malicious_value)

            # If accepted, verify logs don't contain raw newlines
            for record in caplog.records:
                # Logs should not contain unescaped newlines from user input
                assert (
                    "\nFAKE ERROR:" not in record.message
                ), "SDK must sanitize newlines in logged attributes to prevent log forging"

        except ValidationError:
            # Acceptable: SDK rejects attributes with newlines
            pass

        client.shutdown()

    @pytest.mark.error_scenario
    def test_carriage_return_injection_sanitized(self, caplog):
        """Test carriage return (\\r) characters are sanitized.

        AC Round 6: \\r can overwrite previous log lines in terminals.
        """
        caplog.set_level(logging.DEBUG)

        client = MCAClient(
            service_name="cr-injection-test",
            model_id="mdl-log-002",
            team_name="test-team",
        )

        malicious_value = "valid\rOVERWRITTEN"

        try:
            client.record_prediction(latency=0.1, attribute_with_cr=malicious_value)

            # Verify carriage returns are escaped or removed
            for record in caplog.records:
                assert (
                    "\rOVERWRITTEN" not in record.message
                ), "SDK must sanitize carriage returns to prevent log manipulation"

        except ValidationError:
            pass

        client.shutdown()

    @pytest.mark.error_scenario
    def test_ansi_escape_codes_sanitized(self, caplog):
        """Test ANSI escape codes are sanitized from logs.

        AC Round 6: ANSI codes can hide malicious content or corrupt terminals.
        Example: \\x1b[31mRED TEXT\\x1b[0m
        """
        caplog.set_level(logging.DEBUG)

        client = MCAClient(service_name="ansi-test", model_id="mdl-log-003", team_name="test-team")

        # ANSI escape code for red text
        malicious_value = "\x1b[31mMALICIOUS\x1b[0m"

        try:
            client.record_prediction(latency=0.1, ansi_attribute=malicious_value)

            # Verify ANSI codes are escaped or removed
            for record in caplog.records:
                assert (
                    "\x1b[31m" not in record.message
                ), "SDK must sanitize ANSI escape codes from logs"

        except ValidationError:
            pass

        client.shutdown()

    @pytest.mark.error_scenario
    def test_null_byte_injection_sanitized(self, caplog):
        """Test null bytes (\\x00) are sanitized.

        AC Round 6: Null bytes can truncate logs or poison parsers.
        """
        caplog.set_level(logging.DEBUG)

        client = MCAClient(
            service_name="null-byte-test", model_id="mdl-log-004", team_name="test-team"
        )

        malicious_value = "valid\x00HIDDEN"

        try:
            client.record_prediction(latency=0.1, null_byte_attr=malicious_value)

            # Verify null bytes don't truncate logs
            for record in caplog.records:
                # Should not contain raw null bytes
                assert "\x00" not in record.message, "SDK must sanitize null bytes from logs"

        except ValidationError:
            pass

        client.shutdown()

    @pytest.mark.error_scenario
    def test_tab_characters_handled_safely(self, caplog):
        """Test tab characters in attributes.

        Tabs are generally safe but can cause formatting issues in structured logs.
        """
        caplog.set_level(logging.DEBUG)

        client = MCAClient(service_name="tab-test", model_id="mdl-log-005", team_name="test-team")

        value_with_tabs = "field1\tfield2\tfield3"

        # Tabs should be allowed or consistently handled
        client.record_prediction(latency=0.1, tabbed_field=value_with_tabs)

        client.shutdown()

    @pytest.mark.error_scenario
    def test_multi_line_injection_attack(self, caplog):
        """Test comprehensive multi-line log forging attack.

        AC Round 6: Simulate realistic attack with multiple newlines.
        """
        caplog.set_level(logging.WARNING)

        client = MCAClient(
            service_name="multiline-attack",
            model_id="mdl-log-006",
            team_name="test-team",
        )

        # Realistic log forging attack
        attack_payload = (
            "legitimate_value\n"
            "ERROR: Authentication bypass detected\n"
            "WARNING: Security breach in progress\n"
            "CRITICAL: Database credentials exposed"
        )

        try:
            client.record_prediction(latency=0.1, forged_attribute=attack_payload)

            # Verify forged log lines don't appear
            critical_logs = [r for r in caplog.records if r.levelno >= logging.ERROR]

            # Should not contain forged ERROR/CRITICAL messages from user input
            for record in critical_logs:
                assert (
                    "Authentication bypass" not in record.message
                ), "SDK must prevent log forging via newlines"
                assert "Security breach" not in record.message
                assert "Database credentials exposed" not in record.message

        except ValidationError:
            # Acceptable: Reject attributes with suspicious content
            pass

        client.shutdown()

    @pytest.mark.error_scenario
    def test_prediction_id_with_special_characters(self, caplog):
        """Test prediction_id (user-controlled) is sanitized.

        AC Round 6: prediction_id is directly logged and user-controlled.
        """
        caplog.set_level(logging.INFO)

        client = MCAClient(
            service_name="pred-id-test", model_id="mdl-log-007", team_name="test-team"
        )

        malicious_pred_id = "pred-123\nFAKE: Critical alert"

        try:
            client.record_prediction(latency=0.1, prediction_id=malicious_pred_id)

            # Verify prediction_id doesn't forge logs
            for record in caplog.records:
                if "prediction_id" in record.message or "pred" in record.message:
                    assert (
                        "FAKE: Critical alert" not in record.message
                    ), "SDK must sanitize prediction_id before logging"

        except ValidationError:
            pass

        client.shutdown()

    def test_legitimate_special_characters_preserved(self, caplog):
        """Test legitimate special characters are preserved.

        Verifies sanitization doesn't break valid use cases (JSON, paths, etc.).
        """
        caplog.set_level(logging.INFO)

        client = MCAClient(
            service_name="legitimate-chars",
            model_id="mdl-log-008",
            team_name="test-team",
        )

        # Legitimate special characters that should be allowed
        legitimate_values = {
            "json_string": '{"key": "value"}',
            "file_path": "/var/log/app/model.log",
            "url": "https://example.com/api/v1",
            "hyphenated": "model-v2.0-beta",
            "underscored": "model_output_2024",
        }

        for key, value in legitimate_values.items():
            client.record_prediction(latency=0.1, **{key: value})

        # Should not raise errors for legitimate content
        client.shutdown()


class TestErrorMessageSanitization:
    """Test error messages don't leak unsanitized user input."""

    @pytest.mark.error_scenario
    def test_validation_error_sanitizes_user_input(self):
        """Test ValidationError messages don't include raw malicious input.

        AC Round 6: Error messages that echo user input must be sanitized.
        """
        client = MCAClient(
            service_name="error-msg-test", model_id="mdl-log-009", team_name="test-team"
        )

        malicious_input = "value\n\nCRITICAL: System breach"

        try:
            # Trigger validation error with malicious input
            client.record_prediction(
                latency=-999.0,
                malicious_field=malicious_input,  # Invalid latency
            )
        except ValidationError as e:
            error_msg = str(e)

            # Error message should not contain raw newlines from malicious input
            assert (
                "\n\nCRITICAL: System breach" not in error_msg
            ), "ValidationError messages must sanitize user input"

        client.shutdown()
