"""
Behavioral tests for logging patterns.

These tests verify the logic of logging patterns like log formatting,
level filtering, context management, and structured logging
without making actual log writes.
"""

import json
import re
import time
from dataclasses import dataclass
from datetime import datetime
from enum import IntEnum


class LogLevel(IntEnum):
    """Log levels in order of severity."""

    DEBUG = 10
    INFO = 20
    WARNING = 30
    ERROR = 40
    CRITICAL = 50


class TestLogLevelFiltering:
    """Tests for log level filtering."""

    def test_should_log_at_level(self):
        """Test log level filtering logic."""

        def should_log(message_level, configured_level):
            return message_level >= configured_level

        assert should_log(LogLevel.ERROR, LogLevel.INFO) is True
        assert should_log(LogLevel.DEBUG, LogLevel.INFO) is False
        assert should_log(LogLevel.INFO, LogLevel.INFO) is True

    def test_parse_log_level(self):
        """Test parsing log level from string."""

        def parse_level(level_string):
            levels = {
                "debug": LogLevel.DEBUG,
                "info": LogLevel.INFO,
                "warning": LogLevel.WARNING,
                "warn": LogLevel.WARNING,
                "error": LogLevel.ERROR,
                "critical": LogLevel.CRITICAL,
            }
            return levels.get(level_string.lower(), LogLevel.INFO)

        assert parse_level("DEBUG") == LogLevel.DEBUG
        assert parse_level("warning") == LogLevel.WARNING
        assert parse_level("unknown") == LogLevel.INFO

    def test_level_name_conversion(self):
        """Test converting level to name."""

        def level_name(level):
            names = {
                LogLevel.DEBUG: "DEBUG",
                LogLevel.INFO: "INFO",
                LogLevel.WARNING: "WARNING",
                LogLevel.ERROR: "ERROR",
                LogLevel.CRITICAL: "CRITICAL",
            }
            return names.get(level, "UNKNOWN")

        assert level_name(LogLevel.DEBUG) == "DEBUG"
        assert level_name(LogLevel.ERROR) == "ERROR"

    def test_environment_based_level(self):
        """Test determining log level from environment."""

        def get_level_for_env(env):
            env_levels = {
                "development": LogLevel.DEBUG,
                "testing": LogLevel.DEBUG,
                "staging": LogLevel.INFO,
                "production": LogLevel.WARNING,
            }
            return env_levels.get(env, LogLevel.INFO)

        assert get_level_for_env("development") == LogLevel.DEBUG
        assert get_level_for_env("production") == LogLevel.WARNING

    def test_module_specific_level(self):
        """Test module-specific log levels."""

        def get_effective_level(module, module_levels, default_level):
            # Check for exact match
            if module in module_levels:
                return module_levels[module]
            # Check for parent modules
            parts = module.split(".")
            for i in range(len(parts) - 1, 0, -1):
                parent = ".".join(parts[:i])
                if parent in module_levels:
                    return module_levels[parent]
            return default_level

        levels = {
            "myapp": LogLevel.INFO,
            "myapp.db": LogLevel.DEBUG,
        }
        assert (
            get_effective_level("myapp.db.query", levels, LogLevel.WARNING)
            == LogLevel.DEBUG
        )
        assert (
            get_effective_level("myapp.api", levels, LogLevel.WARNING)
            == LogLevel.INFO
        )
        assert (
            get_effective_level("other", levels, LogLevel.WARNING)
            == LogLevel.WARNING
        )


