import threading
from typing import Any, Iterable

from edpauthorization.builder import build_service
from edpauthorization.dto import AuthorizationAction, AuthorizationPrincipal, AuthorizationRequest
from edpauthorization.exception import NotAuthorizedException
from edpauthorization.service import AuthorizationService
from edpauthorization.utils import get_dict_value, set_dict_value

_LOCAL_AUTHORIZATION_REGISTRY = threading.local()
_AUTHORIZATION_SERVICE = None

_SKIP_AUTHORIZATION = "skip_authorization"
_PRINCIPAL_FIELD = "principal"

_REQUEST_CONTEXT_FIELD = "context"
_RESOURCE_FIELD = "resource"
_RESOURCE_TYPE_FIELD = "resource_type"
_ACTION_NAME_FIELD = "action_name"
_HAS_REGISTRY = "has_registry"


_CURRENT_REQUEST_REGISTRY_FIELDS = [
    _REQUEST_CONTEXT_FIELD,
    _RESOURCE_FIELD,
    _RESOURCE_TYPE_FIELD,
    _ACTION_NAME_FIELD,
    _HAS_REGISTRY,
]

_REGISTRY_FIELDS = [
    *_CURRENT_REQUEST_REGISTRY_FIELDS,
    _PRINCIPAL_FIELD,
    _SKIP_AUTHORIZATION,
]


def _set_local_field(field_name: str, field_value: Any) -> None:
    setattr(_LOCAL_AUTHORIZATION_REGISTRY, field_name, field_value)


def _clear_registry(fields: list[str]):
    for field in fields:
        if hasattr(_LOCAL_AUTHORIZATION_REGISTRY, field):
            delattr(_LOCAL_AUTHORIZATION_REGISTRY, field)

def _clear_request_registry() -> None:
    _clear_registry(_CURRENT_REQUEST_REGISTRY_FIELDS)

def cleanup_authz_registry() -> None:
    _clear_registry(_REGISTRY_FIELDS)


def _get_local_field(field_name: str, default_value: Any | None = None) -> Any:
    if not hasattr(_LOCAL_AUTHORIZATION_REGISTRY, field_name):
        _set_local_field(field_name, default_value)
    return getattr(_LOCAL_AUTHORIZATION_REGISTRY, field_name)


def _set_has_registry():
    setattr(_LOCAL_AUTHORIZATION_REGISTRY, _HAS_REGISTRY, True)


def _has_registry() -> bool:
    return _get_local_field(_HAS_REGISTRY, False)


def set_skip_authorization() -> None:
    setattr(_LOCAL_AUTHORIZATION_REGISTRY, _SKIP_AUTHORIZATION, True)


def get_skip_authorization() -> bool:
    return _get_local_field(_SKIP_AUTHORIZATION, False)


def get_resources_attributes() -> dict:
    return _get_local_field(_RESOURCE_FIELD, {})


def add_to_iterable(iterable: Iterable, value: Any) -> None:
    if isinstance(iterable, list):
        if isinstance(value, Iterable):
            iterable.extend(value)
        else:
            iterable.append(value)
    if isinstance(iterable, set):
        if isinstance(value, Iterable):
            iterable.update(value)
        else:
            iterable.add(value)


def set_resource_attribute(
    resource_type: str,
    resource_field: str,
    resource_value: Any,
    default_value: Any = None,
) -> Any:
    if resource_value is None:
        return

    resources = get_resources_attributes()
    current_resource_value = get_dict_value(resources, [resource_type, resource_field], default_value)

    if isinstance(current_resource_value, Iterable):
        add_to_iterable(
            iterable=current_resource_value,
            value=resource_value,
        )

        if len(current_resource_value or []) > 0:
            set_dict_value(resources, [resource_type, resource_field], current_resource_value)
            _set_local_field(_RESOURCE_FIELD, resources)
            _set_has_registry()
    else:
        current_resource_value = resource_value
        set_dict_value(resources, [resource_type, resource_field], current_resource_value)
        _set_local_field(_RESOURCE_FIELD, resources)
        _set_has_registry()


