import logging
from abc import ABC, abstractmethod

from edpauthorization.dto import AuthorizationPrincipal, AuthorizationRequest
from edpauthorization.exception import MissingPrincipalException

logger = logging.getLogger("edpauthorization.service.AuthorizationService")


class AuthorizationService(ABC):
    @abstractmethod
    def _is_authorized(self, principal: AuthorizationPrincipal, request: AuthorizationRequest) -> bool:
        raise NotImplementedError()

    def _are_authorized(self, principal: AuthorizationPrincipal, requests: list[AuthorizationRequest]) -> bool:
        for request in requests:
            if not self._is_authorized(principal=principal, request=request):
                return False
        return True

    @staticmethod
    def _log_decision(
        principal: AuthorizationPrincipal,
        request: AuthorizationRequest,
        allowed: bool,
    ) -> None:
        principal = principal or {}
        request = request or {}
        logger.info(
            "authorization decision: allowed=%s principal_id=%s principal_type=%s action=%s resource_type=%s",
            allowed,
            principal.get("id"),
            principal.get("type"),
            request.get("action_name"),
            request.get("resource_type"),
        )

    def is_authorized(self, principal: AuthorizationPrincipal, request: AuthorizationRequest) -> bool:
        try:
            result = self._is_authorized(principal, request)
            self._log_decision(principal, request, result)
            return result
        except Exception as ex:
            return self.handle_exception_is_allowed(ex)

    def are_authorized(self, principal: AuthorizationPrincipal, requests: list[AuthorizationRequest]) -> bool:
        try:
            result = self._are_authorized(principal, requests)
            for request in requests:
                self._log_decision(principal, request, result)
            return result
        except Exception as ex:
            return self.handle_exception_is_allowed(ex)

    def handle_exception_is_allowed(self, exception: Exception) -> bool:
        raise exception
