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

from edpauthorization.dto import AuthorizationPrincipal, AuthorizationRequest
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:
        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

        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)
