import logging
from contextlib import suppress
from typing import Iterable

from google.protobuf.duration_pb2 import Duration
from google.protobuf.empty_pb2 import Empty
from yandex.cloud.access.access_pb2 import (
    AccessBinding,
    ListAccessBindingsRequest,
    SetAccessBindingsMetadata, SetAccessBindingsRequest, Subject,
)
from yandex.cloud.serverless.functions.v1.function_pb2 import (
    Function,
    Package,
    Resources,
    Version,
)
from yandex.cloud.serverless.functions.v1.function_service_pb2 import (
    CreateFunctionMetadata, CreateFunctionRequest,
    CreateFunctionVersionMetadata, CreateFunctionVersionRequest,
    DeleteFunctionMetadata, DeleteFunctionRequest,
    GetFunctionVersionByTagRequest, ListFunctionsRequest,
)
from yandex.cloud.serverless.functions.v1.function_service_pb2_grpc import \
    FunctionServiceStub

from yappa.utils import convert_size_to_bytes

logger = logging.getLogger(__name__)


class YcFunctionsMixin:
    sdk = None
    folder_id = None

    def get_function(self, name) -> Function:
        """
        convenience method to get deployed function
        """

        for f in self._get_functions():
            if f.name == name:
                return f
        raise ValueError(f"Oops. Didn't find any function by name {name}")

    def _get_functions(self, filter_=None) -> Iterable[Function]:
        functions = self.sdk.client(FunctionServiceStub).List(
            ListFunctionsRequest(folder_id=self.folder_id,
                                 filter=filter_)).functions
        return functions

    def create_function(self, name, description="",
                        is_public=True) -> Function:
        """
        returns function and if it is new function
        """
        with suppress(ValueError):
            function = self.get_function(name)
            return function, False
        operation = self.sdk.client(FunctionServiceStub).Create(
            CreateFunctionRequest(
                folder_id=self.folder_id,
                name=name,
                description=description,
            ))
        operation_result = self.sdk.wait_operation_and_get_result(
            operation,
            response_type=Function,
            meta_type=CreateFunctionMetadata,
        )
        function = operation_result.response
        self.set_function_access(function.id, is_public)
        return function, True

    def delete_function(self, function_name):
        function = self.get_function(function_name)
        operation = self.sdk.client(FunctionServiceStub).Delete(
            DeleteFunctionRequest(
                function_id=function.id
            ))
        operation_result = self.sdk.wait_operation_and_get_result(
            operation,
            response_type=Function,
            meta_type=DeleteFunctionMetadata,
        )
        return operation_result.response

    def set_function_access(self, function_id=None, is_public=True,
                            function_name=None):
        """
        returns if access has changed
        """
        if function_name and not function_id:
            function_id = self.get_function(function_name).id
        if self._is_function_public(function_id) == is_public:
            return False
        if is_public:
            access_bindings = [AccessBinding(
                role_id='serverless.functions.invoker',
                subject=Subject(
                    id="allUsers",
                    type="system",
                )
            )]
        else:
            access_bindings = []
        operation = self.sdk.client(FunctionServiceStub).SetAccessBindings(
            SetAccessBindingsRequest(
                resource_id=function_id,
                access_bindings=access_bindings
            ))
        self.sdk.wait_operation_and_get_result(
            operation,
            response_type=Empty,
            meta_type=SetAccessBindingsMetadata,
        )
        return True

    def _is_function_public(self, function_id) -> bool:
        access_bindings = self.sdk.client(
            FunctionServiceStub).ListAccessBindings(
            ListAccessBindingsRequest(
                resource_id=function_id
            )).access_bindings
        for binding in access_bindings:
            if binding.role_id == 'serverless.functions.invoker':
                subject = binding.subject
                return subject.id == "allUsers"
        return False

    def create_function_version(self, function_name, runtime, description,
                                bucket_name, object_name,
                                entrypoint,
                                memory="128MB", service_account_id=None,
                                timeout=None, named_service_accounts=None,
                                environment=None) -> Version:
        function = self.get_function(function_name)
        operation = self.sdk.client(FunctionServiceStub).CreateVersion(
            CreateFunctionVersionRequest(
                function_id=function.id,
                runtime=runtime,
                description=description,
                entrypoint=entrypoint,
                resources=Resources(
                    memory=convert_size_to_bytes(memory)),
                execution_timeout=Duration(seconds=timeout),
                service_account_id=service_account_id,
                package=Package(bucket_name=bucket_name,
                                object_name=object_name),
                named_service_accounts=named_service_accounts,
                environment=environment
            ))
        operation_result = self.sdk.wait_operation_and_get_result(
            operation,
            response_type=Version,
            meta_type=CreateFunctionVersionMetadata,
        )
        return operation_result.response

    def get_latest_version(self, function_id) -> Version:
        version = self.sdk.client(FunctionServiceStub).GetVersionByTag(
            GetFunctionVersionByTagRequest(
                function_id=function_id,
                tag="$latest",
            ))
        return version
