"""Benchmark tests for llmclient module.

These tests measure performance of key operations and are excluded from regular test runs.
Run with: pytest --benchmark-only -v
"""

from __future__ import annotations

import json
from pathlib import Path
from unittest.mock import MagicMock, Mock, patch

import pytest

from pytola.llm.llmclient.gui import (
    ConnectionTestWorker,
    LLMClientConfig,
    LLMWorker,
)


class TestBenchmarkConfiguration:
    """Benchmark configuration loading and saving operations."""

    @pytest.fixture
    def config_file_with_data(self, tmp_path: Path) -> Path:
        """Create configuration file with sample data."""
        config_file = tmp_path / "bench_config.json"
        config_data = {
            "TITLE": "Benchmark Test",
            "WIN_SIZE": [1024, 768],
            "WIN_POS": [100, 100],
            "SERVER_URL": "http://benchmark:8080",
            "MAX_TOKENS": 512,
            "TEMPERATURE": 0.8,
            "TOP_P": 0.95,
            "TOP_K": 50,
        }
        config_file.write_text(json.dumps(config_data))
        return config_file

    @pytest.mark.benchmark
    def test_benchmark_config_load(self, benchmark, config_file_with_data: Path):
        """Benchmark configuration loading from JSON file."""

        def load_config():
            with patch("pytola.llm.llmclient.gui.CONFIG_FILE", config_file_with_data):
                return LLMClientConfig()

        config = benchmark(load_config)
        assert config.SERVER_URL == "http://benchmark:8080"

    @pytest.mark.benchmark
    def test_benchmark_config_save(self, benchmark, tmp_path: Path):
        """Benchmark configuration saving to JSON file."""
        config_file = tmp_path / "save_bench.json"

        with patch("pytola.llm.llmclient.gui.CONFIG_FILE", config_file):
            config = LLMClientConfig()
            config.SERVER_URL = "http://saved:9999"

            benchmark(config.save)
            assert config_file.exists()

    @pytest.mark.benchmark
    def test_benchmark_config_load_with_defaults(self, benchmark, tmp_path: Path):
        """Benchmark loading default configuration when file doesn't exist."""

        def load_defaults():
            with patch("pytola.llm.llmclient.gui.CONFIG_FILE", tmp_path / "nonexistent.json"):
                return LLMClientConfig()

        config = benchmark(load_defaults)
        assert config.SERVER_URL == "http://localhost:8080"

    @pytest.mark.benchmark
    def test_benchmark_config_malformed_json(self, benchmark, tmp_path: Path):
        """Benchmark configuration handling of malformed JSON."""
        config_file = tmp_path / "malformed.json"
        config_file.write_text("{invalid json content}}}]")

        def load_malformed():
            with patch("pytola.llm.llmclient.gui.CONFIG_FILE", config_file):
                return LLMClientConfig()

        config = benchmark(load_malformed)
        # Should fall back to defaults
        assert config.SERVER_URL == "http://localhost:8080"


class TestBenchmarkWorkerInitialization:
    """Benchmark worker thread initialization and setup."""

    @pytest.mark.benchmark
    def test_benchmark_llm_worker_creation(self, benchmark):
        """Benchmark LLMWorker thread creation and initialization."""

        def create_worker():
            return LLMWorker(
                prompt="Benchmark test prompt",
                server_url="http://localhost:8080",
                max_tokens=256,
                temperature=0.7,
                top_p=0.9,
                top_k=40,
            )

        worker = benchmark(create_worker)
        assert worker.prompt == "Benchmark test prompt"

    @pytest.mark.benchmark
    def test_benchmark_connection_worker_creation(self, benchmark):
        """Benchmark ConnectionTestWorker creation."""

        def create_worker():
            return ConnectionTestWorker("http://localhost:8080")

        worker = benchmark(create_worker)
        assert worker.server_url == "http://localhost:8080"

    @pytest.mark.benchmark
    def test_benchmark_multiple_worker_creation(self, benchmark):
        """Benchmark creating multiple worker instances."""

        def create_multiple_workers():
            workers = []
            for i in range(10):
                worker = LLMWorker(
                    prompt=f"Test {i}",
                    server_url="http://localhost:8080",
                    max_tokens=256,
                    temperature=0.7,
                    top_p=0.9,
                    top_k=40,
                )
                workers.append(worker)
            return workers

        workers = benchmark(create_multiple_workers)
        assert len(workers) == 10


