# -*- coding: utf-8 -*-
"""Location: ./tests/unit/mcpgateway/routers/test_teams_v2.py
Copyright 2026
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

Comprehensive unit tests for teams router - V2 with improved RBAC mocking.
This module tests all team management endpoints including CRUD operations,
member management, invitations, and join requests.
"""

# Standard
from datetime import datetime, timezone
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4

# Third-Party
import pytest
from fastapi import HTTPException, status
from sqlalchemy.orm import Session


# First, patch RBAC decorators before any mcpgateway imports
def mock_require_permission_decorator(permission: str, resource_type=None):
    """Mock decorator that bypasses permission checks."""

    def decorator(func):
        return func

    return decorator


def mock_require_admin_permission():
    """Mock decorator that bypasses admin permission checks."""

    def decorator(func):
        return func

    return decorator


# Apply the patches before importing mcpgateway modules
with patch("mcpgateway.middleware.rbac.require_permission", mock_require_permission_decorator):
    with patch("mcpgateway.middleware.rbac.require_admin_permission", mock_require_admin_permission):
        # Now import mcpgateway modules with mocked decorators
        from mcpgateway.db import EmailTeam, EmailTeamMember, EmailUser
        from mcpgateway.routers import teams
        from mcpgateway.schemas import (
            EmailUserResponse,
            TeamCreateRequest,
            TeamMemberAddRequest,
            TeamMemberUpdateRequest,
            TeamUpdateRequest,
        )
        from mcpgateway.services.team_management_service import TeamManagementService


def mock_permission_check(is_admin=False):
    """Helper context manager to mock PermissionService.check_platform_admin_permission."""
    from contextlib import contextmanager
    from unittest.mock import AsyncMock, patch

    @contextmanager
    def _mock():
        with patch("mcpgateway.routers.teams.PermissionService") as MockPermissionService:
            mock_perm_service = AsyncMock()
            mock_perm_service.check_platform_admin_permission = AsyncMock(return_value=is_admin)
            MockPermissionService.return_value = mock_perm_service
            yield mock_perm_service

    return _mock()


