"""
Database models for authentication

Implements all 8 features' data layer:
- Feature 1: User model
- Feature 5: RefreshToken model  
- Feature 6: Role model for RBAC
"""

from datetime import datetime
from typing import List, Optional
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Table
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship


class Base(DeclarativeBase):
    """Base class for all models"""
    pass


# Many-to-many association table for User ↔ Role
user_roles = Table(
    "user_roles",
    Base.metadata,
    Column("user_id", Integer, ForeignKey("users.id"), primary_key=True),
    Column("role_id", Integer, ForeignKey("roles.id"), primary_key=True),
)


class User(Base):
    """
    User model - Feature 1
    
    Represents an authenticated user in the system.
    
    Attributes:
        id: Primary key
        email: Unique email address
        password_hash: Argon2id hashed password (never store plaintext!)
        is_active: Whether user account is active
        is_verified: Whether email has been verified (Feature 8)
        created_at: Account creation timestamp
        updated_at: Last update timestamp
        verification_token_hash: SHA-256 hash of email verification token
        verification_token_expires: Expiration time for verification token
        reset_token_hash: SHA-256 hash of password reset token (Feature 7)
        reset_token_expires: Expiration time for reset token
        roles: Many-to-many relationship with Role (Feature 6)
        refresh_tokens: One-to-many relationship with RefreshToken (Feature 5)
    """
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True)
    password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
    
    # Account status
    is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
    is_verified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    
    # Timestamps
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
    
    # Email verification (Feature 8)
    verification_token_hash: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
    verification_token_expires: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    
    # Password reset (Feature 7)
    reset_token_hash: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
    reset_token_expires: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    
    # Relationships
    roles: Mapped[List["Role"]] = relationship(
        "Role",
        secondary=user_roles,
        back_populates="users",
        lazy="selectin"
    )
    refresh_tokens: Mapped[List["RefreshToken"]] = relationship(
        "RefreshToken",
        back_populates="user",
        cascade="all, delete-orphan"
    )

    def __repr__(self) -> str:
        return f"<User(id={self.id}, email='{self.email}', verified={self.is_verified})>"

    def has_role(self, role_name: str) -> bool:
        """Check if user has a specific role"""
        return any(role.name == role_name for role in self.roles)

    def has_any_role(self, role_names: List[str]) -> bool:
        """Check if user has any of the specified roles"""
        return any(self.has_role(name) for name in role_names)


class Role(Base):
    """
    Role model - Feature 6 (RBAC)
    
    Represents a role for role-based access control.
    
    Attributes:
        id: Primary key
        name: Unique role name (e.g., "admin", "user", "moderator")
        description: Human-readable description of the role
        created_at: Role creation timestamp
        users: Many-to-many relationship with User
    """
    __tablename__ = "roles"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
    description: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
    
    # Relationships
    users: Mapped[List["User"]] = relationship(
        "User",
        secondary=user_roles,
        back_populates="roles"
    )

    def __repr__(self) -> str:
        return f"<Role(id={self.id}, name='{self.name}')>"


class RefreshToken(Base):
    """
    Refresh token model - Feature 5
    
    Stores refresh tokens for extended authentication sessions.
    
    Attributes:
        id: Primary key
        token_hash: SHA-256 hash of the refresh token (never store plaintext!)
        user_id: Foreign key to User
        expires_at: Token expiration timestamp
        created_at: Token creation timestamp
        revoked: Whether token has been revoked
        revoked_at: Timestamp when token was revoked
        user: Many-to-one relationship with User
    
    Note:
        Tokens are hashed before storage for security. Use constant-time
        comparison when validating tokens.
    """
    __tablename__ = "refresh_tokens"

    id: Mapped[int] = mapped_column(primary_key=True)
    token_hash: Mapped[str] = mapped_column(String(64), unique=True, nullable=False, index=True)
    user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
    
    expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
    
    revoked: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    revoked_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    
    # Relationships
    user: Mapped["User"] = relationship("User", back_populates="refresh_tokens")

    def __repr__(self) -> str:
        return f"<RefreshToken(id={self.id}, user_id={self.user_id}, revoked={self.revoked})>"

    def is_valid(self) -> bool:
        """Check if token is not expired and not revoked"""
        return not self.revoked and self.expires_at > datetime.utcnow()

    def revoke(self) -> None:
        """Revoke this refresh token"""
        self.revoked = True
        self.revoked_at = datetime.utcnow()
