# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/utils/factory.ipynb.

# %% auto 0
__all__ = ['FactoryMessage', 'FactoryResponse', 'FactoryLogs', 'FactoryFunction_MissingParameter', 'FactoryConfig',
           'FactoryFunction_ResponseTypeError', 'FactoryFunction_Error', 'FactoryFunction_ResponseError',
           'FactoryFunction_NotSuccess', 'factory_function']

# %% ../../nbs/utils/factory.ipynb 2
from dataclasses import dataclass, field
from typing import Any, Callable, List
from functools import wraps

import httpx

import domolibrary_extensions.client as dec

# %% ../../nbs/utils/factory.ipynb 6
@dataclass
class FactoryMessage:
    stage: str  # description of the stage of a process
    message: str = "init"  # outcome
    stage_num: int = None
    is_success: bool = False

    """class for logging a stage of a process"""

    def to_json(self):
        return {key: value for key, value in self.__dict__.items() if value is not None}

    def to_string(self):
        string = " | ".join(
            [
                f"{key} - {str(value)}"
                for key, value in self.__dict__.items()
                if value is not None
            ]
        )

        if not self.is_success:
            string = f"💀 {string}"
        return string

    def __eq__(self, other):
        if not isinstance(other, FactoryMessage):
            return False
        return self.stage == other.stage

# %% ../../nbs/utils/factory.ipynb 10
@dataclass
class FactoryResponse:
    function_name: str
    id: str  # identify a set of log entries

    messages: List[FactoryMessage] = field(
        default_factory=lambda: []
    )  # capture intermediate steps of the factory_function
    response: Any = field(
        repr=False, default=None
    )  # final object to return from the factory_function
    is_success: bool = False
    location = None

    """Response class for handling logging of a factory function.
    Accumulates messages as the factory unfolds
    """

    def to_json(self) -> List[dict]:
        columns = [
            "location",
            "function_name",
            "stage_num",
            "stage",
            "id",
            "is_success",
            "message",
            "response",
        ]
        s = [{**self.__dict__, **msg.to_json()} for msg in self.messages]

        return [{col: obj[col] for col in columns if obj.get(col)} for obj in s]

    def add_message(self, message: FactoryMessage):
        if message in self.messages:
            print("message in messages", message)

        self.messages.append(message)

    def test_success(self):
        """tests if all factory_messages are successful"""
        self.is_success = all([message.is_success for message in self.messages])
        return self.is_success

    def __eq__(self, other):
        if not isinstance(other, FactoryResponse):
            return False

        return self.id == other.id and self.function_name == other.function_name

# %% ../../nbs/utils/factory.ipynb 14
@dataclass
class FactoryLogs:
    """factory logs are the complete logs of an entire factory or script"""

    logs: List[FactoryResponse] = field(default_factory=lambda: [])

    def add_response(self, res: FactoryResponse):
        if res not in self.logs:
            self.logs.append(res)

        return self.logs

    def to_json(self) -> List[dict]:
        return [message for log in self.logs for message in log.to_json()]

# %% ../../nbs/utils/factory.ipynb 17
class FactoryFunction_MissingParameter(Exception):
    def __init__(self, parameter, function_name):

        super().__init__(
            f"missing parameter - {parameter} - while calling {function_name}"
        )


@dataclass
class FactoryConfig:
    """create a dataclass which will have all the fields used in your config event"""

    auth: dec.Auth = field(repr=False)
    session: httpx.AsyncClient = field(repr=False)
    logs: FactoryLogs

    factory_fn_ls: List[Callable] = None

    def test_required_attr(self, attr_ls: List[str], function_name):
        for attr in attr_ls:
            if not getattr(self, attr):
                raise FactoryFunction_MissingParameter(attr, function_name)

    async def run(self, factory_fn_ls=None, debug_api: bool = False):

        factory_fn_ls = factory_fn_ls or self.factory_fn_ls

        for fn in factory_fn_ls:
            await fn(config=self, debug_api=debug_api, logs=self.logs)

        return self

# %% ../../nbs/utils/factory.ipynb 21
class FactoryFunction_ResponseTypeError(TypeError):
    """a function wrapped in `process_function` must return FactoryResponse class"""

    def __init__(self, result):
        super().__init__(
            f"Expected function to return an instance of FactoryResponse. Got {type(result)} instead.  Refactor function to return FactoryResponse class"
        )


class FactoryFunction_Error(Exception):
    """base class for capturing errors within a factory function"""

    def __init__(
        self, factory: FactoryResponse, message: FactoryMessage, location=None
    ):

        e = f"💀 | {factory.function_name } | {factory.id} | {message.stage} - {message.message}"

        if location:
            e = f"{e} | in {location}"

        super().__init__(e)


class FactoryFunction_ResponseError(Exception):
    def __init__(self, message):
        super().__init__(message)


class FactoryFunction_NotSuccess(Exception):
    def __init__(self, messages: List[FactoryMessage]):
        super().__init__(
            "\n".join(
                [message.to_string() for message in messages if not message.is_success]
            )
        )

# %% ../../nbs/utils/factory.ipynb 22
def factory_function(config_id_col, **kwargs):
    def inner(func):

        @wraps(func)
        async def wrapper(*args, config_id_col=config_id_col, **kwargs):

            if not kwargs.get("logs"):
                print(kwargs)
                raise FactoryFunction_MissingParameter("logs", func.__name__)

            logs = kwargs["logs"]

            res = FactoryResponse(
                function_name=func.__name__, id=getattr(kwargs["config"], config_id_col)
            )
            logs.add_response(res)

            result = await func(
                *args,
                **kwargs,
                res=res,
            )

            if not isinstance(result, FactoryResponse):
                raise FactoryFunction_ResponseTypeError(result)

            if not result.response:
                raise FactoryFunction_ResponseError(
                    "factory response must return res.response"
                )

            if not result.messages or len(result.messages) == 0:
                raise FactoryFunction_ResponseError(
                    "factory response must have messages"
                )

            result.test_success()

            if not result.is_success:
                raise FactoryFunction_NotSuccess(result.messages)

            return result

        return wrapper

    return inner
