import ast
import logging
import sys

import click
from ruamel.yaml import YAML

from envied.core.config import config, get_config_path
from envied.core.constants import context_settings


@click.command(
    short_help="Manage configuration values for the program and its services.", context_settings=context_settings
)
@click.argument("key", type=str, required=False)
@click.argument("value", type=str, required=False)
@click.option("--unset", is_flag=True, default=False, help="Unset/remove the configuration value.")
@click.option("--list", "list_", is_flag=True, default=False, help="List all set configuration values.")
@click.pass_context
def cfg(ctx: click.Context, key: str, value: str, unset: bool, list_: bool) -> None:
    """
    Manage configuration values for the program and its services.

    \b
    Known Issues:
    - Config changes remove all comments of the changed files, which may hold critical data. (#14)
    """
    if not key and not value and not list_:
        raise click.UsageError("Nothing to do.", ctx)

    if value:
        try:
            value = ast.literal_eval(value)
        except (ValueError, SyntaxError):
            pass  # probably a str without quotes or similar, assume it's a string value

    log = logging.getLogger("cfg")

    yaml, data = YAML(), None
    yaml.default_flow_style = False

    config_path = get_config_path() or config.directories.user_configs / config.filenames.root_config
    if config_path.exists():
        data = yaml.load(config_path)

    if not data:
        log.warning("No config file was found or it has no data, yet")
        # yaml.load() returns `None` if the input data is blank instead of a usable object
        # force a usable object by making one and removing the only item within it
        data = yaml.load("""__TEMP__: null""")
        del data["__TEMP__"]

    if list_:
        yaml.dump(data, sys.stdout)
        return

    key_items = key.split(".")
    parent_key = key_items[:-1]
    trailing_key = key_items[-1]

    is_write = value is not None
    is_delete = unset
    if is_write and is_delete:
        raise click.ClickException("You cannot set a value and use --unset at the same time.")

    if not is_write and not is_delete:
        data = data.mlget(key_items, default=KeyError)
        if data is KeyError:
            raise click.ClickException(f"Key '{key}' does not exist in the config.")
        yaml.dump(data, sys.stdout)
    else:
        try:
            parent_data = data
            if parent_key:
                parent_data = data.mlget(parent_key, default=data)
                if parent_data == data:
                    for key in parent_key:
                        if not hasattr(parent_data, key):
                            parent_data[key] = {}
                        parent_data = parent_data[key]
            if is_write:
                parent_data[trailing_key] = value
                log.info(f"Set {key} to {repr(value)}")
            elif is_delete:
                del parent_data[trailing_key]
                log.info(f"Unset {key}")
        except KeyError:
            raise click.ClickException(f"Key '{key}' does not exist in the config.")
        config_path.parent.mkdir(parents=True, exist_ok=True)
        yaml.dump(data, config_path)
