"""
Data Policy Services - Row-level security and data filtering.

This module provides the core services for:
- Applying data policies to querysets
- Evaluating user data visibility
- Building filter conditions based on policies
"""
from typing import Optional, List, Dict, Any, Type
from uuid import UUID
from datetime import datetime
import pytz

from django.db.models import Q, QuerySet, Model
from django.utils import timezone

from .models import DataPolicy, DataScope, UserDataScope, RoleDataScope, PolicyType


class DataPolicyService:
    """
    Service for applying data policies to queries.
    
    Usage:
        # In a view or service
        queryset = Invoice.objects.all()
        filtered_qs = DataPolicyService.apply_policies(
            queryset=queryset,
            user=request.user,
            module='accounting',
            resource='invoice',
            action='read'
        )
    """
    
    @staticmethod
    def apply_policies(
        queryset: QuerySet,
        user,
        module: str,
        resource: str,
        action: str = 'read'
    ) -> QuerySet:
        """
        Apply all applicable data policies to a queryset.
        
        Args:
            queryset: The base queryset to filter
            user: The user making the request
            module: Module name (e.g., 'accounting')
            resource: Resource name (e.g., 'invoice')
            action: The action being performed (read, write, delete)
            
        Returns:
            Filtered queryset based on applicable policies
        """
        if user.is_superuser:
            return queryset
        
        # Get user's tenant
        tenant_id = user.tenant_id
        if not tenant_id:
            return queryset.none()
        
        # Get applicable policies
        policies = DataPolicyService._get_applicable_policies(
            tenant_id=tenant_id,
            user=user,
            module=module,
            resource=resource,
            action=action
        )
        
        if not policies:
            # No policies = no restrictions (or deny all based on config)
            return queryset
        
        # Build combined filter
        combined_filter = Q()
        has_allow_policy = False
        
        for policy in policies:
            policy_filter = DataPolicyService._build_policy_filter(
                policy=policy,
                user=user,
                model=queryset.model
            )
            
            if policy_filter is not None:
                if policy.is_allow:
                    combined_filter |= policy_filter
                    has_allow_policy = True
                else:
                    # Deny policies exclude records
                    combined_filter &= ~policy_filter
        
        if has_allow_policy:
            return queryset.filter(combined_filter)
        else:
            return queryset
    
    @staticmethod
    def _get_applicable_policies(
        tenant_id: UUID,
        user,
        module: str,
        resource: str,
        action: str
    ) -> List[DataPolicy]:
        """Get all policies that apply to this request."""
        from apps.core.permission.models import UserRole
        
        # Get user's role IDs
        user_role_ids = list(
            UserRole.objects.filter(user=user)
            .values_list('role_id', flat=True)
        )
        
        # Build policy query
        policies = DataPolicy.objects.filter(
            tenant_id=tenant_id,
            is_active=True
        ).filter(
            # Module match
            Q(module='*') | Q(module=module)
        ).filter(
            # Resource match
            Q(resource='*') | Q(resource=resource)
        ).filter(
            # Action match
            Q(actions__contains=['all']) | Q(actions__contains=[action])
        ).filter(
            # Role match (empty = all roles)
            Q(applies_to_roles__isnull=True) |
            Q(applies_to_roles__id__in=user_role_ids)
        ).order_by('-priority').distinct()
        
        return list(policies)
    
    @staticmethod
    def _build_policy_filter(
        policy: DataPolicy,
        user,
        model: Type[Model]
    ) -> Optional[Q]:
        """Build a Q filter from a policy."""
        
        if policy.policy_type == PolicyType.ROW_FILTER:
            return DataPolicyService._build_row_filter(policy, user, model)
        
        elif policy.policy_type == PolicyType.TIME_BASED:
            if not DataPolicyService._check_time_based(policy):
                return Q(pk__isnull=True)  # Always false = deny
            return None  # Time check passed, no filter needed
        
        elif policy.policy_type == PolicyType.CONDITIONAL:
            return DataPolicyService._build_conditional_filter(policy, user, model)
        
        return None
    
    @staticmethod
    def _build_row_filter(
        policy: DataPolicy,
        user,
        model: Type[Model]
    ) -> Optional[Q]:
        """Build row-level filter."""
        conditions = policy.conditions
        filter_type = conditions.get('type', 'org_unit')
        
        if filter_type == 'owner':
            # Owner only filter
            owner_field = conditions.get('owner_field', 'created_by_id')
            return Q(**{owner_field: user.id})
        
        elif filter_type == 'org_unit':
            # Organizational unit filter
            return DataPolicyService._build_org_unit_filter(conditions, user, model)
        
        elif filter_type == 'team':
            # Team member filter
            return DataPolicyService._build_team_filter(conditions, user)
        
        elif filter_type == 'custom':
            # Custom filter expression
            filter_expr = conditions.get('filter_expression')
            if filter_expr:
                # WARNING: Evaluate with caution - this could be a security risk
                # In production, use a safe expression parser
                return DataPolicyService._parse_filter_expression(filter_expr, user)
        
        return None
    
    @staticmethod
    def _build_org_unit_filter(
        conditions: Dict[str, Any],
        user,
        model: Type[Model]
    ) -> Q:
        """Build organizational unit based filter."""
        from apps.core.organization.models import UserOrganizationalUnit, OrganizationalUnit
        
        org_unit_field = conditions.get('org_unit_field', 'organizational_unit_id')
        include_descendants = conditions.get('include_descendants', True)
        
        # Get user's organizational units
        user_org_units = UserOrganizationalUnit.objects.active().filter(
            user=user
        ).values_list('organizational_unit_id', flat=True)
        
        org_unit_ids = set(user_org_units)
        
        # Include descendants if configured
        if include_descendants:
            for membership in UserOrganizationalUnit.objects.active().filter(
                user=user,
                include_descendants=True
            ).select_related('organizational_unit'):
                unit = membership.organizational_unit
                descendants = unit.get_descendants()
                org_unit_ids.update(d.pk for d in descendants)
        
        if not org_unit_ids:
            return Q(pk__isnull=True)  # No access
        
        return Q(**{f'{org_unit_field}__in': org_unit_ids})
    
    @staticmethod
    def _build_team_filter(conditions: Dict[str, Any], user) -> Q:
        """Build team-based filter."""
        # Get user's team members
        # This would need to be implemented based on your team structure
        team_field = conditions.get('team_field', 'team_id')
        
        # Placeholder - implement based on your team model
        return Q(**{f'{team_field}__in': []})
    
    @staticmethod
    def _check_time_based(policy: DataPolicy) -> bool:
        """Check if current time satisfies time-based policy."""
        conditions = policy.conditions
        
        tz_name = conditions.get('timezone', 'UTC')
        tz = pytz.timezone(tz_name)
        now = datetime.now(tz)
        
        # Check day of week (1=Monday, 7=Sunday)
        allowed_days = conditions.get('allowed_days', [1, 2, 3, 4, 5, 6, 7])
        if now.isoweekday() not in allowed_days:
            return False
        
        # Check hours
        allowed_hours = conditions.get('allowed_hours')
        if allowed_hours:
            start_hour = allowed_hours.get('start', 0)
            end_hour = allowed_hours.get('end', 24)
            if not (start_hour <= now.hour < end_hour):
                return False
        
        return True
    
    @staticmethod
    def _build_conditional_filter(
        policy: DataPolicy,
        user,
        model: Type[Model]
    ) -> Optional[Q]:
        """Build conditional filter based on record values."""
        conditions = policy.conditions
        
        q_filter = Q()
        
        for field, condition in conditions.get('field_conditions', {}).items():
            if isinstance(condition, dict):
                for operator, value in condition.items():
                    q_filter &= Q(**{f'{field}__{operator}': value})
            else:
                q_filter &= Q(**{field: condition})
        
        return q_filter
    
    @staticmethod
    def _parse_filter_expression(expression: str, user) -> Q:
        """
        Parse a filter expression string to Q object.
        
        WARNING: This is a simplified implementation.
        In production, use a proper expression parser with security measures.
        """
        # Very basic implementation - just return empty Q for safety
        # Real implementation would parse expressions like:
        # "Q(status='active') & Q(created_by=user.id)"
        return Q()


