import json
import uuid
from abc import ABC, abstractmethod
from typing import Any

from edpauthorization.dto import AuthorizationPrincipal, AuthorizationRequest
from edpauthorization.exception import MissingPrincipalException
from edpauthorization.service import AuthorizationService
from .dto import CedarUidEntity
from .utils import (
    CedarJSONEncoder,
    build_action_ref,
    build_correlation_id,
    build_principal_entity,
    build_resource_entity,
    get_entity_ref,
)


class BaseCedarAuthorizationService(AuthorizationService, ABC):
    def __init__(self, schema_namespace: str = None):
        self.schema_namespace = schema_namespace

    @abstractmethod
    def _call_is_authorized(self, request: dict, entities: list[CedarUidEntity]) -> bool:
        raise NotImplementedError()

    @abstractmethod
    def _call_is_authorized_batch(self, requests: list[dict], entities: list[CedarUidEntity]) -> bool:
        raise NotImplementedError()

    def _is_authorized(self, principal: AuthorizationPrincipal, request: AuthorizationRequest) -> bool:
        if principal is None:
            raise MissingPrincipalException()

        principal_entity, entities = self.build_entities(principal)

        authz_request = self.build_resource_request(
            principal=principal,
            principal_entity=principal_entity,
            request=request,
            entities=entities,
        )

        return self._call_is_authorized(
            request=authz_request,
            entities=entities,
        )

    def _are_authorized(self, principal: AuthorizationPrincipal, requests: list[AuthorizationRequest]) -> bool:
        if len(requests) == 0:
            return True

        if principal is None:
            raise MissingPrincipalException()
        principal_entity, entities = self.build_entities(principal)

        batch_id = str(uuid.uuid4())
        authz_requests: list[dict] = []

        for request in requests:
            authz_requests.append(
                self.build_resource_request(
                    principal=principal,
                    principal_entity=principal_entity,
                    request=request,
                    entities=entities,
                    batch_id=batch_id,
                )
            )

        return self._call_is_authorized_batch(
            requests=authz_requests,
            entities=entities,
        )

    def build_entities(self, principal: AuthorizationPrincipal) -> tuple[CedarUidEntity, list]:
        principal_entity = build_principal_entity(
            schema_namespace=self.schema_namespace,
            principal=principal,
        )
        entities: list = [principal_entity]

        return principal_entity, entities

    def build_resource_request(
        self,
        principal: AuthorizationPrincipal,
        principal_entity: CedarUidEntity,
        request: AuthorizationRequest,
        entities: list[CedarUidEntity],
        batch_id: str | None = None,
    ) -> dict:
        resource_entity = build_resource_entity(
            schema_namespace=self.schema_namespace,
            request=request,
        )

        entities.append(resource_entity)

        authorization_request = {
            "principal": self.get_entity_ref(principal_entity),
            "action": self.build_action_ref(request=request),
            "resource": self.get_entity_ref(resource_entity),
            "context": request["context"],
        }

        if batch_id is not None:
            authorization_request["correlationId"] = build_correlation_id(batch_id=batch_id, request=request)

        return authorization_request

    def build_action_ref(self, request: AuthorizationRequest):
        return build_action_ref(
            schema_namespace=self.schema_namespace,
            action_name=request["action_name"],
        )

    def get_entity_ref(self, entity: CedarUidEntity):
        return get_entity_ref(entity)

    @staticmethod
    def get_encoded_json(payload: Any) -> str:
        return json.dumps(payload, cls=CedarJSONEncoder)