class TestLogFormatting:
    """Tests for log message formatting."""

    def test_basic_format(self):
        """Test basic log message format."""

        def format_log(level, message, timestamp=None):
            if timestamp is None:
                timestamp = datetime.now().isoformat()
            level_name = LogLevel(level).name
            return f"{timestamp} [{level_name}] {message}"

        log = format_log(
            LogLevel.INFO, "Application started", "2024-01-01T12:00:00"
        )
        assert log == "2024-01-01T12:00:00 [INFO] Application started"

    def test_format_with_context(self):
        """Test formatting with context fields."""

        def format_with_context(level, message, context):
            level_name = LogLevel(level).name
            context_str = " ".join(f"{k}={v}" for k, v in context.items())
            return f"[{level_name}] {message} | {context_str}"

        log = format_with_context(
            LogLevel.INFO,
            "User logged in",
            {"user_id": "123", "ip": "192.168.1.1"},
        )
        assert "[INFO]" in log
        assert "User logged in" in log
        assert "user_id=123" in log

    def test_json_format(self):
        """Test JSON log formatting."""

        def format_json(level, message, **extra):
            record = {
                "timestamp": "2024-01-01T12:00:00",
                "level": LogLevel(level).name,
                "message": message,
                **extra,
            }
            return json.dumps(record)

        log = format_json(LogLevel.ERROR, "Failed to connect", error_code=500)
        parsed = json.loads(log)
        assert parsed["level"] == "ERROR"
        assert parsed["message"] == "Failed to connect"
        assert parsed["error_code"] == 500

    def test_format_exception(self):
        """Test formatting exception in log."""

        def format_exception(exc):
            return {
                "type": type(exc).__name__,
                "message": str(exc),
                "module": type(exc).__module__,
            }

        try:
            raise ValueError("Invalid value")
        except ValueError as e:
            formatted = format_exception(e)
            assert formatted["type"] == "ValueError"
            assert formatted["message"] == "Invalid value"

    def test_truncate_long_message(self):
        """Test truncating long messages."""

        def truncate_message(message, max_length=1000):
            if len(message) <= max_length:
                return message
            return message[: max_length - 3] + "..."

        short = "Short message"
        assert truncate_message(short) == short

        long = "x" * 2000
        truncated = truncate_message(long, max_length=100)
        assert len(truncated) == 100
        assert truncated.endswith("...")


class TestStructuredLogging:
    """Tests for structured logging patterns."""

    def test_create_log_record(self):
        """Test creating structured log record."""

        @dataclass
        class LogRecord:
            timestamp: str
            level: str
            message: str
            logger: str
            extra: dict

        def create_record(level, message, logger="root", **extra):
            return LogRecord(
                timestamp=datetime.now().isoformat(),
                level=LogLevel(level).name,
                message=message,
                logger=logger,
                extra=extra,
            )

        record = create_record(LogLevel.INFO, "Test message", user_id="123")
        assert record.level == "INFO"
        assert record.message == "Test message"
        assert record.extra["user_id"] == "123"

    def test_add_common_fields(self):
        """Test adding common fields to log records."""

        def add_common_fields(record, common_fields):
            result = record.copy()
            for key, value in common_fields.items():
                if key not in result:
                    result[key] = value
            return result

        record = {"message": "Test", "level": "INFO"}
        common = {"service": "api", "environment": "prod", "version": "1.0.0"}
        enhanced = add_common_fields(record, common)
        assert enhanced["service"] == "api"
        assert enhanced["environment"] == "prod"

    def test_flatten_nested_context(self):
        """Test flattening nested context for logging."""

        def flatten_context(context, prefix="", separator="."):
            flat = {}
            for key, value in context.items():
                full_key = f"{prefix}{separator}{key}" if prefix else key
                if isinstance(value, dict):
                    flat.update(flatten_context(value, full_key, separator))
                else:
                    flat[full_key] = value
            return flat

        nested = {
            "user": {"id": 123, "name": "John"},
            "request": {"method": "GET", "path": "/api"},
        }
        flat = flatten_context(nested)
        assert flat["user.id"] == 123
        assert flat["user.name"] == "John"
        assert flat["request.method"] == "GET"

    def test_serialize_log_value(self):
        """Test serializing log values."""

        def serialize_value(value):
            if isinstance(value, datetime):
                return value.isoformat()
            if isinstance(value, (set, frozenset)):
                return list(value)
            if isinstance(value, bytes):
                return value.decode("utf-8", errors="replace")
            if hasattr(value, "__dict__"):
                return str(value)
            return value

        assert serialize_value(datetime(2024, 1, 1)) == "2024-01-01T00:00:00"
        assert serialize_value({1, 2, 3}) == [1, 2, 3]
        assert serialize_value(b"bytes") == "bytes"

    def test_mask_sensitive_values(self):
        """Test masking sensitive values in logs."""

        def mask_sensitive(record, sensitive_keys=None):
            if sensitive_keys is None:
                sensitive_keys = {
                    "password",
                    "token",
                    "secret",
                    "api_key",
                    "credit_card",
                }
            result = {}
            for key, value in record.items():
                if key.lower() in sensitive_keys:
                    result[key] = "***MASKED***"
                elif isinstance(value, dict):
                    result[key] = mask_sensitive(value, sensitive_keys)
                else:
                    result[key] = value
            return result

        record = {
            "user": "john",
            "password": "secret123",
            "data": {"api_key": "abc123"},
        }
        masked = mask_sensitive(record)
        assert masked["user"] == "john"
        assert masked["password"] == "***MASKED***"
        assert masked["data"]["api_key"] == "***MASKED***"


