"""
Permission Models - Role and Permission models.

Enhanced permission system with support for:
- Organizational unit scoping
- Module-level access control
- Field-level permissions
- Time-based access
- Constraint-based permissions
- User-level permission overrides (Explicit Allow/Deny)
- Object-level relationship-based access (RAIC)
"""
import uuid

from django.conf import settings
from django.db import models

from apps.core.tenant.models import TenantAwareModel


class PermissionAction(models.TextChoices):
    """Standard permission actions."""
    CREATE = 'create', 'Create'
    READ = 'read', 'Read'
    UPDATE = 'update', 'Update'
    DELETE = 'delete', 'Delete'
    LIST = 'list', 'List'
    EXPORT = 'export', 'Export'
    IMPORT = 'import', 'Import'
    APPROVE = 'approve', 'Approve'
    REJECT = 'reject', 'Reject'
    ASSIGN = 'assign', 'Assign'
    MANAGE = 'manage', 'Manage (Full Control)'
    CUSTOM = 'custom', 'Custom Action'


class Role(TenantAwareModel):
    """
    Role model for grouping permissions.
    
    Supports:
    - Role hierarchy (inherit from parent)
    - System vs custom roles
    - Module-specific roles
    """
    name = models.CharField(max_length=100)
    slug = models.SlugField(max_length=100)
    description = models.TextField(blank=True)
    is_system = models.BooleanField(
        default=False,
        help_text="System roles cannot be deleted"
    )
    is_active = models.BooleanField(default=True)
    
    # Role hierarchy
    parent = models.ForeignKey(
        'self',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='children'
    )
    
    # Module restriction (optional - role only valid for specific module)
    module = models.CharField(
        max_length=100,
        blank=True,
        help_text="If set, role only applies to this module"
    )
    
    # Metadata
    metadata = models.JSONField(default=dict, blank=True)
    
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'roles'
        unique_together = ['tenant', 'slug']
        verbose_name = 'Role'
        verbose_name_plural = 'Roles'

    def __str__(self):
        return self.name

    def get_all_permissions(self):
        """Get all permissions including inherited from parent roles."""
        permissions = set(
            rp.permission for rp in self.role_permissions.select_related('permission').all()
        )
        if self.parent:
            permissions.update(self.parent.get_all_permissions())
        return permissions


class Permission(models.Model):
    """
    Permission model for fine-grained access control.
    
    Permission codename format: {module}.{resource}.{action}
    Examples:
    - accounting.invoice.create
    - hrm.employee.read
    - platform.user.manage
    """
    name = models.CharField(max_length=100)
    codename = models.CharField(max_length=100, unique=True)
    description = models.TextField(blank=True)
    
    # Module/resource this permission belongs to
    module = models.CharField(
        max_length=100,
        db_index=True,
        help_text="Module name (e.g., 'accounting', 'hrm', 'platform')"
    )
    resource = models.CharField(
        max_length=100,
        db_index=True,
        help_text="Resource name (e.g., 'invoice', 'employee', 'user')"
    )
    action = models.CharField(
        max_length=50,
        choices=PermissionAction.choices,
        default=PermissionAction.READ,
        help_text="Action type"
    )
    custom_action = models.CharField(
        max_length=50,
        blank=True,
        help_text="Custom action name when action is CUSTOM"
    )
    
    # Is this a sensitive permission?
    is_sensitive = models.BooleanField(
        default=False,
        help_text="Sensitive permissions require additional audit logging"
    )
    
    # Dependencies (optional - requires other permissions)
    requires = models.ManyToManyField(
        'self',
        symmetrical=False,
        blank=True,
        related_name='required_by',
        help_text="Other permissions required for this permission"
    )
    
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'permissions'
        verbose_name = 'Permission'
        verbose_name_plural = 'Permissions'
        indexes = [
            models.Index(fields=['module', 'resource']),
            models.Index(fields=['module', 'action']),
        ]

    def __str__(self):
        action_name = self.custom_action if self.action == PermissionAction.CUSTOM else self.action
        return f'{self.module}.{self.resource}.{action_name}'
    
    def save(self, *args, **kwargs):
        # Auto-generate codename if not set
        if not self.codename:
            action_name = self.custom_action if self.action == PermissionAction.CUSTOM else self.action
            self.codename = f'{self.module}.{self.resource}.{action_name}'
        super().save(*args, **kwargs)


