"""
Tests for GateWay Log Aggregation Module.
"""

from datetime import datetime, timedelta

import pytest

from verlex_server.gateway.backend.log_aggregation import (
    InMemoryLogBackend,
    LogAggregator,
    LogEntry,
    LogLevel,
    LogQuery,
    LogSource,
    create_log_aggregator,
)


class TestLogEntry:
    """Tests for LogEntry class."""

    def test_create_log_entry(self):
        """Test creating a log entry."""
        entry = LogEntry(
            message="Test message",
            level=LogLevel.INFO,
        )

        assert entry.message == "Test message"
        assert entry.level == LogLevel.INFO
        assert entry.id is not None
        assert entry.timestamp is not None

    def test_log_entry_with_context(self):
        """Test creating a log entry with full context."""
        entry = LogEntry(
            message="Job started",
            level=LogLevel.INFO,
            source=LogSource.JOB,
            job_id="job-123",
            user_id="user-456",
            correlation_id="corr-789",
            metadata={"gpu": "A100"},
        )

        assert entry.job_id == "job-123"
        assert entry.user_id == "user-456"
        assert entry.source == LogSource.JOB
        assert entry.metadata["gpu"] == "A100"

    def test_log_entry_to_dict(self):
        """Test converting log entry to dict."""
        entry = LogEntry(
            message="Test",
            level=LogLevel.ERROR,
            job_id="job-123",
        )

        data = entry.to_dict()

        assert data["message"] == "Test"
        assert data["level"] == "error"
        assert data["job_id"] == "job-123"
        assert "timestamp" in data

    def test_log_entry_from_dict(self):
        """Test creating log entry from dict."""
        data = {
            "id": "entry-123",
            "message": "Test message",
            "level": "warning",
            "timestamp": datetime.utcnow().isoformat(),
            "source": "job",
            "job_id": "job-456",
        }

        entry = LogEntry.from_dict(data)

        assert entry.id == "entry-123"
        assert entry.message == "Test message"
        assert entry.level == LogLevel.WARNING
        assert entry.job_id == "job-456"


class TestLogLevel:
    """Tests for LogLevel enum."""

    def test_level_ordering(self):
        """Test log level ordering."""
        assert LogLevel.DEBUG.to_int() < LogLevel.INFO.to_int()
        assert LogLevel.INFO.to_int() < LogLevel.WARNING.to_int()
        assert LogLevel.WARNING.to_int() < LogLevel.ERROR.to_int()
        assert LogLevel.ERROR.to_int() < LogLevel.CRITICAL.to_int()

    def test_from_string(self):
        """Test creating level from string."""
        assert LogLevel.from_string("debug") == LogLevel.DEBUG
        assert LogLevel.from_string("INFO") == LogLevel.INFO
        assert LogLevel.from_string("Error") == LogLevel.ERROR


