"""
This module provides the `EnvPath` class, which includes
methods to work with the PATH environment variable.
"""

from .file_sys import FileSys

from typing import Optional, cast
from pathlib import Path
import sys as _sys
import os as _os


class EnvPath:
    """This class includes methods to work with the PATH environment variable."""

    @classmethod
    def paths(cls, as_list: bool = False) -> Path | list[Path]:
        """Get the PATH environment variable.\n
        ------------------------------------------------------------------------------------------------
        - `as_list` -⠀if true, returns the paths as a list of `Path`s; otherwise, as a single `Path`"""
        paths_str = _os.environ.get("PATH", "")
        if as_list:
            return [Path(path) for path in paths_str.split(_os.pathsep) if path]
        return Path(paths_str)

    @classmethod
    def has_path(cls, path: Optional[Path | str] = None, cwd: bool = False, base_dir: bool = False) -> bool:
        """Check if a path is present in the PATH environment variable.\n
        ------------------------------------------------------------------------
        - `path` -⠀the path to check for
        - `cwd` -⠀if true, uses the current working directory as the path
        - `base_dir` -⠀if true, uses the script's base directory as the path"""
        check_path = cls._get(path, cwd, base_dir).resolve()
        return check_path in {path.resolve() for path in cast(list[Path], cls.paths(as_list=True))}

    @classmethod
    def add_path(cls, path: Optional[Path | str] = None, cwd: bool = False, base_dir: bool = False) -> None:
        """Add a path to the PATH environment variable.\n
        ------------------------------------------------------------------------
        - `path` -⠀the path to add
        - `cwd` -⠀if true, uses the current working directory as the path
        - `base_dir` -⠀if true, uses the script's base directory as the path"""
        path_obj = cls._get(path, cwd, base_dir)
        if not cls.has_path(path_obj):
            cls._persistent(path_obj)

    @classmethod
    def remove_path(cls, path: Optional[Path | str] = None, cwd: bool = False, base_dir: bool = False) -> None:
        """Remove a path from the PATH environment variable.\n
        ------------------------------------------------------------------------
        - `path` -⠀the path to remove
        - `cwd` -⠀if true, uses the current working directory as the path
        - `base_dir` -⠀if true, uses the script's base directory as the path"""
        path_obj = cls._get(path, cwd, base_dir)
        if cls.has_path(path_obj):
            cls._persistent(path_obj, remove=True)

    @staticmethod
    def _get(path: Optional[Path | str] = None, cwd: bool = False, base_dir: bool = False) -> Path:
        """Internal method to get the normalized `path`, CWD path or script directory path.\n
        --------------------------------------------------------------------------------------
        Raise an error if no path is provided and neither `cwd` or `base_dir` is true."""
        if cwd:
            if base_dir:
                raise ValueError("Both 'cwd' and 'base_dir' cannot be True at the same time.")
            return FileSys.cwd
        elif base_dir:
            return FileSys.script_dir

        if path is None:
            raise ValueError("No path provided.\nPlease provide a 'path' or set either 'cwd' or 'base_dir' to True.")

        return Path(path) if isinstance(path, str) else path

    @classmethod
    def _persistent(cls, path: Path, remove: bool = False) -> None:
        """Internal method to add or remove a path from the PATH environment variable,
        persistently, across sessions, as well as the current session."""
        current_paths = cast(list[Path], cls.paths(as_list=True))
        path_resolved = path.resolve()

        if remove:
            # FILTER OUT THE PATH TO REMOVE
            current_paths = [path for path in current_paths if path.resolve() != path_resolved]
        else:
            # ADD THE NEW PATH IF NOT ALREADY PRESENT
            if path_resolved not in {path.resolve() for path in current_paths}:
                current_paths = [*current_paths, path_resolved]

        # CONVERT TO STRINGS ONLY FOR SETTING THE ENVIRONMENT VARIABLE
        path_strings = [str(path) for path in current_paths]
        _os.environ["PATH"] = new_path = _os.pathsep.join(dict.fromkeys(filter(bool, path_strings)))

        if _sys.platform == "win32":  # WINDOWS
            try:
                _winreg = __import__("winreg")
                key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Environment", 0, _winreg.KEY_ALL_ACCESS)
                _winreg.SetValueEx(key, "PATH", 0, _winreg.REG_EXPAND_SZ, new_path)
                _winreg.CloseKey(key)
            except Exception as e:
                raise RuntimeError("Failed to update PATH in registry:\n  " + str(e).replace("\n", "  \n"))

        else:  # UNIX-LIKE (LINUX/macOS)
            home_path = Path.home()
            bashrc = home_path / ".bashrc"
            zshrc = home_path / ".zshrc"
            shell_rc_file = bashrc if bashrc.exists() else zshrc

            with open(shell_rc_file, "r+") as file:
                content = file.read()
                file.seek(0)

                if remove:
                    new_content = [line for line in content.splitlines() if not line.endswith(f':{path_resolved}"')]
                    file.write("\n".join(new_content))
                else:
                    file.write(f"{content.rstrip()}\n# Added by 'xulbux'\n"
                               f'export PATH="{new_path}"\n')

                file.truncate()

            _os.system(f"source {shell_rc_file}")