class TestLogContext:
    """Tests for log context management."""

    def test_context_manager(self):
        """Test log context as a stack."""

        class LogContext:
            def __init__(self):
                self._stack = [{}]

            def push(self, **kwargs):
                new_context = {**self._stack[-1], **kwargs}
                self._stack.append(new_context)

            def pop(self):
                if len(self._stack) > 1:
                    return self._stack.pop()
                return None

            def get(self):
                return self._stack[-1].copy()

        ctx = LogContext()
        ctx.push(request_id="123")
        assert ctx.get()["request_id"] == "123"

        ctx.push(user_id="456")
        assert ctx.get()["request_id"] == "123"
        assert ctx.get()["user_id"] == "456"

        ctx.pop()
        assert "user_id" not in ctx.get()
        assert ctx.get()["request_id"] == "123"

    def test_bind_context(self):
        """Test binding context to logger."""

        def bind_context(existing_context, **new_context):
            return {**existing_context, **new_context}

        ctx = {"service": "api"}
        ctx = bind_context(ctx, request_id="123")
        ctx = bind_context(ctx, user_id="456")
        assert ctx["service"] == "api"
        assert ctx["request_id"] == "123"
        assert ctx["user_id"] == "456"

    def test_unbind_context(self):
        """Test unbinding context from logger."""

        def unbind_context(context, *keys):
            return {k: v for k, v in context.items() if k not in keys}

        ctx = {"service": "api", "request_id": "123", "user_id": "456"}
        ctx = unbind_context(ctx, "user_id", "request_id")
        assert ctx == {"service": "api"}

    def test_clear_context(self):
        """Test clearing all context."""

        def clear_context(context, preserve_keys=None):
            if preserve_keys is None:
                return {}
            return {k: v for k, v in context.items() if k in preserve_keys}

        ctx = {"service": "api", "request_id": "123", "user_id": "456"}
        assert clear_context(ctx) == {}
        assert clear_context(ctx, preserve_keys={"service"}) == {
            "service": "api"
        }

    def test_context_inheritance(self):
        """Test context inheritance for child loggers."""

        def create_child_context(parent_context, child_name):
            return {
                **parent_context,
                "logger": f"{parent_context.get('logger', 'root')}.{child_name}",
            }

        parent = {"service": "api", "logger": "app"}
        child = create_child_context(parent, "db")
        assert child["service"] == "api"
        assert child["logger"] == "app.db"


