# AUTOGENERATED! DO NOT EDIT! File to edit: notebooks/CLI_Helper.ipynb (unless otherwise specified).

__all__ = [
    "logger",
    "requires_auth_token",
    "humanize_date",
    "humanize_number",
    "humanize_size",
    "get_example_for_type",
    "get_example_output_format",
    "customize_output_format",
    "separate_integers_and_strings",
    "echo_formatted_output",
    "display_formated_table",
    "requires_otp",
]

# Cell

import logging
from typing import *

# Internal Cell

import os
from contextlib import contextmanager
import functools
import ast
import datetime as dt

import pandas as pd
import typer
import humanize
from tabulate import tabulate

from ..client import Client
from ..logger import get_logger, set_level
from ..constant import SERVER_URL, CLIENT_NAME, SERVICE_TOKEN

# Cell

logger = get_logger(__name__)

# Internal Cell


@contextmanager
def authenicate_user():
    Client(
        auth_token=os.environ[SERVICE_TOKEN], server=os.environ.get(SERVER_URL, None)
    )

    yield


# Cell


def requires_auth_token(func):
    @functools.wraps(func)
    def wrapper_decorator(*args, **kwargs):
        try:

            if ("debug" in kwargs) and kwargs["debug"]:
                set_level(logging.DEBUG)
            else:
                set_level(logging.WARNING)

            with authenicate_user():
                # Do something before
                return func(*args, **kwargs)
                # Do something after

        except KeyError as e:
            typer.echo(
                message=f"KeyError: The environment variable {e} is not set.", err=True
            )

            if f"'{SERVICE_TOKEN}'" in str(e):
                typer.echo(
                    f"\nPlease run the command '{CLIENT_NAME} token' to get the application token and set it in the "
                    f"environment variable `{SERVICE_TOKEN}`."
                )
                typer.echo(f"\nTry '{CLIENT_NAME} token --help' for help.")

            raise typer.Exit(code=1)

        except Exception as e:
            typer.echo(message=f"Error: {e}", err=True)
            if ("Invalid OTP" in str(e)) or ("OTP is required" in str(e)):
                raise ValueError(e)
            raise typer.Exit(code=1)

    return wrapper_decorator


# Cell


def humanize_date(s: pd.Series) -> pd.Series:
    return s.apply(
        lambda date: humanize.naturaltime(
            dt.datetime.now() - dt.datetime.strptime(date, "%Y-%m-%dT%H:%M:%S")  # type: ignore
        )
        if date
        else "None"
    )


# Cell


def humanize_number(s: pd.Series) -> pd.Series:
    return s.apply(
        lambda num: humanize.intcomma(int(num)) if pd.notna(num) else "unknown"
    )


# Cell


def humanize_size(s: pd.Series) -> pd.Series:
    return s.apply(
        lambda size: humanize.naturalsize(size) if pd.notna(size) else "unknown"
    )


# Cell


def get_example_for_type(xs: pd.Series) -> str:
    """Get example output for the given series

    Args:
        xs: Input series

    Returns:
        The valid formatting example for the series
    """
    if pd.api.types.is_float_dtype(xs):
        return "€{:,.2f}"
    if pd.api.types.is_integer_dtype(xs):
        return "{:,d}"

    return "{}"


# Cell


def get_example_output_format(df: pd.DataFrame) -> Dict[str, str]:
    """Get example output format for the dataframe

    Args:
        df: Input dataframe

    Returns:
        The example output format for the dataframe
    """
    return {c: get_example_for_type(df[c]) for c in df.columns}


# Cell