class RolePermission(models.Model):
    """
    Many-to-many relationship between Role and Permission.
    
    Supports constraints for conditional permissions:
    - Organizational unit scope
    - Field restrictions
    - Value limits
    - Time-based access
    """
    role = models.ForeignKey(Role, on_delete=models.CASCADE, related_name='role_permissions')
    permission = models.ForeignKey(Permission, on_delete=models.CASCADE, related_name='permission_roles')
    
    # Constraints for this permission assignment
    # Example constraints:
    # {
    #     "org_units": ["uuid1", "uuid2"],  # Only these org units
    #     "include_descendants": true,       # Include child units
    #     "fields": {                        # Field-level restrictions
    #         "salary": "hidden",            # hidden, readonly, editable
    #         "ssn": "hidden"
    #     },
    #     "conditions": {                    # Value-based conditions
    #         "amount__lte": 1000000,        # Can only approve up to 1M
    #         "status__in": ["draft", "pending"]
    #     },
    #     "time_restrictions": {             # Time-based access
    #         "days": [1, 2, 3, 4, 5],       # Monday to Friday
    #         "hours": {"start": 8, "end": 18}
    #     }
    # }
    constraints = models.JSONField(default=dict, blank=True)
    
    # Priority for conflict resolution (higher = more priority)
    priority = models.IntegerField(default=0)
    
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'role_permissions'
        unique_together = ['role', 'permission']
        ordering = ['-priority']


class UserRole(models.Model):
    """
    Many-to-many relationship between User and Role.
    
    Supports:
    - Organizational unit scoping
    - Time-limited assignments
    - Additional constraints per assignment
    """
    user = models.ForeignKey(
        'core_auth.User',
        on_delete=models.CASCADE,
        related_name='user_roles'
    )
    role = models.ForeignKey(Role, on_delete=models.CASCADE, related_name='role_users')
    
    # Organizational unit scope (optional)
    # If set, role only applies within this org unit
    organizational_unit = models.ForeignKey(
        'core_organization.OrganizationalUnit',
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='role_assignments',
        help_text="If set, role only applies in this organizational unit"
    )
    include_descendant_units = models.BooleanField(
        default=True,
        help_text="If true, role applies to child org units too"
    )
    
    # Scope constraints (additional restrictions)
    # Example: {"projects": ["project-1", "project-2"]}
    scope = models.JSONField(default=dict, blank=True)
    
    # Time-based validity
    created_at = models.DateTimeField(auto_now_add=True)
    starts_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When this role assignment becomes active"
    )
    expires_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When this role assignment expires"
    )
    
    # Assignment metadata
    assigned_by = models.ForeignKey(
        'core_auth.User',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='role_assignments_made',
        help_text="User who made this assignment"
    )
    assignment_reason = models.TextField(
        blank=True,
        help_text="Reason for this role assignment"
    )

    class Meta:
        db_table = 'user_roles'
        unique_together = ['user', 'role', 'organizational_unit']
        indexes = [
            models.Index(fields=['user', 'expires_at']),
            models.Index(fields=['organizational_unit']),
        ]
    
    def is_active(self) -> bool:
        """Check if this role assignment is currently active."""
        from django.utils import timezone
        now = timezone.now()
        
        if self.starts_at and self.starts_at > now:
            return False
        if self.expires_at and self.expires_at < now:
            return False
        return True


class FieldPermission(models.Model):
    """
    Field-level permission model.
    
    Controls access to specific fields within a resource.
    Used for sensitive data like salary, SSN, etc.
    """
    
    class AccessLevel(models.TextChoices):
        HIDDEN = 'hidden', 'Hidden (not visible)'
        READONLY = 'readonly', 'Read Only'
        EDITABLE = 'editable', 'Editable'
        MASKED = 'masked', 'Masked (partial visibility)'
    
    role = models.ForeignKey(
        Role,
        on_delete=models.CASCADE,
        related_name='field_permissions'
    )
    
    # Field identification
    module = models.CharField(max_length=100)
    resource = models.CharField(max_length=100)
    field_name = models.CharField(max_length=100)
    
    # Access level
    access_level = models.CharField(
        max_length=20,
        choices=AccessLevel.choices,
        default=AccessLevel.READONLY
    )
    
    # Mask pattern (for MASKED access level)
    # e.g., "****{last4}" for showing last 4 characters
    mask_pattern = models.CharField(max_length=100, blank=True)
    
    # Conditions (when does this apply)
    conditions = models.JSONField(
        default=dict,
        blank=True,
        help_text="Conditions when this field permission applies"
    )
    
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        db_table = 'field_permissions'
        unique_together = ['role', 'module', 'resource', 'field_name']
        verbose_name = 'Field Permission'
        verbose_name_plural = 'Field Permissions'
    
    def __str__(self):
        return f"{self.role.name}: {self.module}.{self.resource}.{self.field_name} = {self.access_level}"


