from typing import Optional, Callable
from zscams.agent.src.support.configuration import config
from zscams.agent.src.support.logger import get_logger

logger = get_logger("bootstrap")


class RequiredFieldException(Exception):
    pass


class InvalidFieldException(Exception):
    pass


def prompt(
    name: str,
    description: Optional[str] = None,
    required=False,
    startswith: Optional[list[str]] = None,
    one_of: Optional[list[str]] = None,
    fail_on_error=False,
    retries_count=3,
):
    _message = name

    if description:
        _message += f" {description}"

    if startswith:
        _message += f" [Must start with {'|'.join(startswith)}]"

    if one_of:
        _message += f" [Can be {'|'.join(one_of)}]"

    if not required:
        _message += " (Optional)"

    _message += ": "

    val = input(_message)

    if required and not val:
        if fail_on_error:
            raise RequiredFieldException(f"Missing {name}")
        logger.error("%s is required..", name)
        retries_count -= 1
        return prompt(
            name, description, required, startswith, fail_on_error=retries_count <= 0
        )

    if startswith:
        matches = next((start for start in startswith if val.startswith(start)), False)
        if not matches:
            error_mesagge = f"The value has to start with one of {startswith}"
            if fail_on_error:
                raise InvalidFieldException(error_mesagge)
            logger.error(error_mesagge)
            retries_count -= 1
            return prompt(
                name,
                description,
                required,
                one_of=one_of,
                startswith=startswith,
                fail_on_error=retries_count <= 0,
            )

    if one_of:
        matches = next((opt for opt in one_of if val == opt), False)
        if not matches:
            error_mesagge = f"The value has to be one of {one_of}"
            if fail_on_error:
                raise InvalidFieldException(error_mesagge)
            logger.error(error_mesagge)
            retries_count -= 1
            return prompt(
                name,
                description,
                required,
                one_of=one_of,
                startswith=startswith,
                fail_on_error=retries_count <= 0,
            )

    return val


def prompt_auth_info():
    """Prompt for username and totp"""

    username = prompt("Username", required=True)
    totp = prompt("One Time Password (OTP)", required=True)
    return username, totp


def ensure_config_value(
    config_key_path: str,
    prompt_message: str,
    prompt_desc: Optional[str] = None,
    process_value: Optional[Callable] = None,
):
    """
    Ensures a configuration value is set. If not, prompts the user and sets it.

    Args:
        config_key_path (list): List of keys representing the path in the config dict.
        prompt_message (str): Message to display in the prompt.
        default_value (str, optional): Default value for the prompt.
        process_value (callable, optional): Function to process the input value.
        append_path_segment (str, optional): Path segment to append if needed.
    """
    if config.has(config_key_path):
        return

    value = prompt(prompt_message, prompt_desc, required=True)

    if process_value:
        value = process_value(value)

    _config = config.to_dict()

    d = _config
    keys_list = config_key_path.split(".")
    for key in keys_list[:-1]:
        if key not in d:
            d[key] = {}
        d = d[key]

    d[keys_list[-1]] = value

    config.override_config(_config)
