import logging
import os
import re
import sys
import typing

_SPACE_RE = re.compile(r"\s")
_WORD_EQUAL = re.compile(r"^-*[\w_\-]+=")
_DOUBLE_ESCAPEES = re.compile(r'([\n`$"\\])')
_REGULAR_ESCAPEES = re.compile(r"""([\s!"$()*+#;<>?@'\[\]\\`|~])""")
_DEFAULT_LOGGER: logging.Logger | None = None
_STDOUT_HANDLER: logging.StreamHandler[typing.Any] | None = None
_STDERR_HANDLER: logging.StreamHandler[typing.Any] | None = None
_LOGGING_SET_UP: bool = False


def _backslash_escape(m: re.Match[str]) -> str:
    return "\\" + m.group(0)


def _escape_shell_word(w: str) -> str:
    if _SPACE_RE.search(w):
        if "=" in w and (m := _WORD_EQUAL.search(w)) is not None:
            s = m.span(0)
            assert s[0] == 0
            prefix = w[0 : s[1]]
            escaped_value = _DOUBLE_ESCAPEES.sub(_backslash_escape, w[s[1] :])
            return f'{prefix}"{escaped_value}"'
        w = _DOUBLE_ESCAPEES.sub(_backslash_escape, w)
        return f'"{w}"'
    return _REGULAR_ESCAPEES.sub(_backslash_escape, w)


def escape_shell(*args: str) -> str:
    return " ".join(_escape_shell_word(w) for w in args)


def program_name() -> str:
    name = os.path.basename(sys.argv[0])
    if name.endswith(".py"):
        name = name[:-3]
    if name == "__main__":
        name = os.path.basename(os.path.dirname(sys.argv[0]))
    return name.replace("_", "-")


def _error(msg: str) -> typing.Never:
    global _DEFAULT_LOGGER
    logger = _DEFAULT_LOGGER
    if logger:
        logger.error(msg)
    else:
        me = program_name()
        print(
            f"{me}: error: {msg}",
            file=sys.stderr,
        )
    sys.exit(1)


def _check_color() -> tuple[bool, bool]:
    requested_color = "never" if "NO_COLOR" in os.environ else "auto"
    if requested_color == "auto":
        stdout_color = sys.stdout.isatty()
        stderr_color = sys.stdout.isatty()
    else:
        enable = requested_color == "always"
        stdout_color = enable
        stderr_color = enable
    return stdout_color, stderr_color


def setup_logging(
    *,
    initial_logging_level: int = logging.INFO,
    log_only_to_stderr: bool = False,
) -> logging.Logger:
    global _LOGGING_SET_UP, _DEFAULT_LOGGER, _STDOUT_HANDLER, _STDERR_HANDLER
    if _LOGGING_SET_UP:
        raise RuntimeError(
            "Logging has already been configured."
            " Use reconfigure_logging=True if you need to reconfigure it"
        )
    stdout_color, stderr_color = _check_color()
    colors: dict[str, str] | None = None

    if stdout_color or stderr_color:
        try:
            import colorlog

        except ImportError:
            stdout_color = False
            stderr_color = False

    if log_only_to_stderr:
        stdout = sys.stderr
        stdout_color = stderr_color
    else:
        stdout = sys.stdout

    class LogLevelFilter(logging.Filter):
        def __init__(self, threshold: int, above: bool):
            super().__init__()
            self.threshold = threshold
            self.above = above

        def filter(self, record: logging.LogRecord) -> bool:
            if self.above:
                return record.levelno >= self.threshold
            else:
                return record.levelno < self.threshold

    color_format = (
        "{bold}{name}{reset}: {bold}{log_color}{levelnamelower}{reset}: {message}"
    )
    colorless_format = "{name}: {levelnamelower}: {message}"

    existing_stdout_handler = _STDOUT_HANDLER
    existing_stderr_handler = _STDERR_HANDLER

    if stdout_color:
        stdout_handler = colorlog.StreamHandler(stdout)
        stdout_handler.setFormatter(
            colorlog.ColoredFormatter(
                color_format,
                style="{",
                force_color=True,
                log_colors=colors,
            )
        )
        logger = colorlog.getLogger()
        if existing_stdout_handler is not None:
            logger.removeHandler(existing_stdout_handler)
        _STDOUT_HANDLER = stdout_handler
        logger.addHandler(stdout_handler)
    else:
        stdout_handler = logging.StreamHandler(stdout)
        stdout_handler.setFormatter(logging.Formatter(colorless_format, style="{"))
        logger = logging.getLogger()
        if existing_stdout_handler is not None:
            logger.removeHandler(existing_stdout_handler)
        _STDOUT_HANDLER = stdout_handler
        logger.addHandler(stdout_handler)

    if stderr_color:
        stderr_handler = colorlog.StreamHandler(sys.stderr)
        stderr_handler.setFormatter(
            colorlog.ColoredFormatter(
                color_format,
                style="{",
                force_color=True,
                log_colors=colors,
            )
        )
        logger = logging.getLogger()
        if existing_stderr_handler is not None:
            logger.removeHandler(existing_stderr_handler)
        _STDERR_HANDLER = stderr_handler
        logger.addHandler(stderr_handler)
    else:
        stderr_handler = logging.StreamHandler(sys.stderr)
        stderr_handler.setFormatter(logging.Formatter(colorless_format, style="{"))
        logger = logging.getLogger()
        if existing_stderr_handler is not None:
            logger.removeHandler(existing_stderr_handler)
        _STDERR_HANDLER = stderr_handler
        logger.addHandler(stderr_handler)

    stdout_handler.addFilter(LogLevelFilter(logging.WARN, False))
    stderr_handler.addFilter(LogLevelFilter(logging.WARN, True))

    name = program_name()

    old_factory = logging.getLogRecordFactory()

    def record_factory(
        *args: typing.Any,
        **kwargs: typing.Any,
    ) -> logging.LogRecord:  # pragma: no cover
        record = old_factory(*args, **kwargs)
        record.levelname = record.levelname.lstrip("_")
        record.levelnamelower = record.levelname.lower()
        return record

    logging.setLogRecordFactory(record_factory)

    logging.getLogger().setLevel(initial_logging_level)
    default_logger = logging.getLogger(name)
    _DEFAULT_LOGGER = default_logger
    _LOGGING_SET_UP = True
    return default_logger