class FieldVisibilityService:
    """
    Service for managing field-level visibility.
    
    Determines which fields a user can see/edit for a given record.
    """
    
    @staticmethod
    def get_visible_fields(
        user,
        module: str,
        resource: str,
        record: Optional[Model] = None
    ) -> Dict[str, str]:
        """
        Get field visibility configuration for a user.
        
        Returns:
            Dict mapping field name to visibility level:
            - 'visible': Full access
            - 'readonly': Can see but not edit
            - 'hidden': Cannot see
            - 'masked': Partial visibility (e.g., ***1234)
        """
        from apps.core.permission.models import FieldPermission, UserRole
        
        # Get user's role IDs
        user_role_ids = list(
            UserRole.objects.filter(user=user)
            .values_list('role_id', flat=True)
        )
        
        # Get field permissions for these roles
        field_permissions = FieldPermission.objects.filter(
            role_id__in=user_role_ids,
            module=module,
            resource=resource
        )
        
        result = {}
        
        for fp in field_permissions:
            field = fp.field_name
            
            # If we already have this field, take the most permissive
            if field in result:
                current = result[field]
                new = fp.access_level
                
                # Visibility hierarchy: editable > readonly > masked > hidden
                hierarchy = ['hidden', 'masked', 'readonly', 'editable']
                if hierarchy.index(new) > hierarchy.index(current):
                    result[field] = new
            else:
                result[field] = fp.access_level
        
        return result
    
    @staticmethod
    def mask_field_value(value: Any, mask_pattern: str) -> str:
        """
        Apply a mask pattern to a field value.
        
        Patterns:
        - "****{last4}": Show last 4 characters
        - "{first3}***": Show first 3 characters
        - "***": Hide completely
        """
        if value is None:
            return None
        
        str_value = str(value)
        
        if '{last4}' in mask_pattern:
            return mask_pattern.replace('{last4}', str_value[-4:] if len(str_value) >= 4 else str_value)
        
        if '{first3}' in mask_pattern:
            return mask_pattern.replace('{first3}', str_value[:3] if len(str_value) >= 3 else str_value)
        
        return mask_pattern
    
    @staticmethod
    def filter_record_fields(
        record_data: Dict[str, Any],
        visibility: Dict[str, str],
        field_masks: Optional[Dict[str, str]] = None
    ) -> Dict[str, Any]:
        """
        Filter a record's fields based on visibility rules.
        
        Args:
            record_data: The record data as a dict
            visibility: Field visibility configuration
            field_masks: Optional mask patterns for masked fields
            
        Returns:
            Filtered record data
        """
        result = {}
        field_masks = field_masks or {}
        
        for field, value in record_data.items():
            vis_level = visibility.get(field, 'visible')
            
            if vis_level == 'hidden':
                continue
            elif vis_level == 'masked':
                mask_pattern = field_masks.get(field, '****')
                result[field] = FieldVisibilityService.mask_field_value(value, mask_pattern)
            else:
                result[field] = value
        
        return result


