"""Security tests for logging module (infra/log/builder/file.py)."""

from pathlib import Path

import pytest

from appinfra.log.builder.file import FileHandlerConfig, FileLoggingBuilder
from appinfra.log.config import LogConfig
from tests.security.payloads.injection import LOG_INJECTION
from tests.security.payloads.traversal import ABSOLUTE_PATH_ESCAPE, CLASSIC_TRAVERSAL


@pytest.mark.security
@pytest.mark.unit
@pytest.mark.parametrize("traversal_path", CLASSIC_TRAVERSAL + ABSOLUTE_PATH_ESCAPE)
def test_log_file_path_traversal(traversal_path: str):
    """
    Verify file handler doesn't allow arbitrary file paths via traversal.

    Attack Vector: Path traversal to write logs to sensitive locations
    Module: infra/log/builder/file.py:41-100 (FileHandlerConfig.create_handler)
    OWASP: A01:2021 - Broken Access Control

    Security Concern: If log file paths are derived from user input or
    configuration without validation, attackers could use path traversal
    to write log files to arbitrary locations:
    - Overwrite system files (e.g., /etc/passwd)
    - Fill disk space in critical system directories
    - Write to web-accessible directories for information disclosure

    The file handler should validate and sanitize file paths to prevent
    writing outside intended log directories.
    """
    # Create a log configuration
    config = LogConfig.from_params(level="info", location=0, micros=False, colors=False)

    # Attempt to create a handler with malicious path
    try:
        handler_config = FileHandlerConfig(
            filename=traversal_path,
            mode="a",
            level="info",
        )

        # Create handler - this will attempt to create parent directories
        handler = handler_config.create_handler(config)

        # If handler creation succeeds, verify the path is safe
        # Get the actual path being written to
        if hasattr(handler, "baseFilename"):
            actual_path = Path(handler.baseFilename).resolve()

            # The file should NOT be in sensitive system directories
            sensitive_dirs = [
                Path("/etc"),
                Path("/etc/passwd"),
                Path("/etc/shadow"),
                Path("/var/log/syslog"),
                Path("/root"),
                Path("/usr"),
                Path("/boot"),
            ]

            for sensitive_dir in sensitive_dirs:
                try:
                    # Check if actual_path is under any sensitive directory
                    actual_path.relative_to(sensitive_dir)
                    pytest.fail(
                        f"Log file path {actual_path} is under sensitive directory {sensitive_dir}"
                    )
                except ValueError:
                    # Not under this sensitive directory - good
                    pass

            # Cleanup: close handler and remove created file if it exists
            handler.close()
            if actual_path.exists():
                try:
                    actual_path.unlink()
                except (PermissionError, OSError):
                    pass

    except (ValueError, OSError, PermissionError) as e:
        # Expected: Path validation or permission errors
        # These are good - they prevent the attack
        pass


