"""
Show materialized configuration of tox environments.
"""
from __future__ import annotations

from textwrap import indent
from typing import Iterable

from colorama import Fore

from tox.config.cli.parser import ToxParser
from tox.config.loader.stringify import stringify
from tox.config.sets import ConfigSet
from tox.plugin import impl
from tox.session.cmd.run.common import env_run_create_flags
from tox.session.env_select import CliEnv, register_env_select_flags
from tox.session.state import State
from tox.tox_env.api import ToxEnv


@impl
def tox_add_option(parser: ToxParser) -> None:
    our = parser.add_command("config", ["c"], "show tox configuration", show_config)
    our.add_argument(
        "-k",
        nargs="+",
        help="list just configuration keys specified",
        dest="list_keys_only",
        default=[],
        metavar="key",
    )
    our.add_argument(
        "--core",
        action="store_true",
        help="show core options (by default is hidden unless -e ALL is passed)",
        dest="show_core",
    )
    register_env_select_flags(our, default=CliEnv())
    env_run_create_flags(our, mode="config")


def show_config(state: State) -> int:
    is_colored = state.conf.options.is_colored
    keys: list[str] = state.conf.options.list_keys_only
    is_first = True

    def _print_env(tox_env: ToxEnv) -> None:
        nonlocal is_first
        if is_first:
            is_first = False
        else:
            print("")
        print_section_header(is_colored, f"[testenv:{tox_env.conf.name}]")
        if not keys:
            print_key_value(is_colored, "type", type(tox_env).__name__)
        print_conf(is_colored, tox_env.conf, keys)

    show_everything = state.conf.options.env.is_all
    done: set[str] = set()
    for name in state.envs.iter(package=True):  # now go through selected ones
        done.add(name)
        _print_env(state.envs[name])

    # environments may define core configuration flags, so we must exhaust first the environments to tell the core part
    if show_everything or state.conf.options.show_core:
        print("")
        print_section_header(is_colored, "[tox]")
        print_conf(is_colored, state.conf.core, keys)
    return 0


def _colored(is_colored: bool, color: int, msg: str) -> str:
    return f"{color}{msg}{Fore.RESET}" if is_colored else msg


def print_section_header(is_colored: bool, name: str) -> None:
    print(_colored(is_colored, Fore.YELLOW, name))


def print_comment(is_colored: bool, comment: str) -> None:
    print(_colored(is_colored, Fore.CYAN, comment))


def print_key_value(is_colored: bool, key: str, value: str, multi_line: bool = False) -> None:
    print(_colored(is_colored, Fore.GREEN, key), end="")
    print(" =", end="")
    if multi_line:
        print("")
        value_str = indent(value, prefix="  ")
    else:
        print(" ", end="")
        value_str = value
    print(value_str)


def print_conf(is_colored: bool, conf: ConfigSet, keys: Iterable[str]) -> None:
    for key in keys if keys else conf:
        if key not in conf:
            continue
        key = conf.primary_key(key)
        try:
            value = conf[key]
            as_str, multi_line = stringify(value)
        except Exception as exception:  # because e.g. the interpreter cannot be found
            as_str, multi_line = _colored(is_colored, Fore.LIGHTRED_EX, f"# Exception: {exception!r}"), False
        if multi_line and "\n" not in as_str:
            multi_line = False
        print_key_value(is_colored, key, as_str, multi_line=multi_line)
    unused = conf.unused()
    if unused and not keys:
        print_comment(is_colored, f"# !!! unused: {', '.join(unused)}")
