from rest_framework.permissions import BasePermission


def invalid_organization_header_response(request):
    """
    If the client sent X-Organization-ID / X-Organization but it did not resolve,
    return a 400 Response. Otherwise None.
    """
    from rest_framework.response import Response
    from rest_framework import status as http_status

    if (
        getattr(request, 'organization_header_provided', False)
        and not getattr(request, 'organization_header_valid', True)
    ):
        return Response(
            {'detail': 'Invalid X-Organization header'},
            status=http_status.HTTP_400_BAD_REQUEST,
        )
    return None


def user_has_organization_membership(
    user,
    organization,
    organization_header_provided=False,
):
    """
    Check whether user may use this organization context.

    - Superuser: always allowed.
    - No organization on the request: allowed (other code decides scope).
    - Staff: if the org was chosen via X-Organization-ID / X-Organization,
      must have a Membership for that org (same as non-staff). If the org
      comes only from the user profile (no header), staff still bypasses
      Membership so existing admin workflows keep working.
    - Everyone else: Membership required when organization is set.
    """
    if not user or not user.is_authenticated:
        return False
    if user.is_superuser:
        return True
    if organization is None:
        return True
    if user.is_staff and not organization_header_provided:
        return True

    from organization.models import Membership
    return Membership.objects.filter(user=user, organization=organization).exists()


def resolve_effective_permission_strings(
    user,
    organization=None,
    strict_enabled_only=False,
    organization_header_provided=False,
):
    """
    Resolve effective permissions as ['app_label.codename', ...].

    - In org context, permissions come from Membership group assignments only
      (no fallback to global user/group perms for non-staff).
    - If strict_enabled_only=True (org-scoped auth payloads): whitelist only —
      include permissions whose content type has an OrganizationModelAccess row
      with is_enabled=True for this org. If none are enabled, return [].
    - With organization=None, uses direct user permissions plus all group perms.
    """
    if user.is_superuser:
        return ['*']

    if organization is None and user.is_staff:
        return ['*']

    permissions = set()

    if organization is not None:
        from organization.models import Membership, OrganizationModelAccess
        from django.contrib.auth.models import Permission

        if not user_has_organization_membership(
            user,
            organization,
            organization_header_provided=organization_header_provided,
        ):
            return []

        org_group_ids = list(
            Membership.objects.filter(
                user=user,
                organization=organization,
            ).values_list('group_id', flat=True)
        )

        perm_qs = Permission.objects.filter(group__id__in=org_group_ids).select_related('content_type')
        group_perm_qs = []

        enabled_content_type_ids = None
        if strict_enabled_only:
            enabled_content_type_ids = set(
                OrganizationModelAccess.objects.filter(
                    organization=organization,
                    is_enabled=True,
                    content_type__isnull=False,
                ).values_list('content_type_id', flat=True)
            )
            if not enabled_content_type_ids:
                return []

        for perm in perm_qs:
            if enabled_content_type_ids is not None and perm.content_type_id not in enabled_content_type_ids:
                continue
            permissions.add(f'{perm.content_type.app_label}.{perm.codename}')

        for perm in group_perm_qs:
            if enabled_content_type_ids is not None and perm.content_type_id not in enabled_content_type_ids:
                continue
            permissions.add(f'{perm.content_type.app_label}.{perm.codename}')
    else:
        for perm in user.user_permissions.all():
            permissions.add(f'{perm.content_type.app_label}.{perm.codename}')
        for group in user.groups.all():
            for perm in group.permissions.all():
                permissions.add(f'{perm.content_type.app_label}.{perm.codename}')

    return sorted(permissions)


def group_permissions_by_model(permission_strings):
    """
    Convert ['app.action_model', ...] into { model: ['view', 'add', ...] }.
    Superuser marker ['*'] becomes {'*': ['*']}.
    """
    if permission_strings == ['*']:
        return {'*': ['*']}

    result = {}
    for perm in permission_strings:
        if '.' not in perm:
            continue
        _app_label, codename = perm.split('.', 1)
        if '_' not in codename:
            continue
        action, model = codename.split('_', 1)
        if action not in {'view', 'add', 'change', 'delete'}:
            continue
        result.setdefault(model, [])
        if action not in result[model]:
            result[model].append(action)
    return result