class UserPermission(models.Model):
    """
    Explicit user-level permission override (Layer 5).
    
    Allows granting or denying specific permissions to individual users,
    independent of their roles. 
    
    Rule: Explicit Deny > Explicit Allow > Role-based permissions.
    """
    user = models.ForeignKey(
        'core_auth.User',
        on_delete=models.CASCADE,
        related_name='user_permissions_custom',
    )
    permission = models.ForeignKey(
        Permission,
        on_delete=models.CASCADE,
        related_name='user_overrides',
    )
    
    # True = explicit allow, False = explicit deny
    allow = models.BooleanField(
        default=True,
        help_text="True = grant, False = deny (deny always wins)"
    )
    
    # Reason for override
    reason = models.TextField(
        blank=True,
        help_text="Why this override was created"
    )
    
    # Who granted this override
    granted_by = models.ForeignKey(
        'core_auth.User',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='permission_overrides_granted',
    )
    
    # Time-based validity
    starts_at = models.DateTimeField(null=True, blank=True)
    expires_at = models.DateTimeField(null=True, blank=True)
    
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'user_permissions'
        unique_together = ['user', 'permission']
        verbose_name = 'User Permission Override'
        verbose_name_plural = 'User Permission Overrides'
        indexes = [
            models.Index(fields=['user', 'allow']),
        ]

    def __str__(self):
        action = "ALLOW" if self.allow else "DENY"
        return f"{self.user} → {action} {self.permission.codename}"

    def is_active(self) -> bool:
        """Check if this override is currently valid."""
        from django.utils import timezone
        now = timezone.now()
        if self.starts_at and self.starts_at > now:
            return False
        if self.expires_at and self.expires_at < now:
            return False
        return True


class RelationType(models.TextChoices):
    """Standard relationship types for RAIC."""
    CREATOR = 'creator', 'Creator'
    ASSIGNEE = 'assignee', 'Assignee'
    WATCHER = 'watcher', 'Watcher'
    APPROVER = 'approver', 'Approver'
    MANAGER_OF_ASSIGNEE = 'manager_of_assignee', 'Manager of Assignee'
    OWNER = 'owner', 'Owner'
    REVIEWER = 'reviewer', 'Reviewer'
    CONTRIBUTOR = 'contributor', 'Contributor'
    CUSTOM = 'custom', 'Custom'


class ObjectRelation(models.Model):
    """
    Object-level user relationships for RAIC (Layer 4).
    
    Tracks which users have what relationship to specific objects.
    Used for dynamic permission checks like:
    - "assignee can update task"
    - "creator can delete their own record"
    - "approver can approve request"
    
    This is a generic/central model — modules register relations via this table.
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    
    # Target object (generic)
    object_type = models.CharField(
        max_length=100,
        db_index=True,
        help_text="e.g., 'pm.task', 'hrm.leave_request'"
    )
    object_id = models.CharField(
        max_length=255,
        db_index=True,
        help_text="UUID or ID of the target object"
    )
    
    # User with the relation
    user = models.ForeignKey(
        'core_auth.User',
        on_delete=models.CASCADE,
        related_name='object_relations',
    )
    
    # Relation type
    relation_type = models.CharField(
        max_length=30,
        choices=RelationType.choices,
    )
    custom_relation = models.CharField(
        max_length=50,
        blank=True,
        help_text="Custom relation name when type is 'custom'"
    )
    
    # Module context
    module = models.CharField(
        max_length=100,
        db_index=True,
        help_text="Module name (e.g., 'pm', 'hrm')"
    )
    
    # Metadata
    metadata = models.JSONField(default=dict, blank=True)
    
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        db_table = 'object_relations'
        unique_together = ['object_type', 'object_id', 'user', 'relation_type']
        verbose_name = 'Object Relation'
        verbose_name_plural = 'Object Relations'
        indexes = [
            models.Index(fields=['object_type', 'object_id']),
            models.Index(fields=['user', 'relation_type']),
            models.Index(fields=['module', 'object_type']),
        ]

    def __str__(self):
        rel = self.custom_relation if self.relation_type == RelationType.CUSTOM else self.relation_type
        return f"{self.user} is {rel} of {self.object_type}:{self.object_id}"


class RAICPolicy(models.Model):
    """
    RAIC Policy mapping: relation → allowed actions.
    
    Defines what actions a user with a specific relation can perform on objects.
    Example:
        relation_type=assignee, permission=pm.task.update → assignees can update tasks
    """
    module = models.CharField(max_length=100, db_index=True)
    object_type = models.CharField(
        max_length=100,
        help_text="e.g., 'pm.task', 'hrm.leave_request'"
    )
    relation_type = models.CharField(
        max_length=30,
        choices=RelationType.choices,
    )
    permission = models.ForeignKey(
        Permission,
        on_delete=models.CASCADE,
        related_name='raic_policies',
    )
    
    # Is this policy active?
    is_active = models.BooleanField(default=True)
    
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        db_table = 'raic_policies'
        unique_together = ['module', 'object_type', 'relation_type', 'permission']
        verbose_name = 'RAIC Policy'
        verbose_name_plural = 'RAIC Policies'

    def __str__(self):
        return f"{self.relation_type} on {self.object_type} → {self.permission.codename}"