class TestBenchmarkStreamProcessing:
    """Benchmark stream processing and JSON parsing."""

    @pytest.fixture
    def mock_streaming_response(self):
        """Create mock streaming response with SSE data."""
        return [
            b'data: {"content": "Hello"}\n',
            b'data: {"content": " world"}\n',
            b'data: {"content": "!"}\n',
            b'data: {"content": " This"}\n',
            b'data: {"content": " is"}\n',
            b'data: {"content": " a"}\n',
            b'data: {"content": " test"}\n',
            b'data: {"content": "."}\n',
        ]

    @pytest.mark.benchmark
    @patch("pytola.llm.llmclient.gui.urlopen")
    def test_benchmark_stream_processing(self, mock_urlopen, benchmark, mock_streaming_response):
        """Benchmark processing of SSE streaming response."""
        mock_response = MagicMock()
        mock_response.status = 200
        mock_response.__iter__ = Mock(return_value=iter(mock_streaming_response))
        mock_response.__enter__ = Mock(return_value=mock_response)
        mock_response.__exit__ = Mock(return_value=False)
        mock_urlopen.return_value = mock_response

        worker = LLMWorker(
            prompt="Benchmark",
            server_url="http://localhost:8080",
            max_tokens=100,
            temperature=0.5,
            top_p=0.8,
            top_k=30,
        )

        responses = []
        worker.response_received.connect(lambda text: responses.append(text))

        benchmark(worker.run)
        assert len(responses) > 0

    @pytest.mark.benchmark
    @patch("pytola.llm.llmclient.gui.urlopen")
    def test_benchmark_large_stream_processing(self, mock_urlopen, benchmark):
        """Benchmark processing of large SSE streaming response."""
        # Generate large response with 1000 chunks
        large_response = [f'data: {{"content": "chunk_{i}"}}\n'.encode() for i in range(1000)]

        mock_response = MagicMock()
        mock_response.status = 200
        mock_response.__iter__ = Mock(return_value=iter(large_response))
        mock_response.__enter__ = Mock(return_value=mock_response)
        mock_response.__exit__ = Mock(return_value=False)
        mock_urlopen.return_value = mock_response

        worker = LLMWorker(
            prompt="Large benchmark",
            server_url="http://localhost:8080",
            max_tokens=2048,
            temperature=0.5,
            top_p=0.8,
            top_k=30,
        )

        responses = []
        worker.response_received.connect(lambda text: responses.append(text))

        benchmark(worker.run)
        assert len(responses) == 1000

    @pytest.mark.benchmark
    def test_benchmark_json_parsing(self, benchmark):
        """Benchmark JSON parsing performance in stream processing."""
        json_strings = [
            '{"content": "test"}',
            '{"content": "hello world"}',
            '{"content": "benchmark test data"}',
        ] * 100  # 300 JSON strings

        def parse_json_batch():
            results = []
            for json_str in json_strings:
                try:
                    data = json.loads(json_str)
                    results.append(data.get("content", ""))
                except json.JSONDecodeError:
                    continue
            return results

        results = benchmark(parse_json_batch)
        assert len(results) == 300