class TestTeamsRouterV2:
    """Comprehensive test suite for teams router endpoints - V2."""

    @pytest.fixture
    def mock_db(self):
        """Create mock database session."""
        return MagicMock(spec=Session)

    @pytest.fixture
    def mock_current_user(self):
        """Create mock current user."""
        user = EmailUserResponse(
            email="test@example.com", full_name="Test User", is_admin=False, is_active=True, auth_provider="basic", created_at=datetime.now(timezone.utc), last_login=datetime.now(timezone.utc)
        )
        return user

    @pytest.fixture
    def mock_user_context(self, mock_db):
        """Create mock user context with permissions."""
        return {"email": "test@example.com", "full_name": "Test User", "is_admin": False, "db": mock_db, "permissions": ["teams.create", "teams.read", "teams.update", "teams.delete"]}

    @pytest.fixture
    def mock_team(self):
        """Create mock team."""
        team = MagicMock(spec=EmailTeam)
        team.id = str(uuid4())
        team.name = "Test Team"
        team.slug = "test-team"
        team.description = "A test team"
        team.created_by = "test@example.com"
        team.is_personal = False
        team.visibility = "private"
        team.max_members = 100
        team.created_at = datetime.now(timezone.utc)
        team.updated_at = datetime.now(timezone.utc)
        team.is_active = True
        team.get_member_count = MagicMock(return_value=1)
        return team

    @pytest.fixture
    def mock_team_member(self):
        """Create mock team member."""
        member = MagicMock(spec=EmailTeamMember)
        member.id = str(uuid4())
        member.team_id = str(uuid4())
        member.user_email = "member@example.com"
        member.role = "member"
        member.joined_at = datetime.now(timezone.utc)
        member.invited_by = "owner@example.com"
        member.is_active = True
        member.grant_source = None
        return member

    # =========================================================================
    # Team CRUD Operations Tests
    # =========================================================================

    @pytest.mark.asyncio
    async def test_create_team_success(self, mock_user_context, mock_team, mock_db):
        """Test successful team creation."""
        request = TeamCreateRequest(name="New Team", description="A new team", visibility="private", max_members=50)

        with mock_permission_check(is_admin=False), \
             patch("mcpgateway.routers.teams.TeamManagementService") as MockService:

            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.create_team = AsyncMock(return_value=mock_team)
            MockService.return_value = mock_service

            result = await teams.create_team(request, current_user_ctx=mock_user_context, db=mock_db)

            assert result.id == mock_team.id
            assert result.name == mock_team.name
            assert result.description == mock_team.description
            mock_service.create_team.assert_called_once_with(
                name=request.name, description=request.description, created_by=mock_user_context["email"], visibility=request.visibility, max_members=request.max_members, skip_limits=False
            )

    @pytest.mark.asyncio
    async def test_create_team_value_error(self, mock_user_context):
        """Test team creation with invalid data."""
        request = TeamCreateRequest(
            name="Valid Name",  # Valid name to pass Pydantic validation
            description="A new team",
            visibility="private",
            max_members=50,
        )

        with mock_permission_check(is_admin=False), \
             patch("mcpgateway.routers.teams.TeamManagementService") as MockService:

            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.create_team = AsyncMock(side_effect=ValueError("Team name cannot be empty"))
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.create_team(request, current_user_ctx=mock_user_context)

            assert exc_info.value.status_code == status.HTTP_400_BAD_REQUEST
            assert "Team name cannot be empty" in str(exc_info.value.detail)

    @pytest.mark.asyncio
    async def test_get_team_success(self, mock_user_context, mock_db, mock_team):
        """Test getting a specific team successfully."""
        team_id = mock_team.id

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_team_by_id = AsyncMock(return_value=mock_team)
            mock_service.get_user_role_in_team = AsyncMock(return_value="member")
            MockService.return_value = mock_service

            result = await teams.get_team(team_id, current_user=mock_user_context, db=mock_db)

            assert result.id == mock_team.id
            assert result.name == mock_team.name
            mock_service.get_team_by_id.assert_called_once_with(team_id)
            mock_service.get_user_role_in_team.assert_called_once_with(mock_user_context["email"], team_id)

    @pytest.mark.asyncio
    async def test_get_team_not_found(self, mock_user_context, mock_db):
        """Test getting a non-existent team."""
        team_id = str(uuid4())

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_team_by_id = AsyncMock(return_value=None)
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.get_team(team_id, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_404_NOT_FOUND
            assert "Team not found" in str(exc_info.value.detail)

    @pytest.mark.asyncio
    async def test_update_team_success(self, mock_user_context, mock_db, mock_team):
        """Test updating a team successfully."""
        team_id = mock_team.id
        request = TeamUpdateRequest(name="Updated Team", description="Updated description", visibility="public", max_members=100)

        with mock_permission_check(is_admin=False), \
             patch("mcpgateway.routers.teams.TeamManagementService") as MockService:

            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.update_team = AsyncMock(return_value=True)  # Returns bool, not team
            mock_service.get_team_by_id = AsyncMock(return_value=mock_team)  # Fetches team after update
            MockService.return_value = mock_service

            result = await teams.update_team(team_id, request, current_user=mock_user_context, db=mock_db)

            assert result.id == mock_team.id
            mock_service.update_team.assert_called_once_with(
                team_id=team_id, name=request.name, description=request.description, visibility=request.visibility, max_members=request.max_members, skip_limits=False
            )

    @pytest.mark.asyncio
    async def test_delete_team_success(self, mock_user_context, mock_db):
        """Test deleting a team successfully."""
        team_id = str(uuid4())

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.delete_team = AsyncMock(return_value=True)
            MockService.return_value = mock_service

            result = await teams.delete_team(team_id, current_user=mock_user_context, db=mock_db)

            assert result.message == "Team deleted successfully"
            mock_service.delete_team.assert_called_once_with(team_id, mock_user_context["email"])

    # =========================================================================
    # Team Member Management Tests
    # =========================================================================

    @pytest.mark.asyncio
    async def test_list_team_members_success(self, mock_user_context, mock_db, mock_team_member):
        """Test listing team members successfully."""
        team_id = str(uuid4())

        # Mock user object to pair with membership
        mock_user = MagicMock(spec=EmailUser)
        mock_user.email = mock_team_member.user_email
        mock_user.full_name = "Test User"

        # When cursor=None and limit=None, get_team_members returns just a list
        members_tuples = [(mock_user, mock_team_member)]

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="member")
            mock_service.get_team_members = AsyncMock(return_value=members_tuples)
            MockService.return_value = mock_service

            result = await teams.list_team_members(team_id=team_id, cursor=None, limit=None, include_pagination=False, current_user=mock_user_context, db=mock_db)

            assert isinstance(result, list)
            assert len(result) == 1
            assert result[0].user_email == mock_team_member.user_email
            assert result[0].role == mock_team_member.role

    @pytest.mark.asyncio
    async def test_add_team_member_success(self, mock_user_context, mock_db, mock_team_member):
        """Test adding a team member successfully."""
        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="newmember@example.com", role="member")

        # Create a new member object for the added member
        new_member = MagicMock(spec=EmailTeamMember)
        new_member.id = str(uuid4())
        new_member.team_id = team_id
        new_member.user_email = request.email
        new_member.role = request.role
        new_member.joined_at = datetime.now(timezone.utc)
        new_member.invited_by = mock_user_context["email"]
        new_member.is_active = True
        new_member.grant_source = None

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.add_member_to_team = AsyncMock(return_value=new_member)
            MockService.return_value = mock_service

            result = await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert result.user_email == request.email
            assert result.role == request.role
            assert result.team_id == team_id
            assert result.invited_by == mock_user_context["email"]
            mock_service.add_member_to_team.assert_called_once_with(team_id, request.email, request.role, invited_by=mock_user_context["email"])

    @pytest.mark.asyncio
    async def test_add_team_member_insufficient_permissions(self, mock_user_context, mock_db):
        """Test adding a team member without owner permissions."""
        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="newmember@example.com", role="member")

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="member")
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_403_FORBIDDEN
            assert "Access denied" in str(exc_info.value.detail)

    @pytest.mark.asyncio
    async def test_add_team_member_add_fails(self, mock_user_context, mock_db):
        """Test adding a team member when add operation fails."""
        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="newmember@example.com", role="member")

        from mcpgateway.services.team_management_service import UserNotFoundError

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.add_member_to_team = AsyncMock(side_effect=UserNotFoundError("User not found"))
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_404_NOT_FOUND
            assert exc_info.value.detail == "User not found"

    @pytest.mark.asyncio
    async def test_add_team_member_not_found_after_adding(self, mock_user_context, mock_db):
        """Test adding a team member when member not found after adding."""
        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="newmember@example.com", role="member")

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.add_member_to_team = AsyncMock(return_value=None)  # Returns None to simulate failure
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
            assert "Failed to add team member" in str(exc_info.value.detail)

    @pytest.mark.asyncio
    async def test_add_team_member_value_error(self, mock_user_context, mock_db):
        """Test adding a team member with invalid role."""
        from mcpgateway.services.team_management_service import InvalidRoleError

        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="newmember@example.com", role="member")

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.add_member_to_team = AsyncMock(side_effect=InvalidRoleError("Invalid role 'invalid'"))
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_400_BAD_REQUEST
            assert "Invalid role" in str(exc_info.value.detail)

    @pytest.mark.asyncio
    async def test_add_team_member_team_not_found(self, mock_user_context, mock_db):
        """Test adding a team member when team is not found."""
        from mcpgateway.services.team_management_service import TeamNotFoundError

        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="newmember@example.com", role="member")

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.add_member_to_team = AsyncMock(side_effect=TeamNotFoundError("Team not found"))
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_404_NOT_FOUND
            assert "Team not found" in str(exc_info.value.detail)

    @pytest.mark.asyncio
    async def test_add_team_member_already_exists(self, mock_user_context, mock_db):
        """Test adding a team member when member already exists."""
        from mcpgateway.services.team_management_service import MemberAlreadyExistsError

        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="existing@example.com", role="member")

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.add_member_to_team = AsyncMock(side_effect=MemberAlreadyExistsError("Member already exists"))
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_409_CONFLICT
            assert "Member already exists" in str(exc_info.value.detail)

    @pytest.mark.asyncio
    async def test_add_team_member_limit_exceeded(self, mock_user_context, mock_db):
        """Test adding a team member when team member limit is exceeded."""
        from mcpgateway.services.team_management_service import TeamMemberLimitExceededError

        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="newmember@example.com", role="member")

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.add_member_to_team = AsyncMock(side_effect=TeamMemberLimitExceededError("Team member limit exceeded"))
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_400_BAD_REQUEST
            assert "Team member limit exceeded" in str(exc_info.value.detail)

    @pytest.mark.asyncio
    async def test_add_team_member_database_error(self, mock_user_context, mock_db):
        """Test adding a team member with database error."""
        from mcpgateway.services.team_management_service import TeamMemberAddError

        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="newmember@example.com", role="member")

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.add_member_to_team = AsyncMock(side_effect=TeamMemberAddError("Failed to add member to team"))
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
            assert "Failed to add member to team" in str(exc_info.value.detail)

    @pytest.mark.asyncio
    async def test_add_team_member_personal_team_rejected(self, mock_user_context, mock_db):
        """Test adding a team member to a personal team is rejected."""
        from mcpgateway.services.team_management_service import TeamManagementError

        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="newmember@example.com", role="member")

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.add_member_to_team = AsyncMock(side_effect=TeamManagementError("Cannot add members to personal teams"))
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_400_BAD_REQUEST
            assert "Cannot add members to personal teams" in str(exc_info.value.detail)

    @pytest.mark.asyncio
    async def test_add_team_member_as_owner_role(self, mock_user_context, mock_db):
        """Test adding a team member with owner role."""
        team_id = str(uuid4())
        request = TeamMemberAddRequest(email="newowner@example.com", role="owner")

        # Create a new owner member
        new_owner = MagicMock(spec=EmailTeamMember)
        new_owner.id = str(uuid4())
        new_owner.team_id = team_id
        new_owner.user_email = request.email
        new_owner.role = "owner"
        new_owner.joined_at = datetime.now(timezone.utc)
        new_owner.invited_by = mock_user_context["email"]
        new_owner.is_active = True
        new_owner.grant_source = None

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.add_member_to_team = AsyncMock(return_value=new_owner)
            MockService.return_value = mock_service

            result = await teams.add_team_member(team_id, request, current_user=mock_user_context, db=mock_db)

            assert result.user_email == request.email
            assert result.role == "owner"
            mock_service.add_member_to_team.assert_called_once_with(team_id, request.email, "owner", invited_by=mock_user_context["email"])

    @pytest.mark.asyncio
    async def test_update_team_member_success(self, mock_user_context, mock_db, mock_team_member):
        """Test updating a team member's role successfully."""
        team_id = str(uuid4())
        user_email = "member@example.com"
        request = TeamMemberUpdateRequest(role="owner")

        mock_team_member.role = "owner"  # Updated role

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.update_member_role = AsyncMock(return_value=True)  # Returns bool, not member
            mock_service.get_member = AsyncMock(return_value=mock_team_member)  # Fetches member after update
            MockService.return_value = mock_service

            result = await teams.update_team_member(team_id, user_email, request, current_user=mock_user_context, db=mock_db)

            assert result.role == "owner"
            mock_service.update_member_role.assert_called_once_with(team_id, user_email, request.role)

    @pytest.mark.asyncio
    async def test_remove_team_member_as_owner(self, mock_user_context, mock_db):
        """Test removing a team member as team owner."""
        team_id = str(uuid4())
        user_email = "member@example.com"

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_user_role_in_team = AsyncMock(return_value="owner")
            mock_service.remove_member_from_team = AsyncMock(return_value=True)
            MockService.return_value = mock_service

            result = await teams.remove_team_member(team_id, user_email, current_user=mock_user_context, db=mock_db)

            assert result.message == "Team member removed successfully"
            mock_service.remove_member_from_team.assert_called_once_with(team_id, user_email)

    # =========================================================================
    # Error Handling Tests
    # =========================================================================

    @pytest.mark.asyncio
    async def test_team_operation_with_database_error(self, mock_user_context, mock_db):
        """Test handling of database errors in team operations."""
        team_id = str(uuid4())

        with patch("mcpgateway.routers.teams.TeamManagementService") as MockService:
            mock_service = AsyncMock(spec=TeamManagementService)
            mock_service.get_team_by_id = AsyncMock(side_effect=Exception("Database connection lost"))
            MockService.return_value = mock_service

            with pytest.raises(HTTPException) as exc_info:
                await teams.get_team(team_id, current_user=mock_user_context, db=mock_db)

            assert exc_info.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
            assert "Failed to get team" in str(exc_info.value.detail)