class TestInMemoryLogBackend:
    """Tests for InMemoryLogBackend."""

    @pytest.mark.asyncio
    async def test_write_and_query(self):
        """Test writing and querying logs."""
        backend = InMemoryLogBackend()

        entry = LogEntry(
            message="Test log",
            level=LogLevel.INFO,
        )

        result = await backend.write(entry)
        assert result is True

        query = LogQuery(limit=10)
        results = await backend.query(query)

        assert len(results.entries) == 1
        assert results.entries[0].message == "Test log"

    @pytest.mark.asyncio
    async def test_query_filtering(self):
        """Test query filtering."""
        backend = InMemoryLogBackend()

        # Write logs with different levels and job IDs
        for i in range(10):
            entry = LogEntry(
                message=f"Log {i}",
                level=LogLevel.INFO if i < 5 else LogLevel.ERROR,
                job_id="job-1" if i < 7 else "job-2",
            )
            await backend.write(entry)

        # Query by job ID
        query = LogQuery(job_id="job-1")
        results = await backend.query(query)
        assert len(results.entries) == 7

        # Query by level
        query = LogQuery(min_level=LogLevel.ERROR)
        results = await backend.query(query)
        assert len(results.entries) == 5

    @pytest.mark.asyncio
    async def test_search_text(self):
        """Test text search in logs."""
        backend = InMemoryLogBackend()

        await backend.write(LogEntry(message="Error: Connection failed", level=LogLevel.ERROR))
        await backend.write(LogEntry(message="Info: Connection established", level=LogLevel.INFO))
        await backend.write(LogEntry(message="Debug: Starting process", level=LogLevel.DEBUG))

        query = LogQuery(search_text="connection")
        results = await backend.query(query)

        assert len(results.entries) == 2

    @pytest.mark.asyncio
    async def test_max_entries_limit(self):
        """Test that max entries limit is enforced."""
        backend = InMemoryLogBackend(max_entries=10)

        for i in range(15):
            await backend.write(LogEntry(message=f"Log {i}", level=LogLevel.INFO))

        query = LogQuery(limit=100)
        results = await backend.query(query)

        assert len(results.entries) == 10

    @pytest.mark.asyncio
    async def test_stream(self):
        """Test real-time streaming."""
        backend = InMemoryLogBackend()

        queue = await backend.stream(job_id="job-123", min_level=LogLevel.INFO)

        # Write logs
        await backend.write(LogEntry(
            message="Should appear",
            level=LogLevel.INFO,
            job_id="job-123",
        ))
        await backend.write(LogEntry(
            message="Should not appear (wrong job)",
            level=LogLevel.INFO,
            job_id="job-456",
        ))
        await backend.write(LogEntry(
            message="Should not appear (low level)",
            level=LogLevel.DEBUG,
            job_id="job-123",
        ))

        # Check queue
        entry = queue.get_nowait()
        assert entry.message == "Should appear"

        assert queue.empty()


class TestLogAggregator:
    """Tests for LogAggregator."""

    @pytest.mark.asyncio
    async def test_multi_backend_write(self):
        """Test writing to multiple backends."""
        backend1 = InMemoryLogBackend()
        backend2 = InMemoryLogBackend()

        aggregator = LogAggregator(backends=[backend1, backend2])
        await aggregator.start()

        await aggregator.info("Test message", job_id="job-123")
        await aggregator._flush()

        # Both backends should have the log
        query = LogQuery()
        results1 = await backend1.query(query)
        results2 = await backend2.query(query)

        assert len(results1.entries) == 1
        assert len(results2.entries) == 1

        await aggregator.stop()

    @pytest.mark.asyncio
    async def test_convenience_methods(self):
        """Test convenience logging methods."""
        backend = InMemoryLogBackend()
        aggregator = LogAggregator(backends=[backend])

        await aggregator.debug("Debug message")
        await aggregator.info("Info message")
        await aggregator.warning("Warning message")
        await aggregator.error("Error message")
        await aggregator.critical("Critical message")
        await aggregator._flush()

        query = LogQuery()
        results = await backend.query(query)

        assert len(results.entries) == 5


class TestLogQuery:
    """Tests for LogQuery."""

    def test_default_values(self):
        """Test default query values."""
        query = LogQuery()

        assert query.limit == 100
        assert query.offset == 0
        assert query.order_desc is True
        assert query.min_level == LogLevel.DEBUG

    def test_time_range(self):
        """Test time range in query."""
        start = datetime.utcnow() - timedelta(hours=1)
        end = datetime.utcnow()

        query = LogQuery(start_time=start, end_time=end)

        assert query.start_time == start
        assert query.end_time == end


class TestCreateLogAggregator:
    """Tests for log aggregator factory."""

    def test_create_memory_backend(self):
        """Test creating with memory backend."""
        aggregator = create_log_aggregator(backend_type="memory")
        assert len(aggregator.backends) == 1

    def test_create_file_backend(self):
        """Test creating with file backend."""
        aggregator = create_log_aggregator(backend_type="file")
        # File backend + in-memory for streaming
        assert len(aggregator.backends) == 2


# Run tests
if __name__ == "__main__":
    pytest.main([__file__, "-v"])
