"""Configuration of the plugin."""
import itertools
import logging
from pathlib import Path
from shutil import rmtree
from typing import Any, Dict, MutableMapping, Optional

import toml

from flake8_nitpick.constants import (
    LOG_ROOT,
    NITPICK_MINIMUM_VERSION_JMEX,
    PROJECT_NAME,
    ROOT_FILES,
    ROOT_PYTHON_FILES,
    TOOL_NITPICK_JMEX,
)
from flake8_nitpick.files.pyproject_toml import PyProjectTomlFile
from flake8_nitpick.files.setup_cfg import SetupCfgFile
from flake8_nitpick.generic import climb_directory_tree, rmdir_if_empty, search_dict, version_to_tuple
from flake8_nitpick.mixin import NitpickMixin
from flake8_nitpick.style import Style
from flake8_nitpick.types import JsonDict, PathOrStr, StrOrList, YieldFlake8Error

LOGGER = logging.getLogger(f"{LOG_ROOT}.config")


class NitpickConfig(NitpickMixin):
    """Plugin configuration, read from the project config."""

    error_base_number = 200

    _singleton_instance: Optional["NitpickConfig"] = None

    def __init__(self) -> None:
        """Init instance."""
        self.root_dir: Optional[Path] = None
        self.cache_dir: Optional[Path] = None
        self.main_python_file: Optional[Path] = None

        self.pyproject_dict: MutableMapping[str, Any] = {}
        self.tool_nitpick_dict: Dict[str, Any] = {}
        self.style_dict: MutableMapping[str, Any] = {}
        self.nitpick_dict: MutableMapping[str, Any] = {}
        self.files: Dict[str, Any] = {}

    @classmethod
    def get_singleton(cls) -> "NitpickConfig":
        """Init the global singleton instance of the plugin configuration, needed by all file checkers."""
        if cls._singleton_instance is None:
            cls._singleton_instance = cls()
        return cls._singleton_instance

    @classmethod
    def reset_singleton(cls):
        """Reset the singleton instance. Useful on automated tests, to simulate ``flake8`` execution."""
        cls._singleton_instance = None

    def find_root_dir(self, starting_file: PathOrStr) -> bool:
        """Find the root dir of the Python project: the dir that has one of the `ROOT_FILES`.

        Also clear the cache dir the first time the root dir is found.
        """
        if self.root_dir:
            return True

        found_files = climb_directory_tree(
            starting_file, ROOT_FILES + (PyProjectTomlFile.file_name, SetupCfgFile.file_name)
        )
        if not found_files:
            LOGGER.error("No files found while climbing directory tree from %s", str(starting_file))
            return False

        self.root_dir = found_files[0].parent
        self.clear_cache_dir()
        return True

    def find_main_python_file(self) -> bool:
        """Find the main Python file in the root dir, the one that will be used to report Flake8 warnings."""
        if not self.root_dir:
            return False
        for the_file in itertools.chain(
            [self.root_dir / root_file for root_file in ROOT_PYTHON_FILES], self.root_dir.glob("*.py")
        ):
            if the_file.exists():
                self.main_python_file = Path(the_file)
                LOGGER.info("Found the file %s", the_file)
                return True
        return False

    def clear_cache_dir(self) -> None:
        """Clear the cache directory (on the project root or on the current directory)."""
        if not self.root_dir:
            return
        cache_root: Path = self.root_dir / ".cache"
        self.cache_dir = cache_root / PROJECT_NAME
        rmtree(str(self.cache_dir), ignore_errors=True)
        rmdir_if_empty(cache_root)

    def merge_styles(self) -> YieldFlake8Error:
        """Merge one or multiple style files."""
        pyproject_path: Path = self.root_dir / PyProjectTomlFile.file_name
        if pyproject_path.exists():
            self.pyproject_dict: JsonDict = toml.load(str(pyproject_path))
            self.tool_nitpick_dict: JsonDict = search_dict(TOOL_NITPICK_JMEX, self.pyproject_dict, {})

        configured_styles: StrOrList = self.tool_nitpick_dict.get("style", "")
        style = Style(self)
        style.find_initial_styles(configured_styles)
        self.style_dict = style.merge_toml_dict()

        from flake8_nitpick.plugin import NitpickChecker

        minimum_version = search_dict(NITPICK_MINIMUM_VERSION_JMEX, self.style_dict, None)
        if minimum_version and version_to_tuple(NitpickChecker.version) < version_to_tuple(minimum_version):
            yield self.flake8_error(
                3,
                f"The style file you're using requires {PROJECT_NAME}>={minimum_version}"
                + f" (you have {NitpickChecker.version}). Please upgrade",
            )

        self.nitpick_dict: JsonDict = self.style_dict.get("nitpick", {})
        self.files = self.nitpick_dict.get("files", {})
