"""Pytest addopts manipulation for safe test execution."""

from __future__ import annotations

import configparser
import logging
from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING

import tomlkit

if TYPE_CHECKING:
    from collections.abc import Iterator

log = logging.getLogger(__name__)

BLACKLIST_ADDOPTS: tuple[str, ...] = (
    "--benchmark",
    "--sugar",
    "--codespeed",
    "--cov",
    "--profile",
    "--junitxml",
    "--dist",
    "--no-flaky-report",
    "--failed-first",
    "-n",
)

_ALL_CONFIG_FILES: dict[Path, dict[str, Path]] = {}


def find_closest_config_file(file_type: str) -> Path | None:
    """Walk up from cwd looking for *file_type*."""
    dir_path = Path.cwd()
    cur_path = dir_path
    if (
        cur_path in _ALL_CONFIG_FILES
        and file_type in _ALL_CONFIG_FILES[cur_path]
    ):
        return _ALL_CONFIG_FILES[cur_path][file_type]
    while dir_path != dir_path.parent:
        config_file = dir_path / file_type
        if config_file.exists():
            if cur_path not in _ALL_CONFIG_FILES:
                _ALL_CONFIG_FILES[cur_path] = {}
            _ALL_CONFIG_FILES[cur_path][file_type] = config_file
            return config_file
        dir_path = dir_path.parent
    return None


def get_all_closest_config_files() -> list[Path]:
    """Return all pytest config files found by walking up from cwd."""
    all_closest: list[Path] = []
    for file_type in [
        "pyproject.toml",
        "pytest.ini",
        ".pytest.ini",
        "tox.ini",
        "setup.cfg",
    ]:
        closest = find_closest_config_file(file_type)
        if closest:
            all_closest.append(closest)
    return all_closest


def filter_args(addopts_args: list[str]) -> list[str]:
    """Remove blacklisted pytest addopts arguments."""
    blacklist = BLACKLIST_ADDOPTS
    n = len(addopts_args)
    filtered_args: list[str] = []
    i = 0
    while i < n:
        current_arg = addopts_args[i]
        if current_arg.startswith(blacklist):
            i += 1
            if i < n and not addopts_args[i].startswith("-"):
                i += 1
        else:
            filtered_args.append(current_arg)
            i += 1
    return filtered_args


def _parse_toml_addopts(
    content: str,
) -> tuple[list[str], tomlkit.TOMLDocument, bool] | None:
    """Parse addopts from pyproject.toml, return (args, doc, uses_ini_options)."""
    data = tomlkit.parse(content)
    pytest_section = data.get("tool", {}).get("pytest", {})
    original_addopts = pytest_section.get("ini_options", {}).get(
        "addopts", ""
    ) or pytest_section.get("addopts", "")
    if original_addopts == "":
        return None
    uses_ini_options = (
        "ini_options" in pytest_section
        and "addopts" in pytest_section.get("ini_options", {})
    )
    if isinstance(original_addopts, list):
        original_addopts = " ".join(original_addopts)
    addopts_args = original_addopts.replace("=", " ").split()
    return addopts_args, data, uses_ini_options


def _ini_section_name(filename: str) -> str:
    """Return the config section name that holds pytest addopts."""
    if filename in {"pytest.ini", ".pytest.ini", "tox.ini"}:
        return "pytest"
    return "tool:pytest"


def modify_addopts(
    config_file: Path,
) -> tuple[str, bool]:
    """Modify addopts in *config_file*, return (original_content, was_modified)."""
    file_type = config_file.suffix.lower()
    filename = config_file.name
    if file_type not in {".toml", ".ini", ".cfg"} or not config_file.exists():
        return "", False
    with config_file.open(encoding="utf-8") as f:
        content = f.read()
    try:
        if filename == "pyproject.toml":
            parsed = _parse_toml_addopts(content)
            if parsed is None:
                return content, False
            addopts_args, data, uses_ini_options = parsed
        else:
            config = configparser.ConfigParser()
            config.read_string(content)
            section = _ini_section_name(filename)
            cfg_data: dict[str, dict[str, str]] = {
                s: dict(config[s]) for s in config.sections()
            }
            original = cfg_data.get(section, {}).get("addopts", "")
            addopts_args = original.replace("=", " ").split()
        new_addopts_args = filter_args(addopts_args)
        if new_addopts_args == addopts_args:
            return content, False
        new_value = " ".join(new_addopts_args)
        if file_type == ".toml":
            if uses_ini_options:
                data["tool"]["pytest"]["ini_options"]["addopts"] = new_value  # type: ignore[index]
            else:
                data["tool"]["pytest"]["addopts"] = new_value  # type: ignore[index]
            with config_file.open("w", encoding="utf-8") as f:
                f.write(tomlkit.dumps(data))
                return content, True
        section = _ini_section_name(filename)
        config.set(section, "addopts", new_value)
        with config_file.open("w", encoding="utf-8") as f:
            config.write(f)
            return content, True

    except Exception:  # noqa: BLE001
        log.debug("Trouble parsing")
        return content, False


@contextmanager
def custom_addopts() -> Iterator[None]:
    """Temporarily strip blacklisted addopts from all pytest config files."""
    closest_config_files = get_all_closest_config_files()

    original_content: dict[Path, tuple[str, bool]] = {}

    try:
        for config_file in closest_config_files:
            original_content[config_file] = modify_addopts(
                config_file,
            )
        yield

    finally:
        for file, (content, was_modified) in original_content.items():
            if was_modified:
                with file.open("w", encoding="utf-8") as f:
                    f.write(content)