def append_resource_attribute(
    resource_type: str,
    resource_field: str,
    resource_value: Any,
    default_value: Iterable | None = None,
) -> Any:
    set_resource_attribute(
        resource_type=resource_type,
        resource_field=resource_field,
        resource_value=resource_value,
        default_value=default_value or set(),
    )


def set_resource_attributes(resource_type: str, attributes: dict) -> Any:
    for resource_field, resource_value in attributes.items():
        set_resource_attribute(
            resource_type=resource_type,
            resource_field=resource_field,
            resource_value=resource_value,
        )


def get_request_context() -> dict:
    return _get_local_field(_REQUEST_CONTEXT_FIELD, {})


def add_request_context(
    context_field: str,
    context_value: Any,
    default_value: Any = None,
):
    contexts = get_request_context()
    current_value = get_dict_value(contexts, context_field, default_value)

    if isinstance(current_value, Iterable):
        add_to_iterable(
            iterable=current_value,
            value=context_value,
        )

    else:
        current_value = context_value

    contexts[context_field] = current_value
    _set_local_field(_REQUEST_CONTEXT_FIELD, contexts)
    _set_has_registry()


def add_request_context_attributes(attributes: dict):
    if attributes is None:
        return
    for context_field, context_value in attributes.items():
        add_request_context(
            context_field=context_field,
            context_value=context_value,
        )


def get_resource_type() -> str:
    return _get_local_field(_RESOURCE_TYPE_FIELD)


def set_resource_type(resource_type: str):
    if resource_type is not None:
        _set_local_field(_RESOURCE_TYPE_FIELD, resource_type)
        _set_has_registry()


def get_principal() -> AuthorizationPrincipal:
    return _get_local_field(_PRINCIPAL_FIELD)


def set_principal(principal: AuthorizationPrincipal):
    _set_local_field(_PRINCIPAL_FIELD, principal)


def get_action_name() -> str:
    return _get_local_field(_ACTION_NAME_FIELD)


def set_action_name(action_name: AuthorizationAction | str):
    if action_name is not None:
        _set_local_field(_ACTION_NAME_FIELD, str(action_name))
        _set_has_registry()


def _set_service(service: AuthorizationService):
    global _AUTHORIZATION_SERVICE
    _AUTHORIZATION_SERVICE = service


def _get_service() -> AuthorizationService:
    global _AUTHORIZATION_SERVICE
    if _AUTHORIZATION_SERVICE is None:
        _AUTHORIZATION_SERVICE = build_service()
    return _AUTHORIZATION_SERVICE


def initialize(**kwargs) -> AuthorizationService:
    _set_service(build_service(**kwargs))
    return _get_service()


def check_resource_is_authorized(request: AuthorizationRequest) -> bool:
    if get_skip_authorization():
        return True

    if not _get_service().is_authorized(
        principal=get_principal(),
        request=request,
    ):
        raise NotAuthorizedException()

    return True


def check_resources_are_authorized(requests: list[AuthorizationRequest]) -> bool:
    if get_skip_authorization():
        return True

    if len(requests) == 1:
        return check_resource_is_authorized(requests[0])
    elif not _get_service().are_authorized(
        principal=get_principal(),
        requests=requests,
    ):
        raise NotAuthorizedException()

    return True


def check_registry_authorized() -> bool:
    if not _has_registry() or get_skip_authorization():
        return True

    resource_type = get_resource_type()
    resources = get_resources_attributes()
    resource_attributes = {}
    if resource_type in resources:
        resource_attributes = resources[resource_type]

    requests: list[AuthorizationRequest] = [
        {
            "action_name": get_action_name(),
            "resource_type": resource_type,
            "resource": resource_attributes,
            "context": get_request_context(),
        }
    ]

    for access_resource_type, access_resource_attributes in resources.items():
        if access_resource_type == resource_type:
            continue

        requests.append(
            {
                "action_name": AuthorizationAction.ACCESS,
                "resource_type": access_resource_type,
                "resource": access_resource_attributes,
                "context": {},
            }
        )

    _clear_request_registry()
    if not check_resources_are_authorized(requests=requests):
        raise NotAuthorizedException()

    return True