def customize_output_format(format_str: str, df: pd.DataFrame) -> pd.DataFrame:
    """Customize output format

    Args:
        format_str: A dict mapping of column names into their python format
        df: Input dataframe

    Returns:
        The formatted pandas DataFrame

    Raises:
        Error: If the formatting string is not a valid python expression
        Error: If invalid column name is passed
        Error: If invalid formatting string is passed
        Error: If wrong formatting is passed for a column
    """
    try:
        formatters = ast.literal_eval(format_str)
    except Exception as e:
        typer.echo(f"Not a valid python expression: {format_str}", err=True)
        typer.echo(
            f"An example of a valid formatting string: {get_example_output_format(df)}",
            err=True,
        )
        raise typer.Exit(code=1)

    if not isinstance(formatters, dict):
        typer.echo(f"The format string is not a dictionary: {formatters}", err=True)
        typer.echo(
            f"An example of a valid formatting string: {get_example_output_format(df)}",
            err=True,
        )
        raise typer.Exit(code=1)

    if not (set(formatters.keys()) <= set(df.columns)):
        typer.echo(
            f"The following columns are not valid: {set(formatters.keys()) - set(df.columns)}. Only the following columns are valid: {set(df.columns)}",
            err=True,
        )
        typer.echo(
            f"An example of a valid formatting string: {get_example_output_format(df)}",
            err=True,
        )
        raise typer.Exit(code=1)

    df_copy = df.copy()
    try:
        for k, v in formatters.items():
            df_copy[k] = df_copy[k].apply(lambda x: v.format(x))
    except Exception as e:
        typer.echo(f"Formatting is wrong for {k}: {v}", err=True)
        typer.echo(
            f"An example of a valid formatting string: {get_example_output_format(df)}",
            err=True,
        )
        raise typer.Exit(code=1)
    return df_copy[formatters.keys()]


# Cell


def separate_integers_and_strings(xs: List[str]) -> List[Union[int, str]]:
    """Seperate integers and strings from the list of strings

    Args:
        xs: List containing string inputs

    Returns:
        A list containing the integers and strings
    """
    return [int(v) if v.isdigit() else v for v in xs]


# Cell


def echo_formatted_output(df: pd.DataFrame):
    """Echo the formatted output to the terminal

    Args:
        df: Input DataFrame
    """
    if len(df.columns) > 1:
        typer.echo(tabulate(df, headers="keys", tablefmt="plain", showindex=False))
    else:
        single_col_results = df.iloc[:, 0].astype(str).to_list()
        typer.echo("\n".join(single_col_results))


# Cell


def display_formated_table(func):
    """A decorator function to format the CLI table output"""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):

        # Do something before
        result_dict = func(*args, **kwargs)
        # Do something after

        df = result_dict["df"]
        quite_column_name = (
            result_dict["quite_column_name"]
            if "quite_column_name" in result_dict
            else "uuid"
        )

        if kwargs["format"]:
            df = customize_output_format(kwargs["format"], df)
            echo_formatted_output(df)

        elif "quiet" in kwargs and kwargs["quiet"]:
            ids = df[quite_column_name].astype(str).to_list()
            typer.echo("\n".join(ids))

        else:
            typer.echo(
                tabulate(
                    result_dict["df"],
                    headers="keys",
                    tablefmt="plain",
                    showindex=False,
                    missingval="<none>",
                )
            )

    return wrapper


# Cell


def requires_otp(no_retries: int = 3):
    """A decorator function to prompt users to enter a valid otp"""

    def wrapper_fn(f):
        @functools.wraps(f)
        def new_wrapper(*args, **kwargs):
            # Non-Interactive mode
            if kwargs["otp"] is not None:
                try:
                    return f(*args, **kwargs)
                except Exception as e:
                    raise typer.Exit(code=1)
            else:
                # Interactive mode
                _activation_otp = None
                for i in range(no_retries):
                    if _activation_otp is not None:
                        kwargs["otp"] = _activation_otp
                    try:
                        return f(*args, **kwargs)
                    except ValueError as e:
                        _activation_otp = typer.prompt(
                            "Please enter the OTP displayed in the authenticator app"
                        )
                raise typer.Exit(code=1)

        return new_wrapper

    return wrapper_fn