class DataScopeService:
    """Service for managing data scopes."""
    
    @staticmethod
    def get_user_scopes(
        user,
        module: Optional[str] = None,
        resource: Optional[str] = None
    ) -> List[DataScope]:
        """Get all data scopes for a user."""
        from apps.core.permission.models import UserRole
        
        # Direct user scopes
        user_scope_qs = UserDataScope.objects.filter(
            user=user,
            is_active=True
        )
        
        if module:
            user_scope_qs = user_scope_qs.filter(
                Q(module='') | Q(module='*') | Q(module=module)
            )
        
        if resource:
            user_scope_qs = user_scope_qs.filter(
                Q(resource='') | Q(resource='*') | Q(resource=resource)
            )
        
        user_scopes = list(user_scope_qs.values_list('scope_id', flat=True))
        
        # Role-based scopes
        user_role_ids = list(
            UserRole.objects.filter(user=user)
            .values_list('role_id', flat=True)
        )
        
        role_scope_qs = RoleDataScope.objects.filter(
            role_id__in=user_role_ids,
            is_active=True
        )
        
        if module:
            role_scope_qs = role_scope_qs.filter(
                Q(module='') | Q(module='*') | Q(module=module)
            )
        
        if resource:
            role_scope_qs = role_scope_qs.filter(
                Q(resource='') | Q(resource='*') | Q(resource=resource)
            )
        
        role_scopes = list(role_scope_qs.values_list('scope_id', flat=True))
        
        # Combine and return
        all_scope_ids = set(user_scopes + role_scopes)
        return list(DataScope.objects.filter(pk__in=all_scope_ids, is_active=True))
    
    @staticmethod
    def build_scope_filter(scope: DataScope, user) -> Q:
        """Build a Q filter from a data scope."""
        config = scope.configuration
        
        if scope.scope_type == 'owner':
            owner_field = config.get('owner_field', 'created_by_id')
            return Q(**{owner_field: user.id})
        
        elif scope.scope_type == 'org_unit':
            # Similar to policy org_unit filter
            return DataPolicyService._build_org_unit_filter(config, user, None)
        
        elif scope.scope_type == 'team':
            return DataPolicyService._build_team_filter(config, user)
        
        return Q()