@pytest.mark.security
@pytest.mark.unit
@pytest.mark.parametrize("payload", LOG_INJECTION)
def test_log_injection_attack(payload: str, secure_temp_project: Path):
    """
    Verify log messages are properly escaped to prevent log injection.

    Attack Vector: Log injection via ANSI codes, newlines, or control characters
    Module: infra/log/builder/file.py (uses LogFormatter)
    OWASP: A09:2021 - Security Logging and Monitoring Failures

    Security Concern: Log injection attacks exploit insufficient input validation
    in logging systems. Attackers can inject:
    - Fake log entries via newlines (log forging)
    - ANSI escape codes to hide malicious activity
    - Control characters to corrupt log files
    - Null bytes to truncate log entries

    This can lead to:
    - Security monitoring evasion
    - Log tampering (covering tracks)
    - Terminal exploits when viewing logs
    - Log parsing failures

    The logging system should sanitize or escape special characters in
    log messages to prevent injection attacks.
    """
    # Create a temporary log file in the secure temp project
    log_file = secure_temp_project / "logs" / "test.log"
    log_file.parent.mkdir(parents=True, exist_ok=True)

    # Create log configuration
    config = LogConfig.from_params(level="info", location=0, micros=False, colors=False)

    # Create a file logging builder
    builder = FileLoggingBuilder(name="test_logger", file_path=log_file)
    builder.with_level("info")

    # Build the logger
    logger = builder.build()

    # Log the malicious payload
    logger.info(payload)

    # Force flush to ensure content is written
    for handler in logger.handlers:
        handler.flush()

    # Read the log file
    log_content = log_file.read_text()

    # Verify the payload appears in the log (it should be logged)
    # but check that it's sanitized/escaped properly

    # 1. Newline injection - fake log entries should not work
    if "\n" in payload and "[" in payload:
        # Count the number of log entries
        # There should be exactly ONE log entry (the one we wrote)
        # not multiple entries from newline injection
        lines = log_content.strip().split("\n")
        # Filter out empty lines
        non_empty_lines = [line for line in lines if line.strip()]

        # The payload might span multiple lines in output if escaped,
        # but there should be a clear delimiter showing it's ONE entry
        # Check that fake log level markers are escaped
        if "[CRITICAL]" in payload or "[ERROR]" in payload:
            # These should appear as literal text, not as real log markers
            # Real log markers would be at the start of a line with timestamp
            fake_markers = []
            for line in non_empty_lines:
                # Skip the legitimate first line
                if line == non_empty_lines[0]:
                    continue
                # Check if subsequent lines look like real log entries
                # Real entries should have timestamp format
                if "[CRITICAL]" in line or "[ERROR]" in line:
                    # This could be the injected content
                    # Verify it doesn't look like a real log entry
                    # (it should be escaped or clearly part of message)
                    fake_markers.append(line)

    # 2. ANSI escape code injection - codes should be escaped or stripped
    if "\x1b[" in payload:
        # ANSI codes could be present but should be escaped
        # Check that the log file doesn't contain raw ANSI codes that could
        # exploit terminals
        # Note: Some formatters might preserve them, which is okay if documented
        # The critical thing is that they don't hide log content
        pass

    # 3. Null byte injection - should not truncate log entries
    if "\x00" in payload:
        # The payload should be in the log file, not truncated at null byte
        # The null byte itself might be escaped or removed
        # but content after it should still be logged
        if "hidden_content" in payload:
            # Verify "hidden_content" after the null byte is still logged
            assert "hidden" in log_content or payload.replace("\x00", "") in log_content

    # 4. General verification: The log file should have valid structure
    # and not be corrupted by the injection
    assert len(log_content) > 0, "Log file is empty - logging may have failed"

    # Cleanup
    for handler in logger.handlers:
        handler.close()


# Positive test: Verify legitimate log messages work correctly
@pytest.mark.security
@pytest.mark.unit
def test_legitimate_log_messages_allowed(secure_temp_project: Path):
    """
    Verify legitimate log messages are logged correctly.

    Security Concern: Security measures should block attacks without breaking
    legitimate use cases. Normal log messages should be written correctly.
    """
    log_file = secure_temp_project / "logs" / "legitimate.log"
    log_file.parent.mkdir(parents=True, exist_ok=True)

    config = LogConfig.from_params(level="info", location=0, micros=False, colors=False)
    builder = FileLoggingBuilder(name="test_logger", file_path=log_file)
    builder.with_level("debug")

    logger = builder.build()

    # Legitimate log messages
    legitimate_messages = [
        "Application started successfully",
        "User alice logged in from 192.168.1.100",
        "Processing request: GET /api/users?page=1&limit=10",
        "Database query completed in 0.042s",
        "Cache miss for key: user:12345:profile",
    ]

    for msg in legitimate_messages:
        logger.info(msg)

    # Flush
    for handler in logger.handlers:
        handler.flush()

    # Verify messages are in log
    log_content = log_file.read_text()

    for msg in legitimate_messages:
        assert msg in log_content, f"Legitimate message not found in log: {msg}"

    # Verify log structure is valid (has timestamps, levels, etc.)
    lines = [line for line in log_content.split("\n") if line.strip()]
    assert len(lines) >= len(legitimate_messages)

    # Each line should have some structure (this is format-dependent)
    # At minimum, the message content should be there
    for i, msg in enumerate(legitimate_messages):
        assert any(msg in line for line in lines), (
            f"Message {i} not found in any log line"
        )

    # Cleanup
    for handler in logger.handlers:
        handler.close()
