"""
Domain models for space composability.

Defines models for space references, inheritance, and access control.
"""

import uuid
from dataclasses import dataclass, field
from datetime import datetime
from typing import Dict, Any, List, Optional
from enum import Enum


class SpaceReferenceType(Enum):
    """Type of reference between spaces."""
    INCLUDES = "includes"       # Space A includes content from Space B
    EXTENDS = "extends"         # Space A extends/inherits from Space B
    LINKS_TO = "links_to"       # Space A has a soft link to Space B
    COMPOSED_OF = "composed_of" # Space A is composed of multiple spaces


@dataclass
class SpaceReference:
    """
    Represents a reference between two spaces.

    Unlike parent_space_id (single inheritance), space references allow
    multiple relationships between spaces for composition.

    Attributes:
        id: Unique reference identifier
        source_space_id: The space making the reference
        target_space_id: The space being referenced
        reference_type: Type of reference relationship
        alias: Optional alias for the referenced space
        metadata: Additional reference metadata
        created_at: When the reference was created
    """
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    source_space_id: str = ""
    target_space_id: str = ""
    reference_type: SpaceReferenceType = SpaceReferenceType.LINKS_TO
    alias: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)
    created_at: datetime = field(default_factory=datetime.now)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        return {
            "id": self.id,
            "source_space_id": self.source_space_id,
            "target_space_id": self.target_space_id,
            "reference_type": self.reference_type.value,
            "alias": self.alias,
            "metadata": self.metadata,
            "created_at": self.created_at.isoformat(),
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SpaceReference":
        """Create from dictionary."""
        created_at = data.get("created_at")
        if isinstance(created_at, str):
            created_at = datetime.fromisoformat(created_at)
        elif created_at is None:
            created_at = datetime.now()

        ref_type = data.get("reference_type", "links_to")
        if isinstance(ref_type, str):
            ref_type = SpaceReferenceType(ref_type)

        return cls(
            id=data.get("id", str(uuid.uuid4())),
            source_space_id=data.get("source_space_id", ""),
            target_space_id=data.get("target_space_id", ""),
            reference_type=ref_type,
            alias=data.get("alias"),
            metadata=data.get("metadata", {}),
            created_at=created_at,
        )


class AccessLevel(Enum):
    """Access level for space permissions."""
    NONE = "none"       # No access
    READ = "read"       # Read-only access
    WRITE = "write"     # Read and write access
    ADMIN = "admin"     # Full administrative access


class SpaceRole(Enum):
    """Predefined roles for space access."""
    OWNER = "owner"         # Full control, can delete space
    ADMIN = "admin"         # Can manage members and settings
    EDITOR = "editor"       # Can edit documents and variables
    VIEWER = "viewer"       # Read-only access
    GUEST = "guest"         # Limited read access


@dataclass
class SpacePermission:
    """
    Permission entry for a space.

    Maps a principal (user/group/role) to an access level for a space.

    Attributes:
        space_id: The space this permission applies to
        principal_type: Type of principal (user, group, role)
        principal_id: Identifier for the principal
        access_level: The access level granted
        role: Optional predefined role
        granted_by: Who granted this permission
        granted_at: When permission was granted
        expires_at: Optional expiration timestamp
    """
    space_id: str
    principal_type: str  # "user", "group", "role"
    principal_id: str
    access_level: AccessLevel = AccessLevel.READ
    role: Optional[SpaceRole] = None
    granted_by: Optional[str] = None
    granted_at: datetime = field(default_factory=datetime.now)
    expires_at: Optional[datetime] = None

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            "space_id": self.space_id,
            "principal_type": self.principal_type,
            "principal_id": self.principal_id,
            "access_level": self.access_level.value,
            "role": self.role.value if self.role else None,
            "granted_by": self.granted_by,
            "granted_at": self.granted_at.isoformat(),
            "expires_at": self.expires_at.isoformat() if self.expires_at else None,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SpacePermission":
        """Create from dictionary."""
        granted_at = data.get("granted_at")
        if isinstance(granted_at, str):
            granted_at = datetime.fromisoformat(granted_at)
        elif granted_at is None:
            granted_at = datetime.now()

        expires_at = data.get("expires_at")
        if isinstance(expires_at, str):
            expires_at = datetime.fromisoformat(expires_at)

        access_level = data.get("access_level", "read")
        if isinstance(access_level, str):
            access_level = AccessLevel(access_level)

        role = data.get("role")
        if isinstance(role, str):
            role = SpaceRole(role)

        return cls(
            space_id=data["space_id"],
            principal_type=data.get("principal_type", "user"),
            principal_id=data["principal_id"],
            access_level=access_level,
            role=role,
            granted_by=data.get("granted_by"),
            granted_at=granted_at,
            expires_at=expires_at,
        )

    def is_expired(self) -> bool:
        """Check if permission has expired."""
        if self.expires_at is None:
            return False
        return datetime.now() > self.expires_at

    def has_access(self, required_level: AccessLevel) -> bool:
        """Check if this permission grants the required access level."""
        if self.is_expired():
            return False

        level_order = [AccessLevel.NONE, AccessLevel.READ, AccessLevel.WRITE, AccessLevel.ADMIN]
        return level_order.index(self.access_level) >= level_order.index(required_level)


@dataclass
class SpaceAccess:
    """
    Computed access result for a principal on a space.

    Combines all applicable permissions to determine effective access.

    Attributes:
        space_id: The space being accessed
        principal_id: The principal accessing
        effective_level: Computed effective access level
        roles: All roles that apply
        inherited_from: Space IDs from which access was inherited
        permissions: Source permissions that contributed
    """
    space_id: str
    principal_id: str
    effective_level: AccessLevel = AccessLevel.NONE
    roles: List[SpaceRole] = field(default_factory=list)
    inherited_from: List[str] = field(default_factory=list)
    permissions: List[SpacePermission] = field(default_factory=list)

    def can_read(self) -> bool:
        """Check if principal can read."""
        return self.effective_level in [AccessLevel.READ, AccessLevel.WRITE, AccessLevel.ADMIN]

    def can_write(self) -> bool:
        """Check if principal can write."""
        return self.effective_level in [AccessLevel.WRITE, AccessLevel.ADMIN]

    def is_admin(self) -> bool:
        """Check if principal has admin access."""
        return self.effective_level == AccessLevel.ADMIN


@dataclass
class InheritedVariable:
    """
    A variable with its inheritance source.

    Attributes:
        name: Variable name
        value: Variable value
        source_space_id: Space where this variable is defined
        inheritance_depth: How many levels up the inheritance chain (0 = local)
        scope: Variable scope
    """
    name: str
    value: Any
    source_space_id: str
    inheritance_depth: int = 0
    scope: str = "space"

    def is_local(self) -> bool:
        """Check if variable is locally defined (not inherited)."""
        return self.inheritance_depth == 0


@dataclass
class InheritedConfig:
    """
    Configuration with inheritance tracking.

    Attributes:
        default_variant: Effective default variant
        enable_caching: Effective caching setting
        theme: Effective theme
        history_enabled: Effective history setting
        variable_scope: Effective variable scope
        source_spaces: Map of config key to source space ID
    """
    default_variant: str = "hierarchical"
    enable_caching: bool = True
    theme: Optional[str] = None
    history_enabled: bool = False
    variable_scope: str = "space"
    source_spaces: Dict[str, str] = field(default_factory=dict)

    def get_source(self, key: str) -> Optional[str]:
        """Get the source space ID for a config key."""
        return self.source_spaces.get(key)

    def is_inherited(self, key: str, current_space_id: str) -> bool:
        """Check if a config key is inherited from a parent."""
        source = self.source_spaces.get(key)
        return source is not None and source != current_space_id