class TestLogFiltering:
    """Tests for log filtering patterns."""

    def test_filter_by_pattern(self):
        """Test filtering logs by message pattern."""

        def matches_filter(
            message, include_patterns=None, exclude_patterns=None
        ):
            if exclude_patterns:
                for pattern in exclude_patterns:
                    if re.search(pattern, message):
                        return False
            if include_patterns:
                for pattern in include_patterns:
                    if re.search(pattern, message):
                        return True
                return False
            return True

        assert matches_filter("User login successful") is True
        assert (
            matches_filter("Healthcheck OK", exclude_patterns=[r"Healthcheck"])
            is False
        )
        assert (
            matches_filter(
                "Error in payment", include_patterns=[r"Error", r"Warning"]
            )
            is True
        )
        assert (
            matches_filter(
                "Info message", include_patterns=[r"Error", r"Warning"]
            )
            is False
        )

    def test_filter_by_rate(self):
        """Test rate-limited log filtering."""

        class RateLimitedFilter:
            def __init__(self, rate_seconds=60):
                self.rate_seconds = rate_seconds
                self.last_log_times = {}

            def should_log(self, key):
                now = time.time()
                last_time = self.last_log_times.get(key, 0)
                if now - last_time >= self.rate_seconds:
                    self.last_log_times[key] = now
                    return True
                return False

        f = RateLimitedFilter(rate_seconds=60)
        assert f.should_log("error_type_1") is True
        assert f.should_log("error_type_1") is False  # Too soon
        assert f.should_log("error_type_2") is True  # Different key

    def test_filter_by_sampling(self):
        """Test sampling filter for high-volume logs."""

        def should_sample(sample_rate):
            import random

            return random.random() < sample_rate

        # With 100% sample rate, always log
        # Can't test randomness deterministically, but can test edge cases

        def sample_decision(sample_rate, random_value):
            return random_value < sample_rate

        assert sample_decision(1.0, 0.5) is True  # 100% rate
        assert sample_decision(0.0, 0.5) is False  # 0% rate
        assert sample_decision(0.5, 0.3) is True  # Under threshold
        assert sample_decision(0.5, 0.7) is False  # Over threshold

    def test_deduplicate_filter(self):
        """Test deduplication filter for repeated messages."""

        class DeduplicateFilter:
            def __init__(self, window_seconds=60, max_duplicates=3):
                self.window_seconds = window_seconds
                self.max_duplicates = max_duplicates
                self.message_counts = {}

            def should_log(self, message_key):
                now = time.time()
                if message_key not in self.message_counts:
                    self.message_counts[message_key] = {
                        "count": 1,
                        "first_seen": now,
                    }
                    return True

                data = self.message_counts[message_key]
                if now - data["first_seen"] > self.window_seconds:
                    # Reset window
                    self.message_counts[message_key] = {
                        "count": 1,
                        "first_seen": now,
                    }
                    return True

                data["count"] += 1
                return data["count"] <= self.max_duplicates

        f = DeduplicateFilter(max_duplicates=3)
        assert f.should_log("error_1") is True
        assert f.should_log("error_1") is True
        assert f.should_log("error_1") is True
        assert f.should_log("error_1") is False  # Exceeded max

    def test_context_filter(self):
        """Test filtering by context fields."""

        def context_matches(log_context, required_context):
            for key, value in required_context.items():
                if key not in log_context:
                    return False
                if callable(value):
                    if not value(log_context[key]):
                        return False
                elif log_context[key] != value:
                    return False
            return True

        log = {"service": "api", "user_id": "123", "status": 500}
        assert context_matches(log, {"service": "api"}) is True
        assert context_matches(log, {"service": "web"}) is False
        assert context_matches(log, {"status": lambda x: x >= 400}) is True


class TestLogOutput:
    """Tests for log output handling."""

    def test_select_output_by_level(self):
        """Test selecting output destination by level."""

        def get_output(level):
            if level >= LogLevel.ERROR:
                return "stderr"
            return "stdout"

        assert get_output(LogLevel.DEBUG) == "stdout"
        assert get_output(LogLevel.INFO) == "stdout"
        assert get_output(LogLevel.ERROR) == "stderr"
        assert get_output(LogLevel.CRITICAL) == "stderr"

    def test_format_for_console(self):
        """Test console-friendly formatting."""

        def format_console(level, message, use_color=True):
            colors = {
                LogLevel.DEBUG: "\033[90m",  # Gray
                LogLevel.INFO: "\033[0m",  # Default
                LogLevel.WARNING: "\033[33m",  # Yellow
                LogLevel.ERROR: "\033[31m",  # Red
                LogLevel.CRITICAL: "\033[1;31m",  # Bold red
            }
            reset = "\033[0m"

            level_name = LogLevel(level).name
            if use_color:
                color = colors.get(level, "")
                return f"{color}[{level_name}] {message}{reset}"
            return f"[{level_name}] {message}"

        log = format_console(LogLevel.ERROR, "Error occurred", use_color=False)
        assert log == "[ERROR] Error occurred"

        colored = format_console(
            LogLevel.ERROR, "Error occurred", use_color=True
        )
        assert "\033[31m" in colored  # Red color code

    def test_format_for_file(self):
        """Test file-friendly formatting."""

        def format_file(level, message, timestamp=None, include_pid=True):
            if timestamp is None:
                timestamp = datetime.now().isoformat()
            level_name = LogLevel(level).name
            pid_str = f" pid={12345}" if include_pid else ""
            return f"{timestamp}{pid_str} [{level_name}] {message}"

        log = format_file(LogLevel.INFO, "Test message", "2024-01-01T12:00:00")
        assert "2024-01-01T12:00:00" in log
        assert "pid=12345" in log
        assert "[INFO]" in log

    def test_format_for_json_lines(self):
        """Test JSON Lines formatting."""

        def format_jsonl(level, message, **extra):
            record = {
                "ts": time.time(),
                "level": LogLevel(level).name,
                "msg": message,
                **extra,
            }
            return json.dumps(record, default=str)

        log = format_jsonl(LogLevel.INFO, "Test", user="john")
        parsed = json.loads(log)
        assert parsed["level"] == "INFO"
        assert parsed["msg"] == "Test"
        assert parsed["user"] == "john"

    def test_batch_logs_for_output(self):
        """Test batching logs for efficient output."""

        def batch_logs(logs, batch_size=100, max_wait_ms=1000):
            batches = []
            current_batch = []
            for log in logs:
                current_batch.append(log)
                if len(current_batch) >= batch_size:
                    batches.append(current_batch)
                    current_batch = []
            if current_batch:
                batches.append(current_batch)
            return batches

        logs = [f"log_{i}" for i in range(250)]
        batches = batch_logs(logs, batch_size=100)
        assert len(batches) == 3
        assert len(batches[0]) == 100
        assert len(batches[2]) == 50


