import inspect
import logging
import os
import re
from functools import wraps
from pathlib import Path
from pprint import pformat
from textwrap import dedent, indent
from typing import Any, Callable
from uuid import uuid4

current_directory = Path(__file__).resolve().parent
logger = logging.getLogger("test-classifier")
logger.setLevel(level=logging.DEBUG)

# create console handler and set level to debug
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# formatter for console handler
console_formatter = logging.Formatter(
    "%(asctime)s - %(name)s - %(levelname)s: %(message)s"
)
console_handler.setFormatter(console_formatter)
# add console handler to logger
logger.addHandler(console_handler)

run_name_prompt = """
Create a random name that sounds german or dutch
The parts should be separated by underscores and contain only lowercase.
Only return the name without any text before or after.""".strip()

RUNS_DIR = current_directory / "runs"


def initialize_run_directory(model: Callable):
    response, usage = model(None, run_name_prompt, chat=True)
    run_name_match = re.search(r"^\w+$", response, re.MULTILINE)
    existing_run_names = os.listdir(RUNS_DIR)
    if run_name_match is None or run_name_match.group(0) in existing_run_names:
        run_name = uuid4().hex
    else:
        run_name = run_name_match.group(0)
    run_directory = RUNS_DIR / run_name
    run_directory.mkdir(parents=True, exist_ok=False)
    # create file handler and set level to debug
    file_handler = logging.FileHandler(run_directory / "output.log")
    file_handler.setLevel(logging.DEBUG)
    formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
    file_handler.setFormatter(formatter)
    # add file handler to logger
    logger.addHandler(file_handler)

    logger.info(f"Hello my name is {run_name} and I live in {run_directory}")
    return run_directory


class log_calls:
    description: str

    prolog = dedent(
        """
    ----------------------------------------
    {description}
    {func_name}:
        \tArguments:
        {arguments}
        """
    )

    epilog = dedent(
        """\tResult:
        {result}
    ----------------------------------------
    """
    )

    def __init__(self, description: str, level: int = logging.DEBUG):
        self.description = description
        self.level = level

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            arguments = self._get_named_arguments(func, *args, **kwargs)
            logger.log(
                self.level,
                self.prolog.format(
                    description=self.description,
                    func_name=func.__name__,
                    arguments=indent(pformat(arguments), "\t"),
                ),
            )

            result = func(*args, **kwargs)

            logger.log(
                self.level,
                self.epilog.format(
                    result=indent(pformat(result), "\t"),
                ),
            )
            return result

        return wrapper

    def _get_named_arguments(self, func: Callable[..., Any], *args, **kwargs):
        signature = inspect.signature(func)
        arguments = {}
        for argument_index, (argument_name, argument) in enumerate(
            signature.parameters.items()
        ):
            if argument_index < len(args):
                # argument_name is from args
                value = args[argument_index]
            elif argument_name in kwargs:
                # argument_name is from kwargs
                value = kwargs[argument_name]
            else:
                # argument_name is from defaults
                value = argument.default

            arguments[argument_name] = value
        return arguments