"""Identity and context management application service.

Orchestrates user identity and workspace context for local-first multi-user support.
Following hexagonal architecture:
    gRPC interceptor → IdentityService (application) → Repositories (infrastructure)
"""

from __future__ import annotations

from typing import TYPE_CHECKING
from uuid import UUID, uuid4

from noteflow.config.constants import ERROR_MSG_WORKSPACE_PREFIX
from noteflow.domain.entities.project import slugify
from noteflow.domain.identity import (
    DEFAULT_PROJECT_NAME,
    DEFAULT_USER_DISPLAY_NAME,
    DEFAULT_WORKSPACE_NAME,
    OperationContext,
    User,
    UserContext,
    Workspace,
    WorkspaceContext,
    WorkspaceRole,
)
from noteflow.infrastructure.logging import get_logger
from noteflow.infrastructure.persistence.models import (
    DEFAULT_USER_ID,
    DEFAULT_WORKSPACE_ID,
)

if TYPE_CHECKING:
    from collections.abc import Sequence

    from noteflow.domain.ports.unit_of_work import UnitOfWork

logger = get_logger(__name__)


class IdentityService:
    """Application service for identity and workspace context management.

    Provide a clean interface for identity operations, abstracting away
    the infrastructure details (database persistence, default creation).

    Orchestrates:
    - Default user and workspace creation on first run
    - Operation context resolution
    - Workspace membership management
    """

    async def get_or_create_default_user(
        self,
        uow: UnitOfWork,
    ) -> UserContext:
        """Get or create the default local user.

        For local-first mode, create a default user on first run.

        Args:
            uow: Unit of work for database access.

        Returns:
            User context for the default user.
        """
        if not uow.supports_users:
            # Return a synthetic context for memory mode
            logger.debug("Memory mode: returning synthetic default user context")
            return UserContext(
                user_id=UUID(DEFAULT_USER_ID),
                display_name=DEFAULT_USER_DISPLAY_NAME,
            )

        user = await uow.users.get_default()
        if user:
            logger.debug("Found existing default user: %s", user.id)
            return UserContext(
                user_id=user.id,
                display_name=user.display_name,
                email=user.email,
            )

        # Create default user
        user_id = UUID(DEFAULT_USER_ID)
        user = await uow.users.create_default(
            user_id=user_id,
            display_name=DEFAULT_USER_DISPLAY_NAME,
        )
        await uow.commit()

        logger.info("Created default local user: %s", user_id)

        return UserContext(
            user_id=user_id,
            display_name=DEFAULT_USER_DISPLAY_NAME,
        )

    async def get_or_create_default_workspace(
        self,
        uow: UnitOfWork,
        user_id: UUID,
    ) -> WorkspaceContext:
        """Get or create the default workspace for a user.

        For local-first mode, each user has a default "Personal" workspace.

        Args:
            uow: Unit of work for database access.
            user_id: User UUID.

        Returns:
            Workspace context for the default workspace.
        """
        if not uow.supports_workspaces:
            # Return a synthetic context for memory mode
            logger.debug("Memory mode: returning synthetic default workspace context")
            return WorkspaceContext(
                workspace_id=UUID(DEFAULT_WORKSPACE_ID),
                workspace_name=DEFAULT_WORKSPACE_NAME,
                role=WorkspaceRole.OWNER,
            )

        workspace = await uow.workspaces.get_default_for_user(user_id)
        if workspace:
            logger.debug(
                "Found existing default workspace for user %s: %s",
                user_id,
                workspace.id,
            )
            membership = await uow.workspaces.get_membership(workspace.id, user_id)
            role = WorkspaceRole(membership.role.value) if membership else WorkspaceRole.OWNER
            return WorkspaceContext(
                workspace_id=workspace.id,
                workspace_name=workspace.name,
                role=role,
            )

        # Create default workspace
        workspace_id = UUID(DEFAULT_WORKSPACE_ID)
        workspace = await uow.workspaces.create(
            workspace_id=workspace_id,
            name=DEFAULT_WORKSPACE_NAME,
            owner_id=user_id,
            is_default=True,
        )
        await uow.commit()

        logger.info("Created default workspace for user %s: %s", user_id, workspace_id)

        return WorkspaceContext(
            workspace_id=workspace_id,
            workspace_name=DEFAULT_WORKSPACE_NAME,
            role=WorkspaceRole.OWNER,
        )

    async def get_context(
        self,
        uow: UnitOfWork,
        workspace_id: UUID | None = None,
        request_id: str | None = None,
    ) -> OperationContext:
        """Get the full operation context.

        Resolve user identity and workspace scope for an operation.

        Args:
            uow: Unit of work for database access.
            workspace_id: Optional specific workspace, or default.
            request_id: Optional request correlation ID.

        Returns:
            Full operation context with user and workspace.

        Raises:
            ValueError: If workspace not found.
            PermissionError: If user not a member of workspace.
        """
        user = await self.get_or_create_default_user(uow)

        if workspace_id:
            logger.info(
                "Resolving context for explicit workspace_id=%s, user_id=%s",
                workspace_id,
                user.user_id,
            )
            ws_context = await self._get_workspace_context(uow, workspace_id, user.user_id)
        else:
            logger.debug("No workspace_id provided, using default workspace")
            ws_context = await self.get_or_create_default_workspace(uow, user.user_id)

        logger.debug(
            "Resolved operation context: user=%s, workspace=%s, request_id=%s",
            user.user_id,
            ws_context.workspace_id,
            request_id,
        )
        return OperationContext(
            user=user,
            workspace=ws_context,
            request_id=request_id,
        )

    async def _get_workspace_context(
        self,
        uow: UnitOfWork,
        workspace_id: UUID,
        user_id: UUID,
    ) -> WorkspaceContext:
        """Get workspace context for a specific workspace.

        Args:
            uow: Unit of work for database access.
            workspace_id: Workspace UUID.
            user_id: User UUID.

        Returns:
            Workspace context.

        Raises:
            ValueError: If workspace not found.
            PermissionError: If user not a member.
        """
        if not uow.supports_workspaces:
            logger.debug("Memory mode: returning synthetic workspace context for %s", workspace_id)
            return WorkspaceContext(
                workspace_id=workspace_id,
                workspace_name=DEFAULT_WORKSPACE_NAME,
                role=WorkspaceRole.OWNER,
            )

        logger.debug("Looking up workspace %s for user %s", workspace_id, user_id)
        workspace = await uow.workspaces.get(workspace_id)
        if not workspace:
            logger.warning("Workspace not found: %s", workspace_id)
            msg = f"{ERROR_MSG_WORKSPACE_PREFIX}{workspace_id} not found"
            raise ValueError(msg)

        membership = await uow.workspaces.get_membership(workspace_id, user_id)
        if not membership:
            logger.warning(
                "Permission denied: user %s is not a member of workspace %s",
                user_id,
                workspace_id,
            )
            msg = f"User not a member of workspace {workspace_id}"
            raise PermissionError(msg)

        logger.debug(
            "Workspace access granted: user=%s, workspace=%s, role=%s",
            user_id,
            workspace_id,
            membership.role,
        )
        return WorkspaceContext(
            workspace_id=workspace.id,
            workspace_name=workspace.name,
            role=membership.role,
        )

    async def list_workspaces(
        self,
        uow: UnitOfWork,
        user_id: UUID,
        limit: int = 50,
        offset: int = 0,
    ) -> Sequence[Workspace]:
        """List workspaces a user is a member of.

        Args:
            uow: Unit of work for database access.
            user_id: User UUID.
            limit: Maximum workspaces to return.
            offset: Pagination offset.

        Returns:
            List of workspaces.
        """
        if not uow.supports_workspaces:
            logger.debug("Memory mode: returning empty workspace list")
            return []

        workspaces = await uow.workspaces.list_for_user(user_id, limit, offset)
        logger.debug(
            "Listed %d workspaces for user %s (limit=%d, offset=%d)",
            len(workspaces),
            user_id,
            limit,
            offset,
        )
        return workspaces

    async def create_workspace(
        self,
        uow: UnitOfWork,
        name: str,
        owner_id: UUID,
        slug: str | None = None,
    ) -> Workspace:
        """Create a new workspace with a default project.

        Each workspace must have exactly one default project. This method
        creates both the workspace and its default project atomically.

        Args:
            uow: Unit of work for database access.
            name: Workspace name.
            owner_id: User UUID of the owner.
            slug: Optional URL slug.

        Returns:
            Created workspace.

        Raises:
            NotImplementedError: If workspaces not supported.
        """
        if not uow.supports_workspaces:
            msg = "Workspaces require database persistence"
            raise NotImplementedError(msg)

        workspace_id = uuid4()
        workspace = await uow.workspaces.create(
            workspace_id=workspace_id,
            name=name,
            owner_id=owner_id,
            slug=slug,
        )

        # Create default project for the workspace (if projects are supported)
        if uow.supports_projects:
            project_id = uuid4()
            await uow.projects.create(
                project_id=project_id,
                workspace_id=workspace_id,
                name=DEFAULT_PROJECT_NAME,
                slug=slugify(DEFAULT_PROJECT_NAME),
                description=f"{DEFAULT_PROJECT_NAME} project for this workspace",
                is_default=True,
            )
            logger.info("Created default project for workspace %s", workspace_id)

        await uow.commit()

        logger.info("Created workspace %s: %s", workspace.name, workspace_id)
        return workspace

    async def get_user(
        self,
        uow: UnitOfWork,
        user_id: UUID,
    ) -> User | None:
        """Get user by ID.

        Args:
            uow: Unit of work for database access.
            user_id: User UUID.

        Returns:
            User if found, None otherwise.
        """
        if not uow.supports_users:
            logger.debug("Memory mode: users not supported, returning None")
            return None

        user = await uow.users.get(user_id)
        if user:
            logger.debug("Found user: %s", user_id)
        else:
            logger.debug("User not found: %s", user_id)
        return user

    async def update_user_profile(
        self,
        uow: UnitOfWork,
        user_id: UUID,
        display_name: str | None = None,
        email: str | None = None,
    ) -> User | None:
        """Update user profile.

        Args:
            uow: Unit of work for database access.
            user_id: User UUID.
            display_name: New display name (optional).
            email: New email (optional).

        Returns:
            Updated user if found, None otherwise.

        Raises:
            NotImplementedError: If users not supported.
        """
        if not uow.supports_users:
            msg = "Users require database persistence"
            raise NotImplementedError(msg)

        user = await uow.users.get(user_id)
        if not user:
            logger.warning("User not found for profile update: %s", user_id)
            return None

        updated_fields: list[str] = []
        if display_name:
            user.display_name = display_name
            updated_fields.append("display_name")
        if email is not None:
            user.email = email
            updated_fields.append("email")

        if not updated_fields:
            logger.debug("No fields to update for user %s", user_id)
            return user

        updated = await uow.users.update(user)
        await uow.commit()

        logger.info(
            "Updated user profile: user_id=%s, fields=%s",
            user_id,
            ", ".join(updated_fields),
        )
        return updated