class TestLogMetrics:
    """Tests for log-based metrics patterns."""

    def test_count_by_level(self):
        """Test counting logs by level."""

        class LogCounter:
            def __init__(self):
                self.counts = {level: 0 for level in LogLevel}

            def increment(self, level):
                self.counts[level] += 1

            def get_counts(self):
                return {LogLevel(k).name: v for k, v in self.counts.items()}

        counter = LogCounter()
        counter.increment(LogLevel.INFO)
        counter.increment(LogLevel.INFO)
        counter.increment(LogLevel.ERROR)

        counts = counter.get_counts()
        assert counts["INFO"] == 2
        assert counts["ERROR"] == 1

    def test_calculate_error_rate(self):
        """Test calculating error rate from log counts."""

        def calculate_error_rate(counts):
            total = sum(counts.values())
            if total == 0:
                return 0.0
            errors = counts.get(LogLevel.ERROR, 0) + counts.get(
                LogLevel.CRITICAL, 0
            )
            return errors / total

        counts = {
            LogLevel.INFO: 90,
            LogLevel.WARNING: 5,
            LogLevel.ERROR: 4,
            LogLevel.CRITICAL: 1,
        }
        rate = calculate_error_rate(counts)
        assert rate == 0.05  # 5% error rate

    def test_track_log_rate(self):
        """Test tracking log rate over time."""

        class LogRateTracker:
            def __init__(self, window_seconds=60):
                self.window_seconds = window_seconds
                self.timestamps = []

            def record(self):
                self.timestamps.append(time.time())
                self._cleanup()

            def _cleanup(self):
                cutoff = time.time() - self.window_seconds
                self.timestamps = [t for t in self.timestamps if t > cutoff]

            def rate_per_second(self):
                self._cleanup()
                if not self.timestamps:
                    return 0.0
                return len(self.timestamps) / self.window_seconds

        tracker = LogRateTracker(window_seconds=60)
        for _ in range(120):
            tracker.record()
        assert tracker.rate_per_second() == 2.0

    def test_detect_anomaly(self):
        """Test detecting logging anomalies."""

        def is_anomalous(current_rate, baseline_rate, threshold_multiplier=3):
            if baseline_rate == 0:
                return current_rate > 0
            return current_rate > baseline_rate * threshold_multiplier

        assert is_anomalous(10, 100) is False  # Normal
        assert is_anomalous(400, 100) is True  # 4x baseline - anomalous
        assert is_anomalous(300, 100) is False  # Exactly 3x - threshold


