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

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

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

import httpx

import domolibrary_extensions.client as dec

# %% ../../nbs/factory/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/factory/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: str = 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/factory/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/factory/factory.ipynb 18
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"""

    logs: FactoryLogs
    config_id_col: str
    location: str
    factory_fn_ls: List[Callable]

    session: httpx.AsyncClient = field(repr=False)

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

        factory_fn_ls = factory_fn_ls or self.factory_fn_ls

        for fn in factory_fn_ls:

            if debug_run:
                print(f"running - {fn.__name__}")
    
            await fn(config=self, debug_api=debug_api, fn_name = fn.__name__, **self.__dict__)

        return self

# %% ../../nbs/factory/factory.ipynb 19
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: str = 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/factory/factory.ipynb 20
def factory_function(func: Callable[..., Any]) -> Callable[..., Any]:

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

        logs = kwargs.pop("logs")
        config_id_col = kwargs.pop("config_id_col")
        location = kwargs.pop("location")
        config = kwargs.pop("config")
        func.__name__ = kwargs.pop('fn_name')

        for req in [logs, config_id_col, location]:
            if req is None:
                raise FactoryFunction_MissingParameter(req.__name__, func.__name__)

        id = getattr( config, config_id_col)

        res = FactoryResponse(
            function_name=func.__name__,
            id=id,
            location=location,
        )
        logs.add_response(res)

        result = await func(
            *args,
            config = config,
            res=res,
            logs=logs,
            location=location,
            **kwargs,
        )

        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
