"""Tests for audit logging infrastructure."""

import json
import tempfile
from pathlib import Path

import pytest

from rpipe.audit import AuditEvent, AuditLogger, get_audit_logger, init_audit_logger


class TestAuditEvent:
    """Test AuditEvent dataclass."""

    def test_create_audit_event(self):
        """Create a basic audit event."""
        event = AuditEvent(
            timestamp=1234567890.0,
            run_id=1,
            level="ALLOW",
            event_type="command_executed",
            cmd="cat /var/log/syslog",
            binary="cat",
            host="example.com",
            policy="read_only",
        )
        assert event.run_id == 1
        assert event.level == "ALLOW"
        assert event.binary == "cat"

    def test_audit_event_to_dict(self):
        """Convert audit event to dict."""
        event = AuditEvent(
            timestamp=1234567890.0,
            run_id=1,
            level="ALLOW",
            event_type="command_executed",
            cmd="cat /var/log/syslog",
            binary="cat",
        )
        d = event.to_dict()
        assert d["run_id"] == 1
        assert d["level"] == "ALLOW"
        assert d["cmd"] == "cat /var/log/syslog"

    def test_audit_event_to_json(self):
        """Convert audit event to JSON."""
        event = AuditEvent(
            timestamp=1234567890.0,
            run_id=1,
            level="ALLOW",
            event_type="command_executed",
            cmd="cat /var/log/syslog",
            binary="cat",
        )
        json_str = event.to_json()
        parsed = json.loads(json_str)
        assert parsed["run_id"] == 1
        assert parsed["level"] == "ALLOW"

    def test_audit_event_with_metadata(self):
        """Audit event with custom metadata."""
        event = AuditEvent(
            timestamp=1234567890.0,
            run_id=1,
            level="ALLOW",
            event_type="command_executed",
            cmd="cat /var/log/syslog",
            binary="cat",
            metadata={"operations": 3, "files_read": 5},
        )
        d = event.to_dict()
        assert d["metadata"]["operations"] == 3
        assert d["metadata"]["files_read"] == 5


class TestAuditLoggerNoDirectory:
    """Test audit logger without file output (stderr only)."""

    def test_init_without_directory(self):
        """Initialize logger without log directory."""
        logger = AuditLogger()
        assert logger.log_dir is None

    def test_log_command_allowed(self, caplog):
        """Log an allowed command."""
        logger = AuditLogger()
        logger.log_command_allowed(
            run_id=1,
            cmd="cat /var/log/syslog",
            binary="cat",
            host="example.com",
            policy="read_only",
        )
        # Should have logged something
        assert len(caplog.records) > 0

    def test_log_command_denied(self, caplog):
        """Log a denied command."""
        logger = AuditLogger()
        logger.log_command_denied(
            run_id=1,
            cmd="rm /tmp/file",
            binary="rm",
            reason="binary 'rm' not permitted in policy read_only",
            policy="read_only",
            host="example.com",
        )
        assert len(caplog.records) > 0

    def test_log_command_error(self, caplog):
        """Log a command error."""
        logger = AuditLogger()
        logger.log_command_error(
            run_id=1,
            cmd="cat /var/log/syslog",
            binary="cat",
            error="SSH connection failed: host unreachable",
            error_type="ssh_error",
            host="example.com",
        )
        assert len(caplog.records) > 0

    def test_log_syntax_error(self, caplog):
        """Log a syntax error."""
        logger = AuditLogger()
        logger.log_syntax_error(
            run_id=1,
            cmd="cat | grep error | wc -l",
            error="pipes not supported in simple command parser",
        )
        assert len(caplog.records) > 0

    def test_log_security_event(self, caplog):
        """Log a security event."""
        logger = AuditLogger()
        logger.log_security_event(
            run_id=1,
            event_type="suspicious_pattern",
            message="Command contains multiple redirects",
            cmd="echo hello > /tmp/a > /tmp/b",
        )
        assert len(caplog.records) > 0


