import logging
import os
from pathlib import Path
from typing import Any, Final

from lang_main.config import load_toml_config

_has_py4cyto: bool = True
try:
    import py4cytoscape as p4c
except ImportError:
    _has_py4cyto = False

# ** external packages config
# ** Huggingface Hub caching
os.environ['HF_HUB_DISABLE_SYMLINKS_WARNING'] = 'set'

# ** py4cytoscape config
if _has_py4cyto:
    p4c.set_summary_logger(False)
    p4c.py4cytoscape_logger.detail_logger.setLevel('ERROR')
    p4c.py4cytoscape_logger.detail_logger.removeHandler(
        p4c.py4cytoscape_logger.detail_handler
    )
    p4c.py4cytoscape_logger.detail_logger.addHandler(logging.NullHandler())

# ** lang-main config
BASE_FOLDERNAME: Final[str] = 'lang-main'
CONFIG_FILENAME: Final[str] = 'lang_main_config.toml'
CYTO_STYLESHEET_FILENAME: Final[str] = r'cytoscape_config/lang_main.xml'
PREFER_INTERNAL_CONFIG: Final[bool] = False
pkg_dir = Path(__file__).parent
cfg_path_internal = (pkg_dir / CONFIG_FILENAME).resolve()
cyto_stylesheet_path = (pkg_dir / CYTO_STYLESHEET_FILENAME).resolve()


# ** load config data: internal/external
# look for external config first, if not found use internal one
def search_cwd(
    glob_pattern: str = CONFIG_FILENAME,
) -> Path | None:
    """Searches the current working directory and looks for files
    matching the glob pattern.
    Returns the first match encountered.

    Parameters
    ----------
    glob_pattern : str, optional
        pattern to look for, first match will be returned,
        by default CONFIG_FILENAME

    Returns
    -------
    Path | None
        Path if corresponding object was found, None otherwise
    """
    cfg_path: Path | None = None
    res = tuple(Path.cwd().glob(glob_pattern))
    if res:
        cfg_path = res[0]

    return cfg_path


def search_iterative(
    starting_path: Path,
    glob_pattern: str = CONFIG_FILENAME,
    stop_folder_name: str | None = None,
) -> Path | None:
    """Iteratively searches the parent directories of the starting path
    and look for files matching the glob pattern. The starting path is not
    searched, only its parents. Therefore the starting path can also point
    to a file. The folder in which it is placed in will be searched.
    Returns the first match encountered.

    Parameters
    ----------
    starting_path : Path
        non-inclusive starting path
    glob_pattern : str, optional
        pattern to look for, first match will be returned,
        by default CONFIG_FILENAME
    stop_folder_name : str, optional
        name of the last folder in the directory tree to search, by default None

    Returns
    -------
    Path | None
        Path if corresponding object was found, None otherwise
    """
    file_path: Path | None = None
    stop_folder_reached: bool = False
    for it in range(len(starting_path.parents)):
        search_path = starting_path.parents[it]  # do not look in library folder
        res = tuple(search_path.glob(glob_pattern))
        if res:
            file_path = res[0]
            break
        elif stop_folder_reached:
            break

        if stop_folder_name is not None and search_path.name == stop_folder_name:
            # library is placed inside a whole python installation for deployment
            # if this folder is reached, only look up one parent above
            stop_folder_reached = True

    return file_path


def search_base_path(
    starting_path: Path,
    stop_folder_name: str | None = None,
) -> Path | None:
    """Iteratively searches the parent directories of the starting path
    and look for folders matching the given name. If a match is encountered,
    the parent path will be returned.

    Example:
    starting_path = path/to/start/folder
    stop_folder_name = 'to'
    returned path = 'path/'

    Parameters
    ----------
    starting_path : Path
        non-inclusive starting path
    stop_folder_name : str, optional
        name of the last folder in the directory tree to search, by default None

    Returns
    -------
    Path | None
        Path if corresponding base path was found, None otherwise
    """
    stop_folder_path: Path | None = None
    base_path: Path | None = None
    for it in range(len(starting_path.parents)):
        search_path = starting_path.parents[it]  # do not look in library folder
        if stop_folder_name is not None and search_path.name == stop_folder_name:
            # library is placed inside a whole python installation for deployment
            # only look up to this folder
            stop_folder_path = search_path
            break

    if stop_folder_path is not None:
        base_path = stop_folder_path.parent

    return base_path


def load_cfg() -> dict[str, Any]:
    cfg_path: Path | None
    if PREFER_INTERNAL_CONFIG:
        cfg_path = cfg_path_internal
    else:
        cfg_path = search_cwd(glob_pattern=CONFIG_FILENAME)

        if cfg_path is None:
            cfg_path = search_iterative(
                starting_path=pkg_dir,
                glob_pattern=CONFIG_FILENAME,
                stop_folder_name='python',
            )
        # backup: use internal config
        if cfg_path is None:
            cfg_path = cfg_path_internal

    config = load_toml_config(path_to_toml=cfg_path)

    return config.copy()


CONFIG: Final[dict[str, Any]] = load_cfg()
base_parent_path = search_base_path(pkg_dir, stop_folder_name=BASE_FOLDERNAME)
if base_parent_path is None:
    raise FileNotFoundError('Could not resolve base path of library')
BASE_PATH: Final[Path] = base_parent_path


# ** Cytoscape configuration
# stylesheet
if not cyto_stylesheet_path.exists():
    raise FileNotFoundError(
        f'Visual stylesheet for Cytoscape not found under: >>{cyto_stylesheet_path}<<'
    )

CYTO_PATH_STYLESHEET: Final[Path] = cyto_stylesheet_path


# TODO check removal
# append Graphviz binary folder to system path if not already contained
# if sys.platform == 'win32':
#     path = Path(r'C:\Program Files\Graphviz\bin')
#     if path.is_dir() and str(path).lower() not in os.environ['PATH'].lower():
#         os.environ['PATH'] += f';{path}'