class TestLogParsing:
    """Tests for log parsing patterns."""

    def test_parse_log_line(self):
        """Test parsing structured log line."""

        def parse_log_line(line):
            # Format: timestamp [LEVEL] message | key=value ...
            match = re.match(
                r"(\S+)\s+\[(\w+)\]\s+(.+?)(?:\s+\|\s+(.+))?$", line
            )
            if not match:
                return None
            timestamp, level, message, context_str = match.groups()
            context = {}
            if context_str:
                for part in context_str.split():
                    if "=" in part:
                        key, value = part.split("=", 1)
                        context[key] = value
            return {
                "timestamp": timestamp,
                "level": level,
                "message": message,
                "context": context,
            }

        line = "2024-01-01T12:00:00 [INFO] User logged in | user_id=123 ip=192.168.1.1"
        parsed = parse_log_line(line)
        assert parsed["timestamp"] == "2024-01-01T12:00:00"
        assert parsed["level"] == "INFO"
        assert parsed["message"] == "User logged in"
        assert parsed["context"]["user_id"] == "123"

    def test_parse_json_log(self):
        """Test parsing JSON log."""

        def parse_json_log(line):
            try:
                return json.loads(line)
            except json.JSONDecodeError:
                return None

        line = '{"timestamp": "2024-01-01T12:00:00", "level": "INFO", "message": "Test"}'
        parsed = parse_json_log(line)
        assert parsed["level"] == "INFO"
        assert parsed["message"] == "Test"

    def test_extract_error_details(self):
        """Test extracting error details from log."""

        def extract_error_info(log_record):
            if log_record.get("level") not in ("ERROR", "CRITICAL"):
                return None
            return {
                "error_type": log_record.get("error_type", "unknown"),
                "error_message": log_record.get(
                    "error_message", log_record.get("message")
                ),
                "stack_trace": log_record.get("stack_trace"),
                "timestamp": log_record.get("timestamp"),
            }

        log = {
            "level": "ERROR",
            "message": "Database connection failed",
            "error_type": "ConnectionError",
            "timestamp": "2024-01-01T12:00:00",
        }
        info = extract_error_info(log)
        assert info["error_type"] == "ConnectionError"

    def test_group_logs_by_request(self):
        """Test grouping logs by request ID."""

        def group_by_request(logs):
            groups = {}
            for log in logs:
                request_id = log.get("request_id", "unknown")
                if request_id not in groups:
                    groups[request_id] = []
                groups[request_id].append(log)
            return groups

        logs = [
            {"request_id": "req1", "message": "Start"},
            {"request_id": "req2", "message": "Start"},
            {"request_id": "req1", "message": "End"},
        ]
        groups = group_by_request(logs)
        assert len(groups["req1"]) == 2
        assert len(groups["req2"]) == 1

    def test_extract_timing_from_logs(self):
        """Test extracting timing information from logs."""

        def extract_timing(logs, start_pattern, end_pattern):
            start_time = None
            end_time = None
            for log in logs:
                if start_pattern in log.get("message", ""):
                    start_time = log.get("timestamp")
                if end_pattern in log.get("message", ""):
                    end_time = log.get("timestamp")
            if start_time and end_time:
                # In real implementation, would parse timestamps
                return {"start": start_time, "end": end_time}
            return None

        logs = [
            {"message": "Request start", "timestamp": 1000},
            {"message": "Processing", "timestamp": 1050},
            {"message": "Request end", "timestamp": 1100},
        ]
        timing = extract_timing(logs, "Request start", "Request end")
        assert timing["start"] == 1000
        assert timing["end"] == 1100


class TestLogRotation:
    """Tests for log rotation patterns."""

    def test_should_rotate_by_size(self):
        """Test size-based rotation decision."""

        def should_rotate_size(current_size, max_size):
            return current_size >= max_size

        assert should_rotate_size(1024 * 1024 * 9, 1024 * 1024 * 10) is False
        assert should_rotate_size(1024 * 1024 * 10, 1024 * 1024 * 10) is True
        assert should_rotate_size(1024 * 1024 * 11, 1024 * 1024 * 10) is True

    def test_should_rotate_by_time(self):
        """Test time-based rotation decision."""

        def should_rotate_time(last_rotation, rotation_interval):
            return time.time() - last_rotation >= rotation_interval

        now = time.time()
        # Less than interval - don't rotate
        assert should_rotate_time(now - 3000, 3600) is False
        # Past interval - rotate
        assert should_rotate_time(now - 4000, 3600) is True

    def test_generate_rotated_filename(self):
        """Test generating rotated log filename."""

        def generate_rotated_name(base_name, index=None, timestamp=None):
            if timestamp:
                return f"{base_name}.{timestamp}"
            if index is not None:
                return f"{base_name}.{index}"
            return f"{base_name}.1"

        assert generate_rotated_name("app.log", index=1) == "app.log.1"
        assert (
            generate_rotated_name("app.log", timestamp="2024-01-01")
            == "app.log.2024-01-01"
        )

    def test_calculate_files_to_delete(self):
        """Test calculating old files to delete."""

        def files_to_delete(existing_files, max_files):
            if len(existing_files) <= max_files:
                return []
            # Sort by modification time (oldest first)
            sorted_files = sorted(existing_files, key=lambda f: f["mtime"])
            return sorted_files[: len(existing_files) - max_files]

        files = [
            {"name": "log.1", "mtime": 1000},
            {"name": "log.2", "mtime": 2000},
            {"name": "log.3", "mtime": 3000},
            {"name": "log.4", "mtime": 4000},
        ]
        to_delete = files_to_delete(files, max_files=2)
        assert len(to_delete) == 2
        assert to_delete[0]["name"] == "log.1"
        assert to_delete[1]["name"] == "log.2"

    def test_compress_rotated_log(self):
        """Test determining if log should be compressed."""

        def should_compress(file_size, compression_threshold=1024 * 100):
            return file_size >= compression_threshold

        assert should_compress(1024 * 50) is False
        assert should_compress(1024 * 100) is True
        assert should_compress(1024 * 200) is True