class TestAuditLoggerWithDirectory:
    """Test audit logger with file output."""

    def test_init_with_directory(self):
        """Initialize logger with log directory."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)
            assert logger.log_dir == Path(tmpdir)
            assert logger.log_dir.exists()

    def test_log_file_created(self):
        """Log file is created when logging."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)
            logger.log_command_allowed(
                run_id=1,
                cmd="cat /var/log/syslog",
                binary="cat",
                host="example.com",
                policy="read_only",
            )
            # Should have created a log file
            log_files = list(Path(tmpdir).glob("audit-*.jsonl"))
            assert len(log_files) == 1

    def test_log_file_contains_json(self):
        """Log file contains valid JSON events."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)
            logger.log_command_allowed(
                run_id=1,
                cmd="cat /var/log/syslog",
                binary="cat",
                host="example.com",
            )
            logger.log_command_denied(
                run_id=2,
                cmd="rm /tmp/file",
                binary="rm",
                reason="binary not permitted",
            )

            # Read log file
            log_files = list(Path(tmpdir).glob("audit-*.jsonl"))
            assert len(log_files) == 1

            with open(log_files[0], "r") as f:
                lines = f.readlines()
                assert len(lines) == 2

                # Parse each line as JSON
                event1 = json.loads(lines[0])
                assert event1["run_id"] == 1
                assert event1["level"] == "ALLOW"

                event2 = json.loads(lines[1])
                assert event2["run_id"] == 2
                assert event2["level"] == "DENY"

    def test_multiple_events_logged(self):
        """Multiple events are appended to log file."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)

            for i in range(5):
                logger.log_command_allowed(
                    run_id=i,
                    cmd=f"command {i}",
                    binary="cmd",
                    host="example.com",
                )

            log_files = list(Path(tmpdir).glob("audit-*.jsonl"))
            with open(log_files[0], "r") as f:
                lines = f.readlines()
                assert len(lines) == 5

    def test_log_structure_allow_event(self):
        """ALLOW event has correct structure."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)
            logger.log_command_allowed(
                run_id=123,
                cmd="grep error /var/log/syslog",
                binary="grep",
                host="web1.example.com",
                policy="read_only",
                username="admin",
                cwd="/var/log",
                metadata={"operations": 2},
            )

            log_files = list(Path(tmpdir).glob("audit-*.jsonl"))
            with open(log_files[0], "r") as f:
                event = json.loads(f.readline())

            assert event["run_id"] == 123
            assert event["level"] == "ALLOW"
            assert event["event_type"] == "command_executed"
            assert event["cmd"] == "grep error /var/log/syslog"
            assert event["binary"] == "grep"
            assert event["host"] == "web1.example.com"
            assert event["policy"] == "read_only"
            assert event["username"] == "admin"
            assert event["cwd"] == "/var/log"
            assert event["metadata"]["operations"] == 2

    def test_log_structure_deny_event(self):
        """DENY event has correct structure."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)
            logger.log_command_denied(
                run_id=456,
                cmd="rm /tmp/file",
                binary="rm",
                reason="execute_binary on 'rm' not permitted in policy read_only",
                policy="read_only",
                host="db.example.com",
                username="developer",
            )

            log_files = list(Path(tmpdir).glob("audit-*.jsonl"))
            with open(log_files[0], "r") as f:
                event = json.loads(f.readline())

            assert event["run_id"] == 456
            assert event["level"] == "DENY"
            assert event["event_type"] == "command_rejected"
            assert event["reason"] == "execute_binary on 'rm' not permitted in policy read_only"

    def test_log_structure_error_event(self):
        """ERROR event has correct structure."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)
            logger.log_command_error(
                run_id=789,
                cmd="cat /var/log/syslog",
                binary="cat",
                error="SSH connection timeout after 30s",
                error_type="ssh_error",
                host="slow-host.example.com",
            )

            log_files = list(Path(tmpdir).glob("audit-*.jsonl"))
            with open(log_files[0], "r") as f:
                event = json.loads(f.readline())

            assert event["run_id"] == 789
            assert event["level"] == "ERROR"
            assert event["event_type"] == "ssh_error"
            assert event["error"] == "SSH connection timeout after 30s"

    def test_completed_event_with_exit_code(self):
        """Log command completion with exit code."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)
            logger.log_command_completed(
                run_id=1,
                exit_code=0,
                duration_s=2.5,
            )

            log_files = list(Path(tmpdir).glob("audit-*.jsonl"))
            with open(log_files[0], "r") as f:
                event = json.loads(f.readline())

            assert event["level"] == "ALLOW"
            assert event["event_type"] == "command_completed"
            assert event["exit_code"] == 0
            assert event["duration_s"] == 2.5


