# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
# pylint: disable=missing-module-docstring, missing-function-docstring

from __future__ import annotations

import contextlib
import os
import pathlib
import shlex
import sys
from collections.abc import Sequence
from io import BufferedReader
from typing import Any, Protocol
from unittest.mock import MagicMock, mock_open, patch

import pytest

from pylint import run_pylint, run_pyreverse, run_symilar
from pylint.testutils import GenericTestReporter as Reporter
from pylint.testutils._run import _Run as Run
from pylint.testutils.utils import _test_cwd


class _RunCallable(Protocol):  # pylint: disable=too-few-public-methods
    def __call__(self, argv: Sequence[str] | None = None) -> None: ...


@pytest.mark.parametrize("runner", [run_pylint, run_pyreverse, run_symilar])
def test_runner(runner: _RunCallable, tmp_path: pathlib.Path) -> None:
    filepath = os.path.abspath(__file__)
    testargs = ["", filepath]
    with _test_cwd(tmp_path):
        with patch.object(sys, "argv", testargs):
            with pytest.raises(SystemExit) as err:
                runner()
            assert err.value.code == 0


@pytest.mark.parametrize("runner", [run_pylint, run_pyreverse, run_symilar])
def test_runner_with_arguments(runner: _RunCallable, tmp_path: pathlib.Path) -> None:
    """Check the runners with arguments as parameter instead of sys.argv."""
    filepath = os.path.abspath(__file__)
    testargs = [filepath]
    with _test_cwd(tmp_path):
        with pytest.raises(SystemExit) as err:
            runner(testargs)
        assert err.value.code == 0


def test_pylint_argument_deduplication(
    tmp_path: pathlib.Path, tests_directory: pathlib.Path
) -> None:
    """Check that the Pylint runner does not over-report on duplicate
    arguments.

    See https://github.com/pylint-dev/pylint/issues/6242 and
    https://github.com/pylint-dev/pylint/issues/4053
    """
    filepath = str(tests_directory / "functional/t/too/too_many_branches.py")
    testargs = shlex.split("--report n --score n --max-branches 13")
    testargs.extend([filepath] * 4)
    exit_stack = contextlib.ExitStack()
    exit_stack.enter_context(_test_cwd(tmp_path))
    exit_stack.enter_context(patch.object(sys, "argv", testargs))
    err = exit_stack.enter_context(pytest.raises(SystemExit))
    with exit_stack:
        run_pylint(testargs)
    assert err.value.code == 0


def test_pylint_run_jobs_equal_zero_dont_crash_with_cpu_fraction(
    tmp_path: pathlib.Path,
) -> None:
    """Check that the pylint runner does not crash if `pylint.lint.run._query_cpu`
    determines only a fraction of a CPU core to be available.
    """
    builtin_open = open

    def _mock_open(*args: Any, **kwargs: Any) -> BufferedReader:
        if args[0] == "/sys/fs/cgroup/cpu/cpu.cfs_quota_us":
            return mock_open(read_data=b"-1")(*args, **kwargs)  # type: ignore[no-any-return]
        if args[0] == "/sys/fs/cgroup/cpu/cpu.shares":
            return mock_open(read_data=b"2")(*args, **kwargs)  # type: ignore[no-any-return]
        return builtin_open(*args, **kwargs)  # type: ignore[no-any-return]

    pathlib_path = pathlib.Path

    def _mock_path(*args: str, **kwargs: Any) -> pathlib.Path:
        if args[0] == "/sys/fs/cgroup/cpu/cpu.shares":
            return MagicMock(is_file=lambda: True)
        return pathlib_path(*args, **kwargs)

    filepath = os.path.abspath(__file__)
    testargs = [filepath, "--jobs=0"]
    with _test_cwd(tmp_path):
        with pytest.raises(SystemExit) as err:
            with patch("builtins.open", _mock_open):
                with patch("pylint.lint.run.Path", _mock_path):
                    Run(testargs, reporter=Reporter())
        assert err.value.code == 0
