from beanie import Document, PydanticObjectId
from pydantic import Field
from typing import Optional, Dict, Any, List
from datetime import datetime, timedelta

class UserRole(Document):
    # User and Role References
    user_id: PydanticObjectId  # References User from auth service
    role_name: str  # References Role.name
    
    # Assignment Details
    assigned_by: Optional[PydanticObjectId] = None  # User ID who assigned this role
    assigned_at: datetime = Field(default_factory=datetime.utcnow)
    
    # Status
    is_active: bool = True
    
    # Expiration
    expires_at: Optional[datetime] = None
    is_permanent: bool = True
    
    # Scope and Conditions
    scope: Optional[str] = None  # e.g., "department:engineering", "project:123"
    conditions: Dict[str, Any] = Field(default_factory=dict)
    
    # Metadata
    notes: Optional[str] = None  # Reason for assignment
    created_at: datetime = Field(default_factory=datetime.utcnow)
    updated_at: datetime = Field(default_factory=datetime.utcnow)
    
    # Revocation
    revoked_at: Optional[datetime] = None
    revoked_by: Optional[PydanticObjectId] = None
    revocation_reason: Optional[str] = None
    
    class Config:
        arbitrary_types_allowed = True
    
    class Settings:
        name = "user_roles"
        indexes = [
            "user_id",
            "role_name",
            "is_active",
            "expires_at",
            "assigned_at",
            "created_at"
        ]

    @classmethod
    async def assign_role(
        cls,
        user_id: PydanticObjectId,
        role_name: str,
        assigned_by: Optional[PydanticObjectId] = None,
        expires_in_days: Optional[int] = None,
        scope: Optional[str] = None,
        notes: Optional[str] = None
    ) -> "UserRole":
        """Assign a role to a user"""
        # Check if user already has this role
        existing = await cls.find_one(
            cls.user_id == user_id,
            cls.role_name == role_name,
            cls.is_active == True
        )
        
        if existing:
            raise ValueError(f"User already has role: {role_name}")
        
        # Calculate expiration
        expires_at = None
        is_permanent = True
        if expires_in_days:
            expires_at = datetime.utcnow() + timedelta(days=expires_in_days)
            is_permanent = False
        
        # Create role assignment
        user_role = cls(
            user_id=user_id,
            role_name=role_name,
            assigned_by=assigned_by,
            expires_at=expires_at,
            is_permanent=is_permanent,
            scope=scope,
            notes=notes
        )
        
        await user_role.save()
        return user_role

    @classmethod
    async def revoke_role(
        cls,
        user_id: PydanticObjectId,
        role_name: str,
        revoked_by: Optional[PydanticObjectId] = None,
        reason: Optional[str] = None
    ) -> bool:
        """Revoke a role from a user"""
        user_role = await cls.find_one(
            cls.user_id == user_id,
            cls.role_name == role_name,
            cls.is_active == True
        )
        
        if not user_role:
            return False
        
        user_role.is_active = False
        user_role.revoked_at = datetime.utcnow()
        user_role.revoked_by = revoked_by
        user_role.revocation_reason = reason
        user_role.updated_at = datetime.utcnow()
        
        await user_role.save()
        return True

    @classmethod
    async def get_user_roles(cls, user_id: PydanticObjectId, active_only: bool = True) -> List["UserRole"]:
        """Get all roles for a user"""
        if active_only:
            return await cls.find(
                cls.user_id == user_id,
                cls.is_active == True
            ).to_list()
        else:
            return await cls.find(cls.user_id == user_id).to_list()

    @classmethod
    async def get_role_users(cls, role_name: str, active_only: bool = True) -> List["UserRole"]:
        """Get all users with a specific role"""
        if active_only:
            return await cls.find(
                cls.role_name == role_name,
                cls.is_active == True
            ).to_list()
        else:
            return await cls.find(cls.role_name == role_name).to_list()

    def is_expired(self) -> bool:
        """Check if role assignment is expired"""
        if not self.expires_at:
            return False
        return datetime.utcnow() > self.expires_at

    def is_valid(self) -> bool:
        """Check if role assignment is valid"""
        return self.is_active and not self.is_expired()

    async def extend_expiration(self, days: int):
        """Extend role expiration"""
        if self.expires_at:
            self.expires_at += timedelta(days=days)
        else:
            self.expires_at = datetime.utcnow() + timedelta(days=days)
            self.is_permanent = False
        
        self.updated_at = datetime.utcnow()
        await self.save()

    async def make_permanent(self):
        """Make role assignment permanent"""
        self.expires_at = None
        self.is_permanent = True
        self.updated_at = datetime.utcnow()
        await self.save()

    @classmethod
    async def cleanup_expired_roles(cls):
        """Clean up expired role assignments"""
        expired_roles = await cls.find(
            cls.expires_at < datetime.utcnow(),
            cls.is_active == True
        ).to_list()
        
        for role in expired_roles:
            role.is_active = False
            role.revoked_at = datetime.utcnow()
            role.revocation_reason = "expired"
            role.updated_at = datetime.utcnow()
            await role.save()
        
        return len(expired_roles)

    @classmethod
    async def assign_default_role(cls, user_id: PydanticObjectId) -> Optional["UserRole"]:
        """Assign default role to a new user"""
        from app.models.role import Role
        
        default_role = await Role.get_default_role()
        if not default_role:
            return None
        
        try:
            return await cls.assign_role(
                user_id=user_id,
                role_name=default_role.name,
                notes="Default role assignment"
            )
        except ValueError:
            # User already has the role
            return None
