"""Benchmark tests for taskkill module."""

from __future__ import annotations

import subprocess
import time
from unittest.mock import MagicMock, Mock, patch

import pytest

from ..cli import (
    ProcessInfo,
    _get_process_list_unix,
    _get_process_list_windows,
    get_matched_process,
    get_process_list,
    kill_process,
)


@pytest.fixture
def large_process_list():
    """Large process list for benchmarking."""
    processes = []
    for i in range(1000):
        processes.append(ProcessInfo(name=f"process_{i}.exe", pid=str(1000 + i)))
    return processes


class TestTaskkillBenchmark:
    """Benchmark tests for taskkill performance."""

    @pytest.mark.benchmark(group="process_matching")
    def test_get_matched_process_small_list(self, benchmark, large_process_list):
        """Benchmark process matching with small pattern."""
        with patch("pytola.system.taskkill.cli._cached_process_list", large_process_list):
            result = benchmark(get_matched_process, "process_1*")
            assert len(result) > 0

    @pytest.mark.benchmark(group="process_matching")
    def test_get_matched_process_large_list(self, benchmark, large_process_list):
        """Benchmark process matching with large pattern."""
        with patch("pytola.system.taskkill.cli._cached_process_list", large_process_list):
            result = benchmark(get_matched_process, "*")
            assert len(result) == 1000

    @pytest.mark.benchmark(group="process_matching")
    def test_get_matched_process_no_matches(self, benchmark, large_process_list):
        """Benchmark process matching with no matches."""
        with patch("pytola.system.taskkill.cli._cached_process_list", large_process_list):
            result = benchmark(get_matched_process, "nonexistent*")
            assert len(result) == 0

    @pytest.mark.benchmark(group="process_list")
    def test_get_process_list_windows(self, benchmark):
        """Benchmark Windows process list retrieval."""
        mock_result = MagicMock()
        mock_result.stdout = '"python.exe","1234"\n"chrome.exe","5678"\n"notepad.exe","9012"\n' * 10
        mock_result.stderr = ""

        with patch("subprocess.run", return_value=mock_result), patch("sys.platform", "win32"):
            processes = []
            with patch("pytola.system.taskkill.cli._cached_process_list", processes):
                benchmark(_get_process_list_windows)
                # Just verify some processes were added
                assert len(processes) > 0

    @pytest.mark.benchmark(group="process_list")
    def test_get_process_list_unix(self, benchmark):
        """Benchmark Unix process list retrieval."""
        mock_result = MagicMock()
        mock_result.stdout = "1234 python\n5678 chrome\n9012 notepad\n" * 10
        mock_result.stderr = ""

        with patch("subprocess.run", return_value=mock_result), patch("sys.platform", "linux"):
            processes = []
            with patch("pytola.system.taskkill.cli._cached_process_list", processes):
                benchmark(_get_process_list_unix)
                # Just verify some processes were added
                assert len(processes) > 0

    @pytest.mark.benchmark(group="process_operations")
    def test_kill_process_single(self, benchmark):
        """Benchmark killing a single process."""
        processes = [ProcessInfo(name="test.exe", pid="1234")]

        with patch("pytola.system.taskkill.cli.get_process_list"), patch(
            "pytola.system.taskkill.cli.get_matched_process", return_value=processes
        ), patch("pytola.system.taskkill.cli._kill_process_by_pid", return_value=True), patch(
            "pytola.system.taskkill.cli.logger"
        ):
            benchmark(kill_process, "test.exe")

    @pytest.mark.benchmark(group="process_operations")
    def test_kill_process_multiple(self, benchmark):
        """Benchmark killing multiple processes."""
        processes = [
            ProcessInfo(name="test1.exe", pid="1234"),
            ProcessInfo(name="test2.exe", pid="5678"),
            ProcessInfo(name="test3.exe", pid="9012"),
        ]

        with patch("pytola.system.taskkill.cli.get_process_list"), patch(
            "pytola.system.taskkill.cli.get_matched_process", return_value=processes
        ), patch("pytola.system.taskkill.cli._kill_process_by_pid", return_value=True), patch(
            "pytola.system.taskkill.cli.logger"
        ):
            benchmark(kill_process, "test*")


