"""Test thread safety of browser and connection pool."""

from __future__ import annotations

import threading
import time
from unittest.mock import MagicMock, patch

from browser_hybrid import Browser
from browser_hybrid.connection import Connection


class TestConcurrency:
    @patch("browser_hybrid.browser.ConnectionPool")
    @patch("browser_hybrid.browser.urllib.request.urlopen")
    def test_concurrent_pool_initialization(self, mock_urlopen, mock_pool_class):
        """Test that _get_pool is thread-safe using double-checked locking."""
        # Setup mocks to succeed initial connection check
        mock_urlopen.return_value.__enter__.return_value.read.return_value = (
            b'{"Browser": "Chrome"}'
        )

        # Create pool instance after a small delay to induce race if lock is missing
        def delayed_pool_init(*args, **kwargs):
            time.sleep(0.1)
            return MagicMock()

        mock_pool_class.side_effect = delayed_pool_init

        browser = Browser(retry_attempts=0)

        # Collect initialized pools from different threads
        results = []

        def get_pool_task():
            results.append(browser._get_pool())

        threads = [threading.Thread(target=get_pool_task) for _ in range(10)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        # All threads should have received the exact same pool instance
        assert len(results) == 10
        first_pool = results[0]
        for pool in results[1:]:
            assert pool is first_pool

        # ConnectionPool should have been instantiated exactly once
        assert mock_pool_class.call_count == 1

    def test_concurrent_id_generation(self):
        """Test that Connection._next_id is thread-safe."""
        # Create a Connection instance (doesn't need to be connected for ID generation)
        conn = Connection(ws_url="ws://localhost:9222/test", timeout=10.0)

        ids = []
        lock = threading.Lock()

        def collect_ids():
            for _ in range(100):
                new_id = conn._next_id()
                with lock:
                    ids.append(new_id)

        threads = [threading.Thread(target=collect_ids) for _ in range(5)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        # We should have 500 unique IDs
        assert len(ids) == 500
        assert len(set(ids)) == 500, "Duplicate IDs found - _next_id is not thread-safe!"
        assert min(ids) == 1
        assert max(ids) == 500
