import json
from pathlib import Path

from cedarpy import AuthzResult, is_authorized, is_authorized_batch

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

from .._BaseCedarAuthorizationService import BaseCedarAuthorizationService
from ..dto import CedarUidEntity


class CedarpyAuthorizationServiceImpl(BaseCedarAuthorizationService):
    def __init__(
        self,
        permission_dir: str,
        schema_namespace: str | None = None,
    ):
        super().__init__(schema_namespace=schema_namespace)
        self.permission_dir = permission_dir
        self._loaded_policies = {}
        self._loaded_schema = None
        self._schema_loaded = False

    def _permission_dir_path(self, principal_type: str, resource_type: str) -> Path:
        return Path(self.permission_dir, principal_type, resource_type)

    def _get_schema_path(self) -> Path:
        return Path(self.permission_dir, "schema.json")

    def _load_schema(self) -> dict | None:
        if self._schema_loaded:
            return self._loaded_schema

        if self.schema_namespace is not None:
            schema_path = self._get_schema_path()

            if schema_path.exists() and schema_path.is_file():
                with open(schema_path, "r") as sf:
                    self._loaded_schema = json.loads(sf.read())

        self._schema_loaded = True
        return self._loaded_schema

    def _load_policies(self, principal_type: str, resource_type: str) -> str:
        policy_key = f"{principal_type}/{resource_type}"
        if policy_key not in self._loaded_policies:
            permission_dir_path = self._permission_dir_path(principal_type, resource_type)

            if not permission_dir_path.exists() or not permission_dir_path.is_dir():
                raise PolicyNotFoundException(f"Policy Resource dir for {resource_type} not found")

            content = []
            for permission_path in Path(permission_dir_path).iterdir():
                if permission_path.is_file():
                    with open(permission_path, "r") as f:
                        content.append(f.read())

            self._loaded_policies[policy_key] = "\n".join(content)

        return self._loaded_policies[policy_key]

    def get_loaded_policies(self) -> str:
        policies = self._loaded_policies.values()
        return "\n".join(policies)

    def _call_is_authorized(self, request: dict, entities: list[CedarUidEntity]) -> bool:
        schema = self._load_schema()

        authz_result = is_authorized(
            request=request,
            policies=self.get_loaded_policies(),
            entities=self.get_encoded_json(entities),
            schema=schema,
        )
        return authz_result.allowed

    def _call_is_authorized_batch(self, requests: list[dict], entities: list[CedarUidEntity]) -> bool:
        schema = self._load_schema()
        authz_results: list[AuthzResult] = is_authorized_batch(
            requests=requests,
            policies=self.get_loaded_policies(),
            entities=self.get_encoded_json(entities),
            schema=schema,
        )
        allowed = not any(result for result in authz_results if not result.allowed)
        return allowed

    def build_resource_request(
        self,
        principal: AuthorizationPrincipal,
        principal_entity: CedarUidEntity,
        request: AuthorizationRequest,
        entities: list[CedarUidEntity],
        batch_id: str | None = None,
    ) -> dict:
        self._load_policies(
            principal_type=principal["type"],
            resource_type=request["resource_type"],
        )
        return super().build_resource_request(
            principal=principal,
            principal_entity=principal_entity,
            request=request,
            entities=entities,
            batch_id=batch_id,
        )