class TestAuditLoggerGlobal:
    """Test global audit logger functions."""

    def test_init_audit_logger(self):
        """Initialize global audit logger."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = init_audit_logger(tmpdir)
            assert logger.log_dir == Path(tmpdir)

    def test_get_audit_logger_after_init(self):
        """Get audit logger after initialization."""
        with tempfile.TemporaryDirectory() as tmpdir:
            init_audit_logger(tmpdir)
            logger = get_audit_logger()
            assert logger.log_dir == Path(tmpdir)

    def test_get_audit_logger_creates_default(self):
        """Get audit logger creates default if not initialized."""
        # Reset global state for this test
        import rpipe.audit

        old_logger = rpipe.audit._audit_logger
        rpipe.audit._audit_logger = None

        try:
            logger = get_audit_logger()
            assert logger is not None
            assert isinstance(logger, AuditLogger)
        finally:
            rpipe.audit._audit_logger = old_logger


class TestAuditLoggerEdgeCases:
    """Test edge cases and error handling."""

    def test_long_command(self):
        """Log very long command."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)
            long_cmd = "grep " + "a" * 10000
            logger.log_command_allowed(
                run_id=1,
                cmd=long_cmd,
                binary="grep",
                host="example.com",
            )

            log_files = list(Path(tmpdir).glob("audit-*.jsonl"))
            with open(log_files[0], "r") as f:
                event = json.loads(f.readline())
            assert len(event["cmd"]) == len(long_cmd)

    def test_special_characters_in_log(self):
        """Log commands with special characters."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)
            logger.log_command_allowed(
                run_id=1,
                cmd='grep "error|warning" /var/log/syslog',
                binary="grep",
                host="example.com",
            )

            log_files = list(Path(tmpdir).glob("audit-*.jsonl"))
            with open(log_files[0], "r") as f:
                event = json.loads(f.readline())
            assert '"error|warning"' in event["cmd"]

    def test_unicode_in_log(self):
        """Log commands with unicode characters."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)
            logger.log_command_denied(
                run_id=1,
                cmd="echo 'Ñoño'",
                binary="echo",
                reason="Unicode test: café",
            )

            log_files = list(Path(tmpdir).glob("audit-*.jsonl"))
            with open(log_files[0], "r", encoding="utf-8") as f:
                event = json.loads(f.readline())
            assert "café" in event["reason"]

    def test_none_values_in_event(self):
        """Event with None values serializes correctly."""
        event = AuditEvent(
            timestamp=1234567890.0,
            run_id=1,
            level="ERROR",
            event_type="error",
            cmd="",
            binary="",
            host=None,
            username=None,
            exit_code=None,
            duration_s=None,
        )
        json_str = event.to_json()
        parsed = json.loads(json_str)
        assert parsed["host"] is None
        assert parsed["exit_code"] is None

    def test_permission_error_handling(self):
        """Handle permission errors when writing logs."""
        with tempfile.TemporaryDirectory() as tmpdir:
            logger = AuditLogger(tmpdir)

            # Log first event (should succeed)
            logger.log_command_allowed(
                run_id=1,
                cmd="test",
                binary="test",
                host="example.com",
            )

            # Make directory read-only to cause write errors
            log_dir = Path(tmpdir)
            log_dir.chmod(0o444)

            try:
                # Try to log another event (will fail gracefully)
                logger.log_command_allowed(
                    run_id=2,
                    cmd="test2",
                    binary="test2",
                    host="example.com",
                )
                # Should not raise, but log the error internally
            finally:
                # Restore permissions for cleanup
                log_dir.chmod(0o755)