class TestLogAggregation:
    """Tests for log aggregation patterns."""

    def test_aggregate_by_level(self):
        """Test aggregating logs by level."""

        def aggregate_by_level(logs):
            result = {}
            for log in logs:
                level = log.get("level", "UNKNOWN")
                if level not in result:
                    result[level] = {"count": 0, "messages": []}
                result[level]["count"] += 1
                result[level]["messages"].append(log.get("message"))
            return result

        logs = [
            {"level": "INFO", "message": "msg1"},
            {"level": "ERROR", "message": "err1"},
            {"level": "INFO", "message": "msg2"},
        ]
        agg = aggregate_by_level(logs)
        assert agg["INFO"]["count"] == 2
        assert agg["ERROR"]["count"] == 1

    def test_aggregate_by_time_bucket(self):
        """Test aggregating logs by time bucket."""

        def aggregate_by_time(logs, bucket_seconds=60):
            result = {}
            for log in logs:
                ts = log.get("timestamp", 0)
                bucket = (ts // bucket_seconds) * bucket_seconds
                if bucket not in result:
                    result[bucket] = 0
                result[bucket] += 1
            return result

        logs = [
            {"timestamp": 1000},  # bucket 960 (960 <= 1000 < 1020)
            {"timestamp": 1030},  # bucket 1020 (1020 <= 1030 < 1080)
            {"timestamp": 1070},  # bucket 1020 (1020 <= 1070 < 1080)
            {"timestamp": 1130},  # bucket 1080 (1080 <= 1130 < 1140)
        ]
        agg = aggregate_by_time(logs, bucket_seconds=60)
        assert agg[960] == 1  # First bucket has 1 log (1000)
        assert agg[1020] == 2  # Second bucket has 2 logs (1030, 1070)
        assert agg[1080] == 1  # Third bucket has 1 log (1130)

    def test_top_error_messages(self):
        """Test finding top error messages."""

        def top_errors(logs, top_n=5):
            error_counts = {}
            for log in logs:
                if log.get("level") == "ERROR":
                    msg = log.get("message", "")
                    error_counts[msg] = error_counts.get(msg, 0) + 1
            sorted_errors = sorted(
                error_counts.items(), key=lambda x: x[1], reverse=True
            )
            return sorted_errors[:top_n]

        logs = [
            {"level": "ERROR", "message": "Connection failed"},
            {"level": "ERROR", "message": "Timeout"},
            {"level": "ERROR", "message": "Connection failed"},
            {"level": "INFO", "message": "OK"},
        ]
        top = top_errors(logs, top_n=2)
        assert top[0] == ("Connection failed", 2)
        assert top[1] == ("Timeout", 1)

    def test_calculate_percentiles(self):
        """Test calculating log latency percentiles."""

        def calculate_percentiles(values, percentiles):
            if not values:
                return {}
            sorted_values = sorted(values)
            n = len(sorted_values)
            result = {}
            for p in percentiles:
                index = int(p / 100 * (n - 1))
                result[f"p{p}"] = sorted_values[index]
            return result

        latencies = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
        percentiles = calculate_percentiles(latencies, [50, 90, 99])
        assert percentiles["p50"] == 50
        assert percentiles["p90"] == 90
