from __future__ import annotations

from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from django.utils.translation import gettext as _
from rest_framework.permissions import BasePermission, SAFE_METHODS
from rest_framework.request import Request
from rest_framework.exceptions import PermissionDenied
from saas_base.models import get_tenant_model, get_cached_tenant, Member
from saas_base.registry import perm_registry
from saas_base.registry.checker import has_permission, get_view_permission

__all__ = [
    'IsTenantOwner',
    'IsTenantOwnerOrReadOnly',
    'IsTenantActive',
    'IsTenantActiveOrReadOnly',
    'HasResourcePermission',
    'HasResourcePermissionOrReadOnly',
]

TenantModel = get_tenant_model()


class BaseTenantPermission(BasePermission):
    tenant_id_field = 'tenant_id'

    @staticmethod
    def check_tenant_permission(request, view, tenant_id):
        return True

    def has_permission(self, request: Request, view):
        if not request.user.is_authenticated:
            return False
        tenant_id = getattr(request, 'tenant_id', None)
        return self.check_tenant_permission(request, view, tenant_id)

    def has_object_permission(self, request, view, obj):
        # permission checked above
        tenant_id = getattr(request, 'tenant_id', None)
        if tenant_id:
            return True

        if hasattr(view, 'object_tenant_id_field'):
            tenant_id_field = view.object_tenant_id_field
        else:
            tenant_id_field = getattr(view, 'tenant_id_field', self.tenant_id_field)

        if tenant_id_field is None:
            return True

        tenant_id = getattr(obj, tenant_id_field, None)
        return self.check_tenant_permission(request, view, tenant_id)


class IsTenantOwner(BaseTenantPermission):
    """The authenticated user is the tenant owner."""

    @staticmethod
    def check_tenant_permission(request, view, tenant_id):
        if not tenant_id:
            return False

        member = get_requested_member(request, tenant_id)
        if member:
            # OWNER is a system role code
            return member.role == 'OWNER'

        tenant = get_cached_tenant(tenant_id, request._request)
        if not tenant:
            return False

        # this user is primary owner
        return request.user.pk == tenant.owner_id


class IsTenantOwnerOrReadOnly(IsTenantOwner):
    """The authenticated user is the tenant owner, or is a read-only request."""

    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        return super().has_permission(request, view)

    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True
        return super().has_object_permission(request, view, obj)


class IsTenantActive(BaseTenantPermission):
    """The requested tenant is not expired."""

    @staticmethod
    def check_tenant_permission(request, view, tenant_id):
        tenant = get_cached_tenant(tenant_id, request._request)
        if not tenant:
            return False

        if not tenant.expires_at:
            return True

        if tenant.expires_at < timezone.now():
            raise PermissionDenied(_('This tenant is expired.'))
        return True


class IsTenantActiveOrReadOnly(IsTenantActive):
    """The requested tenant is not expired, or is a read-only request."""

    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        return super().has_permission(request, view)

    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True
        return super().has_object_permission(request, view, obj)


class HasResourcePermission(BasePermission):
    """The authenticated user is a member of the tenant, and the user
    has the given resource permission.
    """

    tenant_id_field = 'tenant_id'

    def has_permission(self, request: Request, view):
        required_perm = get_view_permission(view, request.method)
        if not required_perm:
            return True

        if not request.user.is_authenticated:
            return False

        # filter scopes permissions
        if request.auth:
            scopes_perms = self.get_scopes_permissions(request.auth)
            if not has_permission(required_perm, scopes_perms):
                return False

        # this is a tenant related permission
        if not required_perm.startswith('user.'):
            tenant_id = getattr(request, 'tenant_id', None)
            return self.check_tenant_permission(request, required_perm, tenant_id)

        # user related permission has been checked above by token scopes permissions
        # if there is no `request.auth`, it is a session based authentication
        return True

    def has_object_permission(self, request, view, obj):
        # permission checked above
        tenant_id = getattr(request, 'tenant_id', None)
        if tenant_id:
            return True

        if hasattr(view, 'object_tenant_id_field'):
            tenant_id_field = view.object_tenant_id_field
        else:
            tenant_id_field = getattr(view, 'tenant_id_field', self.tenant_id_field)

        if tenant_id_field is None:
            return True

        # it is not related with tenant
        if not hasattr(obj, tenant_id_field):
            return True

        tenant_id = getattr(obj, tenant_id_field, None)
        return self.check_tenant_permission(request, view, tenant_id)

    @staticmethod
    def get_scopes_permissions(token):
        if hasattr(token, 'scope') and token.scope:
            scopes = token.scope.split(' ')
        elif hasattr(token, 'scopes'):
            scopes = token.scopes
        elif hasattr(token, 'get_scopes'):
            scopes = token.get_scopes()
        else:
            scopes = []

        if not scopes:
            return

        return perm_registry.get_permissions_for_scopes(scopes)

    @classmethod
    def check_tenant_permission(cls, request: Request, required_perm: str, tenant_id):
        if not tenant_id:
            return False

        tenant = get_cached_tenant(tenant_id, request._request)
        if not tenant:
            return False

        # Tenant owner has all permissions
        if tenant.owner_id == request.user.pk:
            return True

        member = get_requested_member(request, tenant_id)
        if not member:
            return False

        assigned_perms = member.get_all_permissions()
        return has_permission(required_perm, assigned_perms)


class HasResourcePermissionOrReadOnly(HasResourcePermission):
    """The authenticated user has the tenant permission, or is a read-only request."""

    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        return super().has_permission(request, view)

    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True
        return super().has_object_permission(request, view, obj)


def get_requested_member(request: Request, tenant_id):
    member = getattr(request, 'member', None)
    if member:
        return member

    try:
        return Member.objects.get_from_cache_by_natural_key(tenant_id, request.user.pk)
    except ObjectDoesNotExist:
        return None
