# -*- coding: utf-8 -*-
from __future__ import annotations

import functools
import http
import types
import typing

from hotpocket_soa.exceptions.backend import BackendServiceError
from hotpocket_soa.exceptions.frontend import SOAError


class Service:
    def __getattribute__(self, name: str) -> typing.Any:
        result = super().__getattribute__(name)

        is_service_method = all((
            name.startswith('_') is False,
            hasattr(Service, name) is False,
            isinstance(result, types.MethodType),
            getattr(result, '__self__', None) is self,
        ))
        if is_service_method is True:
            @functools.wraps(result)
            def wrapped_result(*args, **kwargs):
                try:
                    return result(*args, **kwargs)
                except Exception as exception:
                    raise self.wrap_exception(exception) from exception

            return wrapped_result

        return result

    def get_error_class(self) -> type[SOAError]:
        return SOAError

    def wrap_exception(self, exception: Exception) -> SOAError:
        error_class = self.get_error_class()

        if isinstance(exception, error_class) is True:
            return typing.cast(SOAError, exception)

        result = error_class(
            http.HTTPStatus.IM_A_TEAPOT.value,
            'SOA Error',
            *exception.args,
        )

        if isinstance(exception, BackendServiceError) is True:
            baskend_error = typing.cast(BackendServiceError, exception)
            result = error_class(
                baskend_error.CODE.value,
                baskend_error.message,
                baskend_error.data,
                *baskend_error.args,
            )

        result.__cause__ = exception
        return result

    def call(self, *args, **kwargs) -> typing.Any:
        raise NotImplementedError('TODO')


class ProxyService(Service):
    def call(self, service: typing.Any, method: str, *args, **kwargs) -> typing.Any:
        handler = getattr(service, method, None)
        assert handler is not None, f'Unknown method: method=`{method}`'

        return handler(*args, **kwargs)