class TestBenchmarkConnectionTesting:
    """Benchmark connection testing operations."""

    @pytest.mark.benchmark
    @patch("pytola.llm.llmclient.gui.urlopen")
    def test_benchmark_successful_connection_test(self, mock_urlopen, benchmark):
        """Benchmark successful health check connection test."""
        mock_response = MagicMock()
        mock_response.status = 200
        mock_response.__enter__ = Mock(return_value=mock_response)
        mock_response.__exit__ = Mock(return_value=False)
        mock_urlopen.return_value = mock_response

        def run_connection_test():
            worker = ConnectionTestWorker("http://localhost:8080")
            worker.run()
            return True

        result = benchmark(run_connection_test)
        assert result is True

    @pytest.mark.benchmark
    @patch("pytola.llm.llmclient.gui.urlopen")
    def test_benchmark_failed_connection_test(self, mock_urlopen, benchmark):
        """Benchmark failed connection test with URLError."""
        from urllib.error import URLError

        mock_urlopen.side_effect = URLError("Connection refused")

        def run_failed_test():
            worker = ConnectionTestWorker("http://localhost:8080")
            worker.run()
            return True

        result = benchmark(run_failed_test)
        assert result is True

    @pytest.mark.benchmark
    def test_benchmark_multiple_connection_tests(self, benchmark):
        """Benchmark creating multiple connection test workers."""

        def create_multiple_tests():
            workers = []
            for i in range(50):
                worker = ConnectionTestWorker(f"http://server{i}.test:8080")
                workers.append(worker)
            return workers

        workers = benchmark(create_multiple_tests)
        assert len(workers) == 50


class TestBenchmarkErrorHandling:
    """Benchmark error handling and recovery operations."""

    @pytest.mark.benchmark
    @patch("pytola.llm.llmclient.gui.urlopen")
    def test_benchmark_http_error_handling(self, mock_urlopen, benchmark):
        """Benchmark handling of HTTP error responses."""
        mock_response = MagicMock()
        mock_response.status = 500
        mock_response.read.return_value = b"Internal Server Error"
        mock_response.__enter__ = Mock(return_value=mock_response)
        mock_response.__exit__ = Mock(return_value=False)
        mock_urlopen.return_value = mock_response

        def run_error_test():
            worker = LLMWorker(
                prompt="Error test",
                server_url="http://localhost:8080",
                max_tokens=100,
                temperature=0.5,
                top_p=0.8,
                top_k=30,
            )
            worker.run()
            return True

        result = benchmark(run_error_test)
        assert result is True

    @pytest.mark.benchmark
    @patch("pytola.llm.llmclient.gui.urlopen")
    def test_benchmark_malformed_json_recovery(self, mock_urlopen, benchmark):
        """Benchmark recovery from malformed JSON in stream."""
        # Generate large response with 1000 lines (40% invalid JSON)
        mixed_response = [
            b'data: {"content": "valid1"}\n',
            b"data: {invalid json}\n",
            b'data: {"content": "valid2"}\n',
            b"data: {more invalid}\n",
            b'data: {"content": "valid3"}\n',
        ] * 20

        mock_response = MagicMock()
        mock_response.status = 200
        mock_response.__iter__ = Mock(return_value=iter(mixed_response))
        mock_response.__enter__ = Mock(return_value=mock_response)
        mock_response.__exit__ = Mock(return_value=False)
        mock_urlopen.return_value = mock_response

        def run_recovery_test():
            worker = LLMWorker(
                prompt="Recovery test",
                server_url="http://localhost:8080",
                max_tokens=100,
                temperature=0.5,
                top_p=0.8,
                top_k=30,
            )
            worker.run()
            return True

        result = benchmark(run_recovery_test)
        assert result is True


class TestBenchmarkMemoryUsage:
    """Benchmark memory efficiency of operations."""

    @pytest.mark.benchmark
    def test_benchmark_config_memory_footprint(self, benchmark, tmp_path: Path):
        """Benchmark memory usage of configuration objects."""
        config_file = tmp_path / "memory_config.json"

        def create_configs():
            configs = []
            with patch("pytola.llm.llmclient.gui.CONFIG_FILE", config_file):
                for _ in range(100):
                    config = LLMClientConfig()
                    configs.append(config)
            return configs

        configs = benchmark(create_configs)
        assert len(configs) == 100

    @pytest.mark.benchmark
    def test_benchmark_worker_memory_footprint(self, benchmark):
        """Benchmark memory usage of worker objects."""

        def create_workers():
            workers = []
            for i in range(100):
                worker = LLMWorker(
                    prompt=f"Memory test {i}",
                    server_url="http://localhost:8080",
                    max_tokens=256,
                    temperature=0.7,
                    top_p=0.9,
                    top_k=40,
                )
                workers.append(worker)
            return workers

        workers = benchmark(create_workers)
        assert len(workers) == 100
