"""
Database module for WebAuthn passkey authentication.

This module provides dataclasses and database abstractions for managing
users, credentials, and sessions in a WebAuthn authentication system.
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from uuid import UUID


@dataclass
class Permission:
    id: str  # String primary key (max 128 chars)
    display_name: str


@dataclass
class Role:
    uuid: UUID
    org_uuid: UUID
    display_name: str
    # List of permission IDs this role grants to its members
    permissions: list[str] = field(default_factory=list)  # permission IDs


@dataclass
class Org:
    uuid: UUID
    display_name: str
    # All permission IDs that the Org is allowed to grant to its roles
    permissions: list[str] = field(default_factory=list)  # permission IDs
    # Roles belonging to this org
    roles: list[Role] = field(default_factory=list)


@dataclass
class User:
    uuid: UUID
    display_name: str
    role_uuid: UUID
    created_at: datetime | None = None
    last_seen: datetime | None = None
    visits: int = 0


@dataclass
class Credential:
    uuid: UUID
    credential_id: bytes  # Long binary ID passed from the authenticator
    user_uuid: UUID
    aaguid: UUID
    public_key: bytes
    sign_count: int
    created_at: datetime
    last_used: datetime | None = None
    last_verified: datetime | None = None


@dataclass
class Session:
    key: bytes
    user_uuid: UUID
    expires: datetime
    info: dict
    credential_uuid: UUID | None = None


@dataclass
class SessionContext:
    session: Session
    user: User
    org: Org
    role: Role
    permissions: list[Permission] | None = None


class DatabaseInterface(ABC):
    """Abstract base class defining the database interface.

    This class defines the public API that database implementations should provide.
    Implementations may use decorators like @with_session that modify method signatures
    at runtime, so this interface focuses on the logical operations rather than
    exact parameter matching.
    """

    @abstractmethod
    async def init_db(self) -> None:
        """Initialize database tables."""
        pass

    # User operations
    @abstractmethod
    async def get_user_by_uuid(self, user_uuid: UUID) -> User:
        """Get user record by WebAuthn user UUID."""

    @abstractmethod
    async def create_user(self, user: User) -> None:
        """Create a new user."""

    @abstractmethod
    async def update_user_display_name(
        self, user_uuid: UUID, display_name: str
    ) -> None:
        """Update a user's display name."""

    # Role operations
    @abstractmethod
    async def create_role(self, role: Role) -> None:
        """Create new role."""

    @abstractmethod
    async def update_role(self, role: Role) -> None:
        """Update a role's display name and synchronize its permissions."""

    @abstractmethod
    async def delete_role(self, role_uuid: UUID) -> None:
        """Delete a role by UUID. Implementations may prevent deletion if users exist."""

    # Credential operations
    @abstractmethod
    async def create_credential(self, credential: Credential) -> None:
        """Store a credential for a user."""

    @abstractmethod
    async def get_credential_by_id(self, credential_id: bytes) -> Credential:
        """Get credential by credential ID."""

    @abstractmethod
    async def get_credentials_by_user_uuid(self, user_uuid: UUID) -> list[bytes]:
        """Get all credential IDs for a user."""

    @abstractmethod
    async def update_credential(self, credential: Credential) -> None:
        """Update the sign count, created_at, last_used, and last_verified for a credential."""

    @abstractmethod
    async def delete_credential(self, uuid: UUID, user_uuid: UUID) -> None:
        """Delete a specific credential for a user."""

    # Session operations
    @abstractmethod
    async def create_session(
        self,
        user_uuid: UUID,
        key: bytes,
        expires: datetime,
        info: dict,
        credential_uuid: UUID | None = None,
    ) -> None:
        """Create a new session."""

    @abstractmethod
    async def get_session(self, key: bytes) -> Session | None:
        """Get session by key."""

    @abstractmethod
    async def delete_session(self, key: bytes) -> None:
        """Delete session by key."""

    @abstractmethod
    async def update_session(
        self, key: bytes, expires: datetime, info: dict
    ) -> Session | None:
        """Update session expiry and info."""

    @abstractmethod
    async def cleanup(self) -> None:
        """Called periodically to clean up expired records."""

    # Organization operations
    @abstractmethod
    async def create_organization(self, org: Org) -> None:
        """Add a new organization."""

    @abstractmethod
    async def get_organization(self, org_id: str) -> Org:
        """Get organization by ID, including its permission IDs and roles (with their permission IDs)."""

    @abstractmethod
    async def list_organizations(self) -> list[Org]:
        """List all organizations with their roles and permission IDs."""

    @abstractmethod
    async def update_organization(self, org: Org) -> None:
        """Update organization options."""

    @abstractmethod
    async def delete_organization(self, org_uuid: UUID) -> None:
        """Delete organization by ID."""

    @abstractmethod
    async def add_user_to_organization(
        self, user_uuid: UUID, org_id: str, role: str
    ) -> None:
        """Set a user's organization and role."""

    @abstractmethod
    async def transfer_user_to_organization(
        self, user_uuid: UUID, new_org_id: str, new_role: str | None = None
    ) -> None:
        """Transfer a user to another organization with an optional role."""

    @abstractmethod
    async def get_user_organization(self, user_uuid: UUID) -> tuple[Org, str]:
        """Get the organization and role for a user."""

    @abstractmethod
    async def get_organization_users(self, org_id: str) -> list[tuple[User, str]]:
        """Get all users in an organization with their roles."""

    @abstractmethod
    async def get_roles_by_organization(self, org_id: str) -> list[Role]:
        """List roles belonging to an organization."""

    @abstractmethod
    async def get_user_role_in_organization(
        self, user_uuid: UUID, org_id: str
    ) -> str | None:
        """Get a user's role in a specific organization."""

    @abstractmethod
    async def update_user_role_in_organization(
        self, user_uuid: UUID, new_role: str
    ) -> None:
        """Update a user's role in their organization."""

    # Permission operations
    @abstractmethod
    async def create_permission(self, permission: Permission) -> None:
        """Create a new permission."""

    @abstractmethod
    async def get_permission(self, permission_id: str) -> Permission:
        """Get permission by ID."""

    @abstractmethod
    async def list_permissions(self) -> list[Permission]:
        """List all permissions."""

    @abstractmethod
    async def update_permission(self, permission: Permission) -> None:
        """Update permission details."""

    @abstractmethod
    async def delete_permission(self, permission_id: str) -> None:
        """Delete permission by ID."""

    @abstractmethod
    async def rename_permission(
        self, old_id: str, new_id: str, display_name: str
    ) -> None:
        """Rename a permission's ID (and display name) updating all references.

        This must update:
            - permissions.id (primary key)
            - org_permissions.permission_id
            - role_permissions.permission_id
        """

    @abstractmethod
    async def add_permission_to_organization(
        self, org_id: str, permission_id: str
    ) -> None:
        """Add a permission to an organization."""

    @abstractmethod
    async def remove_permission_from_organization(
        self, org_id: str, permission_id: str
    ) -> None:
        """Remove a permission from an organization."""

    @abstractmethod
    async def get_organization_permissions(self, org_id: str) -> list[Permission]:
        """Get all permissions assigned to an organization."""

    @abstractmethod
    async def get_permission_organizations(self, permission_id: str) -> list[Org]:
        """Get all organizations that have a specific permission."""

    # Role-permission operations
    @abstractmethod
    async def add_permission_to_role(self, role_uuid: UUID, permission_id: str) -> None:
        """Add a permission to a role."""

    @abstractmethod
    async def remove_permission_from_role(
        self, role_uuid: UUID, permission_id: str
    ) -> None:
        """Remove a permission from a role."""

    @abstractmethod
    async def get_role_permissions(self, role_uuid: UUID) -> list[Permission]:
        """List all permissions granted to a role."""

    @abstractmethod
    async def get_permission_roles(self, permission_id: str) -> list[Role]:
        """List all roles that grant a permission."""

    @abstractmethod
    async def get_role(self, role_uuid: UUID) -> Role:
        """Get a role by UUID, including its permission IDs."""

    # Combined operations
    @abstractmethod
    async def login(self, user_uuid: UUID, credential: Credential) -> None:
        """Update user and credential timestamps after successful login."""

    @abstractmethod
    async def create_user_and_credential(
        self, user: User, credential: Credential
    ) -> None:
        """Create a new user and their first credential in a transaction."""

    @abstractmethod
    async def get_session_context(self, session_key: bytes) -> SessionContext | None:
        """Get complete session context including user, organization, role, and permissions."""

        # Combined atomic operations
        @abstractmethod
        async def create_credential_session(
            self,
            user_uuid: UUID,
            credential: Credential,
            reset_key: bytes | None,
            session_key: bytes,
            session_expires: datetime,
            session_info: dict,
            display_name: str | None = None,
        ) -> None:
            """Atomically add a credential and create a session.

            Steps (single transaction):
                1. Insert credential
                2. Optionally delete old session (e.g. reset token) if provided
                3. Optionally update user's display name
                4. Insert new session referencing the credential
                5. Update user's last_seen and increment visits (treat as a login)
            """


__all__ = [
    "User",
    "Credential",
    "Session",
    "SessionContext",
    "Org",
    "Role",
    "Permission",
    "DatabaseInterface",
]