class TestTaskkillPerformanceScenarios:
    """Performance scenario tests."""

    @pytest.mark.benchmark(group="matching_performance")
    @pytest.mark.parametrize("list_size", [100, 500, 1000])
    def test_matching_performance_scaling(self, list_size, benchmark):
        """Test how matching performance scales with list size."""
        processes = [ProcessInfo(name=f"proc_{i}.exe", pid=str(1000 + i)) for i in range(list_size)]

        with patch("pytola.system.taskkill.cli._cached_process_list", processes):

            def match_operation():
                return get_matched_process("proc_1*")

            result = benchmark(match_operation)
            # Calculate expected matches for pattern "proc_1*"
            # This matches: proc_1, proc_10-19, proc_100-199, etc.
            if list_size >= 200:
                # For larger lists, we get proc_1 + proc_10-19 + proc_100-199 = 111 matches
                expected_matches = (
                    111 if list_size >= 200 else (1 + min(9, list_size - 1) + max(0, min(100, list_size - 100)))
                )
            else:
                # For smaller lists, calculate precisely
                expected_matches = 1  # proc_1
                if list_size > 10:
                    expected_matches += min(10, list_size - 10)  # proc_10-19
                if list_size > 100:
                    expected_matches += min(100, list_size - 100)  # proc_100-199

            # Allow reasonable tolerance for pattern matching variations
            assert abs(len(result) - expected_matches) <= 5

    def test_cache_performance_benefit(self, large_process_list):
        """Test that caching provides performance benefit."""
        # Mock the actual process list retrieval to simulate real work
        mock_get_processes = Mock()
        mock_get_processes.side_effect = lambda: time.sleep(0.01)  # Simulate 10ms work

        # First call - no cache (simulates actual work)
        with patch("pytola.system.taskkill.cli._processes_cached", False), patch(
            "pytola.system.taskkill.cli._cached_process_list", large_process_list
        ), patch("sys.platform", "win32"), patch(
            "pytola.system.taskkill.cli._get_process_list_windows",
            mock_get_processes,
        ):
            start_time = time.perf_counter()
            get_process_list()
            first_call_time = time.perf_counter() - start_time

        # Verify the mock was called
        mock_get_processes.assert_called_once()

        # Reset mock for second call
        mock_get_processes.reset_mock()

        # Second call - with cache (should skip actual work)
        with patch("pytola.system.taskkill.cli._processes_cached", True), patch(
            "pytola.system.taskkill.cli._cached_process_list", large_process_list
        ):
            start_time = time.perf_counter()
            get_process_list()
            second_call_time = time.perf_counter() - start_time

        # Verify the mock was NOT called on cached call
        mock_get_processes.assert_not_called()

        # Cached call should be significantly faster (at least 5x faster)
        assert second_call_time < first_call_time * 0.2, (
            f"Cached call ({second_call_time:.6f}s) not significantly faster than first call ({first_call_time:.6f}s)"
        )
        assert first_call_time > 0.005, f"First call time too small: {first_call_time:.6f}s"
        assert second_call_time < 0.002, f"Second call time too large: {second_call_time:.6f}s"

    @pytest.mark.benchmark(group="error_handling")
    def test_error_handling_performance(self, benchmark):
        """Benchmark error handling performance."""
        with patch("subprocess.run", side_effect=subprocess.CalledProcessError(1, "tasklist")), patch(
            "sys.platform", "win32"
        ):
            processes = []
            with patch("pytola.system.taskkill.cli._cached_process_list", processes):

                def error_scenario():
                    _get_process_list_windows()
                    return len(processes)

                result = benchmark(error_scenario)
                assert result == 0  # No processes added on error


def test_concurrent_access_simulation():
    """Simulate concurrent access to process list."""
    processes = [ProcessInfo(name=f"app_{i}.exe", pid=str(1000 + i)) for i in range(100)]

    with patch("pytola.system.taskkill.cli._cached_process_list", processes):
        # Simulate multiple concurrent match operations
        results = []
        for pattern in ["app_1*", "app_2*", "app_*"]:
            matches = get_matched_process(pattern)
            results.append(len(matches))

        # Should have reasonable match counts
        assert all(count > 0 for count in results)
        assert sum(results) > 100  # Some overlap expected