def has_effective_model_action_permission(request, app_label, model_name, action):
    """
    Check if user has the specific action permission for app/model in current org.
    Mirrors DynamicModelPermission Layer 2 behavior.
    """
    codename = f'{action}_{model_name}'
    full_perm = f'{app_label}.{codename}'
    organization = getattr(request, 'organization', None)

    if organization is not None:
        from organization.models import Membership
        from django.contrib.auth.models import Permission

        if not user_has_organization_membership(
            request.user,
            organization,
            organization_header_provided=getattr(
                request, 'organization_header_provided', False
            ),
        ):
            return False

        org_group_ids = Membership.objects.filter(
            user=request.user,
            organization=organization,
        ).values_list('group_id', flat=True)

        if org_group_ids.exists():
            return Permission.objects.filter(
                group__id__in=org_group_ids,
                content_type__app_label=app_label,
                codename=codename,
            ).exists()

    return request.user.has_perm(full_perm)


class DynamicModelPermission(BasePermission):
    """
    2-layer permission check:

    Layer 1 — OrganizationModelAccess:
        Is this model enabled for the current organization?
        No record → allowed by default (open).

    Layer 2 — Django Group permissions (existing behavior):
        Does the user have the required Django permission for this HTTP method?
        Superusers bypass both layers.
    """

    METHOD_ACTION_MAP = {
        'GET':    'view',
        'POST':   'add',
        'PUT':    'change',
        'PATCH':  'change',
        'DELETE': 'delete',
    }

    def _get_action(self, request, view):
        """Determine the action being performed."""
        # Check for custom actions (decorated with @action)
        if hasattr(view, 'action') and view.action:
            action_map = {
                'list': 'view',
                'retrieve': 'view',
                'create': 'add',
                'update': 'change',
                'partial_update': 'change',
                'destroy': 'delete',
                'get_courses_for_routine': 'view'
            }
            # If it's a standard viewset action, map it.
            # Otherwise, use the action name directly (e.g., 'get_courses_for_routine')
            # but default to 'view' if it's a GET request, or 'add' if it's a POST.
            mapped_action = action_map.get(view.action)
            if mapped_action:
                return mapped_action
            
            # For custom actions like get_courses_for_routine, 
            # we check the request method to decide the required base permission.
            return self.METHOD_ACTION_MAP.get(request.method, 'view')
        return self.METHOD_ACTION_MAP.get(request.method, 'view')

    def _get_model_info(self, view):
        """Extract (app_label, model_name) from the ViewSet queryset."""
        try:
            model = view.queryset.model
            return model._meta.app_label, model._meta.model_name
        except AttributeError:
            return None, None

    def _layer1_org_access(self, organization, app_label, model_name):
        """
        Check Layer 1: is this model enabled for the organization?
        Returns True if no rule exists (default open) or rule says enabled.
        """
        if organization is None:
            return True
        from organization.models import OrganizationModelAccess
        from django.contrib.contenttypes.models import ContentType
        try:
            ct = ContentType.objects.get_by_natural_key(app_label, model_name)
            rule = OrganizationModelAccess.objects.get(
                organization=organization,
                content_type=ct,
            )
            return rule.is_enabled
        except (OrganizationModelAccess.DoesNotExist, ContentType.DoesNotExist):
            return True  # No rule → allow by default

    def _layer2_group_permission(self, request, view, app_label, model_name):
        """
        Check Layer 2: does the user have the required Django permission?
        Checks both direct user permissions and org-scoped Membership groups.
        Falls back to standard user.has_perm if no Membership exists.
        """
        action = self._get_action(request, view)
        print(action)
        return has_effective_model_action_permission(request, app_label, model_name, action)

    def has_permission(self, request, view):
        if not request.user or not request.user.is_authenticated:
            return False

        # Explicit but invalid organization header should fail fast.
        if (
            getattr(request, 'organization_header_provided', False)
            and not getattr(request, 'organization_header_valid', True)
        ):
            return False

        # Superusers bypass everything
        if request.user.is_superuser:
            return True

        app_label, model_name = self._get_model_info(view)
        if not app_label:
            return True  # No model info → allow authenticated users

        organization = getattr(request, 'organization', None)

        # Staff without org header see everything (existing behavior)
        if request.user.is_staff and organization is None:
            return True

        # Org context must match Membership when the client pins org via header;
        # staff without header still rely on bypass above when organization is None.
        if not user_has_organization_membership(
            request.user,
            organization,
            organization_header_provided=getattr(
                request, 'organization_header_provided', False
            ),
        ):
            return False

        # Layer 1: org-level model access
        if not self._layer1_org_access(organization, app_label, model_name):
            return False

        # Layer 2: role/group permission
        return self._layer2_group_permission(request, view, app_label, model_name)
