import inspect
from typing import List, Union, Optional

from ldap3.core.exceptions import LDAPException
from perun.connector.models.Resource import Resource

from perun.connector.adapters.AdapterInterface import AdapterInterface
from perun.connector.adapters.LdapAdapter import AdapterSkipException
from perun.connector.adapters.LdapAdapter import LdapAdapter, LDAPNotExistsException
from perun.connector.adapters.PerunRpcAdapter import (
    PerunRpcAdapter,
    RPCAdapterNotExistsException,
)
from perun.connector.models.Facility import Facility
from perun.connector.models.Group import Group
from perun.connector.models.Member import Member
from perun.connector.models.User import User
from perun.connector.models.UserExtSource import UserExtSource
from perun.connector.models.VO import VO
from perun.connector.perun_openapi import ApiException
from perun.connector.perun_openapi.exceptions import NotFoundException
from perun.connector.utils.Logger import Logger


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


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


class AdaptersManager(AdapterInterface):
    def __init__(self, config, attrs_map):
        self._logger = Logger.get_logger(self.__class__.__name__)
        self._STARTING_PRIORITY = 1
        self.adapters = {}

        adapters_info = config["adapters"]

        for adapter_info in adapters_info:
            config_data = adapter_info.copy()
            adapter_type = config_data.pop("type")
            priority = config_data.pop("priority")

            if adapter_type == "ldap":
                ldap_adapter = LdapAdapter(config_data, attrs_map)

                self.adapters[priority] = {
                    "name": "ldap_adapter",
                    "adapter": ldap_adapter,
                }
            elif adapter_type == "openApi":
                rpc_adapter = PerunRpcAdapter(config_data, attrs_map)

                self.adapters[priority] = {
                    "name": "rpc_adapter",
                    "adapter": rpc_adapter,
                }
            else:
                self._logger.warning(
                    f'Config file includes unsupported adapter type "'
                    f'{adapter_type}"'
                )

    def _execute_method_by_priority(self, method_name: str, *args):
        current_priority = self._STARTING_PRIORITY
        current_adapter = self.adapters.get(current_priority)

        while current_adapter is not None:
            adapter_impl = current_adapter["adapter"]
            try:
                return getattr(adapter_impl, method_name)(*args)
            except AdapterSkipException as e:
                self._logger.warning(
                    f'{e.message} Method: "{method_name}" Adapter: '
                    f'{current_adapter["name"]} Going to try another '
                    f"adapter if available."
                )
                current_priority += 1
                current_adapter = self.adapters.get(current_priority)
            except (ApiException, LDAPException) as ex:
                if (ex.body and "NotExistsException" in ex.body) or isinstance(
                    ex, NotFoundException
                ):
                    raise AdaptersManagerNotExistsException(ex.body)
                self._logger.warning(
                    f'Method "{method_name}" could not be executed '
                    f'successfully by {current_adapter["name"]}, exception '
                    f'occurred: "{ex}" Going to try another '
                    f"adapter if available."
                )
                current_priority += 1
                current_adapter = self.adapters.get(current_priority)
            except (LDAPNotExistsException, RPCAdapterNotExistsException) as ex:
                raise AdaptersManagerNotExistsException(ex.body)

        raise AdaptersManagerException(
            f'None of the provided adapters was able to resolve method "'
            f'{method_name}"'
        )

    def _get_caller_name(self):
        return inspect.stack()[1].function

    def get_perun_user(self, idp_id: str, uids: List[str]) -> Optional[User]:
        return self._execute_method_by_priority(self._get_caller_name(), idp_id, uids)

    def get_group_by_name(self, vo: Union[int, VO], name: str) -> Group:
        return self._execute_method_by_priority(self._get_caller_name(), vo, name)

    def get_vo(self, short_name="", vo_id=None) -> VO:
        return self._execute_method_by_priority(
            self._get_caller_name(), short_name, vo_id
        )

    def get_member_groups(
        self, user: Union[int, User], vo: Union[int, VO]
    ) -> List[Group]:
        return self._execute_method_by_priority(self._get_caller_name(), user, vo)

    def get_sp_groups_by_facility(self, facility: Union[Facility, int]) -> List[Group]:
        return self._execute_method_by_priority(self._get_caller_name(), facility)

    def get_sp_groups_by_rp_id(self, rp_id: str) -> List[Group]:
        return self._execute_method_by_priority(self._get_caller_name(), rp_id)

    def get_user_attributes(
        self, user: Union[int, User], attr_names: List[str]
    ) -> dict[str, Union[str, Optional[int], bool, List[str], dict[str, str]]]:
        return self._execute_method_by_priority(
            self._get_caller_name(), user, attr_names
        )

    def get_entityless_attribute(
        self, attr_name: str
    ) -> Union[str, Optional[int], bool, List[str], dict[str, str]]:
        return self._execute_method_by_priority(self._get_caller_name(), attr_name)

    def get_vo_attributes(
        self, vo: Union[int, VO], attr_names: List[str]
    ) -> dict[str, Union[str, Optional[int], bool, List[str], dict[str, str]]]:
        return self._execute_method_by_priority(self._get_caller_name(), vo, attr_names)

    def get_facility_by_rp_identifier(self, rp_identifier: str) -> Facility:
        return self._execute_method_by_priority(self._get_caller_name(), rp_identifier)

    def get_users_groups_on_facility_by_rp_id(
        self, rp_identifier: str, user: Union[User, int]
    ) -> List[Group]:
        return self._execute_method_by_priority(
            self._get_caller_name(), rp_identifier, user
        )

    def get_users_groups_on_facility(
        self, facility: Union[Facility, int], user: Union[User, int]
    ) -> List[Group]:
        return self._execute_method_by_priority(self._get_caller_name(), facility, user)

    def get_facilities_by_attribute_value(
        self, attribute: dict[str, str]
    ) -> List[Facility]:
        return self._execute_method_by_priority(self._get_caller_name(), attribute)

    def get_facility_attributes(
        self, facility: Union[int, Facility], attr_names: List[str]
    ) -> dict[str, Union[str, Optional[int], bool, List[str], dict[str, str]]]:
        return self._execute_method_by_priority(
            self._get_caller_name(), facility, attr_names
        )

    def get_user_ext_source(
        self, ext_source_name: str, ext_source_login: str
    ) -> UserExtSource:
        return self._execute_method_by_priority(
            self._get_caller_name(), ext_source_name, ext_source_login
        )

    def update_user_ext_source_last_access(self, user_ext_source: str) -> None:
        return self._execute_method_by_priority(
            self._get_caller_name(), user_ext_source
        )

    def get_user_ext_source_attributes(
        self, user_ext_source: Union[int, UserExtSource], attr_names: List[str]
    ) -> dict[str, Union[str, Optional[int], bool, List[str], dict[str, str]]]:
        return self._execute_method_by_priority(
            self._get_caller_name(), user_ext_source, attr_names
        )

    def set_user_ext_source_attributes(
        self,
        user_ext_source: Union[int, UserExtSource],
        attributes: dict[
            str, Union[str, Optional[int], bool, List[str], dict[str, str]]
        ],
    ) -> None:
        return self._execute_method_by_priority(
            self._get_caller_name(), user_ext_source, attributes
        )

    def get_member_status_by_user_and_vo(
        self, user: Union[int, User], vo: Union[int, VO]
    ) -> str:
        return self._execute_method_by_priority(self._get_caller_name(), user, vo)

    def is_user_in_vo_by_short_name(
        self, user: Union[int, User], vo_short_name: str
    ) -> bool:
        return self._execute_method_by_priority(
            self._get_caller_name(), user, vo_short_name
        )

    def get_resource_capabilities_by_facility(
        self, facility: Union[Facility, int], user_groups: List[Union[Group, int]]
    ) -> List[str]:
        return self._execute_method_by_priority(
            self._get_caller_name(), facility, user_groups
        )

    def get_resource_capabilities_by_rp_id(
        self, rp_identifier: str, user_groups: List[Union[Group, int]]
    ) -> List[str]:
        return self._execute_method_by_priority(
            self._get_caller_name(), rp_identifier, user_groups
        )

    def get_facility_capabilities_by_rp_id(self, rp_identifier: str) -> List[str]:
        return self._execute_method_by_priority(self._get_caller_name(), rp_identifier)

    def get_facility_capabilities_by_facility(
        self, facility: Union[Facility, int]
    ) -> List[str]:
        return self._execute_method_by_priority(self._get_caller_name(), facility)

    def get_groups_where_member_is_active(
        self, member: Union[Member, int]
    ) -> List[Group]:
        return self._execute_method_by_priority(self._get_caller_name(), member)

    def get_groups_where_user_as_member_is_active(
        self, user: Union[User, int], vo: Union[VO, int]
    ) -> List[Group]:
        return self._execute_method_by_priority(self._get_caller_name(), user, vo)

    def has_registration_form_group(self, group: Union[Group, int]) -> bool:
        return self._execute_method_by_priority(self._get_caller_name(), group)

    def has_registration_form_vo(self, vo: Union[VO, int]) -> bool:
        return self._execute_method_by_priority(self._get_caller_name(), vo)

    def has_registration_form_by_vo_short_name(self, vo_short_name: str) -> bool:
        return self._execute_method_by_priority(self._get_caller_name(), vo_short_name)

    def create_facility(self, name: str, description="") -> Facility:
        return self._execute_method_by_priority(
            self._get_caller_name(), name, description
        )

    def set_facility_attributes(
        self,
        facility: Union[Facility, int],
        attributes: dict[
            str, Union[str, Optional[int], bool, List[str], dict[str, str]]
        ],
    ) -> None:
        return self._execute_method_by_priority(
            self._get_caller_name(), facility, attributes
        )

    def get_attributes_definition(self) -> List[dict[str, Union[str, int, bool]]]:
        return self._execute_method_by_priority(self._get_caller_name())

    def get_member_by_user(
        self, user: Union[User, int], vo: Union[VO, int]
    ) -> Optional[Member]:
        return self._execute_method_by_priority(self._get_caller_name(), user, vo)

    def is_user_perun_admin(self, user: Union[User, int]) -> bool:
        return self._execute_method_by_priority(self._get_caller_name(), user)

    def get_vos_where_user_is_admin(
        self, user: Union[User, int], check_perun_admin=True
    ) -> List[VO]:
        return self._execute_method_by_priority(
            self._get_caller_name(), user, check_perun_admin
        )

    def get_groups_where_user_is_admin(
        self,
        user: Union[User, int],
        check_perun_admin=True,
        fill_group_unique_name=True,
    ) -> List[Group]:
        return self._execute_method_by_priority(
            self._get_caller_name(), user, check_perun_admin, fill_group_unique_name
        )

    def get_facilities_where_user_is_admin(
        self, user: Union[User, int], check_perun_admin=True, fill_facility_rp_id=True
    ) -> List[Facility]:
        return self._execute_method_by_priority(
            self._get_caller_name(), user, check_perun_admin, fill_facility_rp_id
        )

    def get_resources_where_user_is_admin(
        self, user: Union[User, int], check_perun_admin=True, fill_facility_rp_id=True
    ) -> List[Resource]:
        return self._execute_method_by_priority(
            self._get_caller_name(), user, check_perun_admin, fill_facility_rp_id
        )

    def get_all_vos(self) -> List[VO]:
        return self._execute_method_by_priority(self._get_caller_name())

    def get_all_facilities(self, fill_rp_ids=True) -> List[Facility]:
        return self._execute_method_by_priority(self._get_caller_name(), fill_rp_ids)

    def get_groups_where_user_is_active_resource(
        self,
        user: Union[User, int],
        resource: Union[Resource, int],
        map_unique_name=True,
    ) -> List[Group]:
        return self._execute_method_by_priority(
            self._get_caller_name(), user, resource, map_unique_name
        )

    def get_facility_by_id(self, facility_id: int) -> Facility:
        return self._execute_method_by_priority(self._get_caller_name(), facility_id)

    def get_resources_for_facility(
        self, facility: Union[Facility, int], map_vo=True
    ) -> List[Resource]:
        return self._execute_method_by_priority(
            self._get_caller_name(), facility, map_vo
        )

    def get_groups_for_resource(
        self, resource: Union[Resource, int], map_unique_name=True
    ) -> List[Group]:
        return self._execute_method_by_priority(
            self._get_caller_name(), resource, map_unique_name
        )

    def get_resource_by_id(
        self, resource_id: int, map_vo=True, map_facility=True
    ) -> Resource:
        return self._execute_method_by_priority(
            self._get_caller_name(), resource_id, map_vo, map_facility
        )

    def get_all_parent_groups_for_group(
        self, group: Union[Group, int], map_unique_name=True
    ) -> List[Group]:
        return self._execute_method_by_priority(
            self._get_caller_name(), group, map_unique_name
        )
