"""Project membership mixin for gRPC service."""

from __future__ import annotations

from typing import TYPE_CHECKING
from uuid import UUID

from noteflow.config.constants import (
    ERROR_INVALID_PROJECT_ID_PREFIX,
    ERROR_INVALID_UUID_PREFIX,
    ERROR_PROJECT_ID_REQUIRED,
    ERROR_USER_ID_REQUIRED,
)

from ...proto import noteflow_pb2
from ..errors import (
    ENTITY_PROJECT,
    abort_invalid_argument,
    abort_not_found,
    require_feature_projects,
    require_project_service,
)
from ._converters import (
    membership_to_proto,
    proto_to_project_role,
)

if TYPE_CHECKING:
    from .._types import GrpcContext
    from ._types import ProjectServicer


class ProjectMembershipMixin:
    """Mixin providing project membership functionality.

    Require host to implement ProjectServicer protocol.
    Provide CRUD operations for project memberships.
    """

    async def AddProjectMember(
        self: ProjectServicer,
        request: noteflow_pb2.AddProjectMemberRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.ProjectMembershipProto:
        """Add a member to a project."""
        project_service = await require_project_service(self.project_service, context)

        if not request.project_id:
            await abort_invalid_argument(context, ERROR_PROJECT_ID_REQUIRED)

        if not request.user_id:
            await abort_invalid_argument(context, ERROR_USER_ID_REQUIRED)

        try:
            project_id = UUID(request.project_id)
            user_id = UUID(request.user_id)
        except ValueError as e:
            await abort_invalid_argument(context, f"{ERROR_INVALID_UUID_PREFIX}{e}")
            raise  # Unreachable but helps type checker

        role = proto_to_project_role(request.role)

        async with self.create_repository_provider() as uow:
            await require_feature_projects(uow, context)

            membership = await project_service.add_project_member(
                uow=uow,
                project_id=project_id,
                user_id=user_id,
                role=role,
            )
            if membership is None:
                await abort_not_found(context, ENTITY_PROJECT, request.project_id)
                raise  # Unreachable but helps type checker

            return membership_to_proto(membership)

    async def UpdateProjectMemberRole(
        self: ProjectServicer,
        request: noteflow_pb2.UpdateProjectMemberRoleRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.ProjectMembershipProto:
        """Update a project member's role."""
        project_service = await require_project_service(self.project_service, context)

        if not request.project_id:
            await abort_invalid_argument(context, ERROR_PROJECT_ID_REQUIRED)

        if not request.user_id:
            await abort_invalid_argument(context, ERROR_USER_ID_REQUIRED)

        try:
            project_id = UUID(request.project_id)
            user_id = UUID(request.user_id)
        except ValueError as e:
            await abort_invalid_argument(context, f"{ERROR_INVALID_UUID_PREFIX}{e}")
            raise  # Unreachable but helps type checker

        role = proto_to_project_role(request.role)

        async with self.create_repository_provider() as uow:
            await require_feature_projects(uow, context)

            membership = await project_service.update_project_member_role(
                uow=uow,
                project_id=project_id,
                user_id=user_id,
                role=role,
            )
            if membership is None:
                await abort_not_found(context, "Membership", f"{request.project_id}/{request.user_id}")
                raise  # Unreachable but helps type checker

            return membership_to_proto(membership)

    async def RemoveProjectMember(
        self: ProjectServicer,
        request: noteflow_pb2.RemoveProjectMemberRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.RemoveProjectMemberResponse:
        """Remove a member from a project."""
        project_service = await require_project_service(self.project_service, context)

        if not request.project_id:
            await abort_invalid_argument(context, ERROR_PROJECT_ID_REQUIRED)

        if not request.user_id:
            await abort_invalid_argument(context, ERROR_USER_ID_REQUIRED)

        try:
            project_id = UUID(request.project_id)
            user_id = UUID(request.user_id)
        except ValueError as e:
            await abort_invalid_argument(context, f"{ERROR_INVALID_UUID_PREFIX}{e}")
            raise  # Unreachable but helps type checker

        async with self.create_repository_provider() as uow:
            await require_feature_projects(uow, context)

            removed = await project_service.remove_project_member(
                uow=uow,
                project_id=project_id,
                user_id=user_id,
            )
            return noteflow_pb2.RemoveProjectMemberResponse(success=removed)

    async def ListProjectMembers(
        self: ProjectServicer,
        request: noteflow_pb2.ListProjectMembersRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.ListProjectMembersResponse:
        """List members of a project."""
        project_service = await require_project_service(self.project_service, context)

        if not request.project_id:
            await abort_invalid_argument(context, ERROR_PROJECT_ID_REQUIRED)

        try:
            project_id = UUID(request.project_id)
        except ValueError:
            await abort_invalid_argument(context, f"{ERROR_INVALID_PROJECT_ID_PREFIX}{request.project_id}")
            raise  # Unreachable but helps type checker

        limit = request.limit if request.limit > 0 else 100
        offset = max(request.offset, 0)

        async with self.create_repository_provider() as uow:
            await require_feature_projects(uow, context)

            members = await project_service.list_project_members(
                uow=uow,
                project_id=project_id,
                limit=limit,
                offset=offset,
            )

            total_count = await project_service.count_project_members(
                uow=uow,
                project_id=project_id,
            )

            return noteflow_pb2.ListProjectMembersResponse(
                members=[membership_to_proto(m) for m in members],
                total_count=total_count,
            )
