# tests/unit/services/test_vendor_team_service.py
"""Unit tests for VendorTeamService."""

import uuid
from datetime import datetime, timedelta
from unittest.mock import patch

import pytest

from app.modules.tenancy.exceptions import (
    CannotRemoveOwnerException,
    InvalidInvitationTokenException,
    TeamInvitationAlreadyAcceptedException,
    TeamMemberAlreadyExistsException,
    UserNotFoundException,
)
from app.modules.tenancy.models import Role, User, Vendor, VendorUser, VendorUserType
from app.modules.tenancy.services.vendor_team_service import vendor_team_service


# =============================================================================
# FIXTURES
# =============================================================================


@pytest.fixture
def team_vendor(db, test_company):
    """Create a vendor for team tests."""
    unique_id = str(uuid.uuid4())[:8]
    vendor = Vendor(
        company_id=test_company.id,
        vendor_code=f"TEAMVENDOR_{unique_id.upper()}",
        subdomain=f"teamvendor{unique_id.lower()}",
        name=f"Team Vendor {unique_id}",
        is_active=True,
        is_verified=True,
    )
    db.add(vendor)
    db.commit()
    db.refresh(vendor)
    return vendor


@pytest.fixture
def vendor_owner(db, team_vendor, test_user):
    """Create an owner for the team vendor."""
    vendor_user = VendorUser(
        vendor_id=team_vendor.id,
        user_id=test_user.id,
        user_type=VendorUserType.OWNER.value,
        is_active=True,
    )
    db.add(vendor_user)
    db.commit()
    db.refresh(vendor_user)
    return vendor_user


@pytest.fixture
def team_member(db, team_vendor, other_user, auth_manager):
    """Create a team member for the vendor."""
    # Create a role first
    role = Role(
        vendor_id=team_vendor.id,
        name="staff",
        permissions=["orders.view", "products.view"],
    )
    db.add(role)
    db.commit()

    vendor_user = VendorUser(
        vendor_id=team_vendor.id,
        user_id=other_user.id,
        user_type=VendorUserType.TEAM_MEMBER.value,
        role_id=role.id,
        is_active=True,
        invitation_accepted_at=datetime.utcnow(),
    )
    db.add(vendor_user)
    db.commit()
    db.refresh(vendor_user)
    return vendor_user


@pytest.fixture
def pending_invitation(db, team_vendor, test_user, auth_manager):
    """Create a pending invitation."""
    unique_id = str(uuid.uuid4())[:8]

    # Create new user for invitation
    new_user = User(
        email=f"pending_{unique_id}@example.com",
        username=f"pending_{unique_id}",
        hashed_password=auth_manager.hash_password("temppass"),
        role="vendor",
        is_active=False,
    )
    db.add(new_user)
    db.commit()

    # Create role
    role = Role(
        vendor_id=team_vendor.id,
        name="support",
        permissions=["support.view"],
    )
    db.add(role)
    db.commit()

    # Create pending vendor user
    vendor_user = VendorUser(
        vendor_id=team_vendor.id,
        user_id=new_user.id,
        user_type=VendorUserType.TEAM_MEMBER.value,
        role_id=role.id,
        invited_by=test_user.id,
        invitation_token=f"pending_token_{unique_id}",
        invitation_sent_at=datetime.utcnow(),
        is_active=False,
    )
    db.add(vendor_user)
    db.commit()
    db.refresh(vendor_user)
    return vendor_user


@pytest.fixture
def expired_invitation(db, team_vendor, test_user, auth_manager):
    """Create an expired invitation."""
    unique_id = str(uuid.uuid4())[:8]

    # Create new user for invitation
    new_user = User(
        email=f"expired_{unique_id}@example.com",
        username=f"expired_{unique_id}",
        hashed_password=auth_manager.hash_password("temppass"),
        role="vendor",
        is_active=False,
    )
    db.add(new_user)
    db.commit()

    # Create role
    role = Role(
        vendor_id=team_vendor.id,
        name="viewer",
        permissions=["read_only"],
    )
    db.add(role)
    db.commit()

    # Create expired vendor user (sent 8 days ago, expires in 7)
    vendor_user = VendorUser(
        vendor_id=team_vendor.id,
        user_id=new_user.id,
        user_type=VendorUserType.TEAM_MEMBER.value,
        role_id=role.id,
        invited_by=test_user.id,
        invitation_token=f"expired_token_{unique_id}",
        invitation_sent_at=datetime.utcnow() - timedelta(days=8),
        is_active=False,
    )
    db.add(vendor_user)
    db.commit()
    db.refresh(vendor_user)
    return vendor_user


# =============================================================================
# INVITE TEAM MEMBER TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestVendorTeamServiceInvite:
    """Test suite for inviting team members."""

    @patch("app.modules.billing.services.subscription_service.SubscriptionService.check_team_limit")
    def test_invite_new_user(self, mock_check_limit, db, team_vendor, test_user):
        """Test inviting a new user to the team."""
        mock_check_limit.return_value = None  # No limit reached
        unique_id = str(uuid.uuid4())[:8]
        email = f"newmember_{unique_id}@example.com"

        result = vendor_team_service.invite_team_member(
            db=db,
            vendor=team_vendor,
            inviter=test_user,
            email=email,
            role_name="staff",
        )
        db.commit()

        assert result is not None
        assert result["email"] == email
        assert result["invitation_token"] is not None
        assert result["role"] == "staff"
        assert result["existing_user"] is False

    @patch("app.modules.billing.services.subscription_service.SubscriptionService.check_team_limit")
    def test_invite_existing_user(self, mock_check_limit, db, team_vendor, test_user, other_user):
        """Test inviting an existing user to the team."""
        mock_check_limit.return_value = None

        result = vendor_team_service.invite_team_member(
            db=db,
            vendor=team_vendor,
            inviter=test_user,
            email=other_user.email,
            role_name="manager",
        )
        db.commit()

        assert result is not None
        assert result["email"] == other_user.email
        assert result["invitation_token"] is not None

    @patch("app.modules.billing.services.subscription_service.SubscriptionService.check_team_limit")
    def test_invite_already_member_raises_error(
        self, mock_check_limit, db, team_vendor, test_user, team_member
    ):
        """Test inviting an existing member raises exception."""
        mock_check_limit.return_value = None

        with pytest.raises(TeamMemberAlreadyExistsException):
            vendor_team_service.invite_team_member(
                db=db,
                vendor=team_vendor,
                inviter=test_user,
                email=team_member.user.email,
                role_name="staff",
            )

    @patch("app.modules.billing.services.subscription_service.SubscriptionService.check_team_limit")
    def test_invite_with_custom_permissions(self, mock_check_limit, db, team_vendor, test_user):
        """Test inviting with custom permissions."""
        mock_check_limit.return_value = None
        unique_id = str(uuid.uuid4())[:8]
        email = f"custom_{unique_id}@example.com"
        custom_perms = ["orders.view", "orders.edit", "products.view"]

        result = vendor_team_service.invite_team_member(
            db=db,
            vendor=team_vendor,
            inviter=test_user,
            email=email,
            role_name="custom_role",
            custom_permissions=custom_perms,
        )
        db.commit()

        assert result is not None
        assert result["role"] == "custom_role"


# =============================================================================
# ACCEPT INVITATION TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestVendorTeamServiceAccept:
    """Test suite for accepting invitations."""

    def test_accept_invitation_success(self, db, pending_invitation):
        """Test accepting a valid invitation."""
        result = vendor_team_service.accept_invitation(
            db=db,
            invitation_token=pending_invitation.invitation_token,
            password="newpassword123",
            first_name="John",
            last_name="Doe",
        )
        db.commit()

        assert result is not None
        assert result["user"].is_active is True
        assert result["user"].first_name == "John"
        assert result["user"].last_name == "Doe"

    def test_accept_invitation_invalid_token(self, db):
        """Test accepting with invalid token raises exception."""
        with pytest.raises(InvalidInvitationTokenException):
            vendor_team_service.accept_invitation(
                db=db,
                invitation_token="invalid_token_12345",
                password="password123",
            )

    def test_accept_invitation_already_accepted(self, db, team_member):
        """Test accepting an already accepted invitation raises exception."""
        # team_member already has invitation_accepted_at set
        with pytest.raises(InvalidInvitationTokenException):
            vendor_team_service.accept_invitation(
                db=db,
                invitation_token="some_token",  # team_member has no token
                password="password123",
            )

    def test_accept_invitation_expired(self, db, expired_invitation):
        """Test accepting an expired invitation raises exception."""
        with pytest.raises(InvalidInvitationTokenException) as exc_info:
            vendor_team_service.accept_invitation(
                db=db,
                invitation_token=expired_invitation.invitation_token,
                password="password123",
            )

        assert "expired" in str(exc_info.value).lower()


# =============================================================================
# REMOVE TEAM MEMBER TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestVendorTeamServiceRemove:
    """Test suite for removing team members."""

    def test_remove_team_member_success(self, db, team_vendor, team_member):
        """Test removing a team member."""
        result = vendor_team_service.remove_team_member(
            db=db,
            vendor=team_vendor,
            user_id=team_member.user_id,
        )
        db.commit()
        db.refresh(team_member)

        assert result is True
        assert team_member.is_active is False

    def test_remove_owner_raises_error(self, db, team_vendor, vendor_owner):
        """Test removing owner raises exception."""
        with pytest.raises(CannotRemoveOwnerException):
            vendor_team_service.remove_team_member(
                db=db,
                vendor=team_vendor,
                user_id=vendor_owner.user_id,
            )

    def test_remove_nonexistent_user_raises_error(self, db, team_vendor):
        """Test removing non-existent user raises exception."""
        with pytest.raises(UserNotFoundException):
            vendor_team_service.remove_team_member(
                db=db,
                vendor=team_vendor,
                user_id=99999,
            )


# =============================================================================
# UPDATE MEMBER ROLE TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestVendorTeamServiceUpdateRole:
    """Test suite for updating member roles."""

    def test_update_role_success(self, db, team_vendor, team_member):
        """Test updating a member's role."""
        result = vendor_team_service.update_member_role(
            db=db,
            vendor=team_vendor,
            user_id=team_member.user_id,
            new_role_name="manager",
        )
        db.commit()

        assert result is not None
        assert result.role.name == "manager"

    def test_update_owner_role_raises_error(self, db, team_vendor, vendor_owner):
        """Test updating owner's role raises exception."""
        with pytest.raises(CannotRemoveOwnerException):
            vendor_team_service.update_member_role(
                db=db,
                vendor=team_vendor,
                user_id=vendor_owner.user_id,
                new_role_name="staff",
            )

    def test_update_role_with_custom_permissions(self, db, team_vendor, team_member):
        """Test updating role with custom permissions."""
        custom_perms = ["orders.view", "orders.edit", "reports.view"]

        result = vendor_team_service.update_member_role(
            db=db,
            vendor=team_vendor,
            user_id=team_member.user_id,
            new_role_name="analyst",
            custom_permissions=custom_perms,
        )
        db.commit()

        assert result.role.name == "analyst"
        assert result.role.permissions == custom_perms

    def test_update_nonexistent_user_raises_error(self, db, team_vendor):
        """Test updating non-existent user raises exception."""
        with pytest.raises(UserNotFoundException):
            vendor_team_service.update_member_role(
                db=db,
                vendor=team_vendor,
                user_id=99999,
                new_role_name="staff",
            )


# =============================================================================
# GET TEAM MEMBERS TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestVendorTeamServiceGetMembers:
    """Test suite for getting team members."""

    def test_get_team_members(self, db, team_vendor, vendor_owner, team_member):
        """Test getting all team members."""
        members = vendor_team_service.get_team_members(db, team_vendor)

        assert len(members) >= 2
        user_ids = [m["id"] for m in members]
        assert vendor_owner.user_id in user_ids
        assert team_member.user_id in user_ids

    def test_get_team_members_excludes_inactive(
        self, db, team_vendor, vendor_owner, team_member
    ):
        """Test getting only active team members."""
        # Deactivate team_member
        team_member.is_active = False
        db.commit()

        members = vendor_team_service.get_team_members(
            db, team_vendor, include_inactive=False
        )

        user_ids = [m["id"] for m in members]
        assert vendor_owner.user_id in user_ids
        assert team_member.user_id not in user_ids

    def test_get_team_members_includes_inactive(
        self, db, team_vendor, vendor_owner, team_member
    ):
        """Test getting all members including inactive."""
        # Deactivate team_member
        team_member.is_active = False
        db.commit()

        members = vendor_team_service.get_team_members(
            db, team_vendor, include_inactive=True
        )

        user_ids = [m["id"] for m in members]
        assert vendor_owner.user_id in user_ids
        assert team_member.user_id in user_ids


# =============================================================================
# GET VENDOR ROLES TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestVendorTeamServiceGetRoles:
    """Test suite for getting vendor roles."""

    def test_get_vendor_roles_existing(self, db, team_vendor, team_member):
        """Test getting roles when they exist."""
        # team_member fixture creates a role
        roles = vendor_team_service.get_vendor_roles(db, team_vendor.id)

        assert len(roles) >= 1
        role_names = [r["name"] for r in roles]
        assert "staff" in role_names

    def test_get_vendor_roles_creates_defaults(self, db, team_vendor):
        """Test default roles are created if none exist."""
        roles = vendor_team_service.get_vendor_roles(db, team_vendor.id)
        db.commit()

        assert len(roles) >= 5  # Default roles
        role_names = [r["name"] for r in roles]
        assert "manager" in role_names
        assert "staff" in role_names
        assert "support" in role_names
        assert "viewer" in role_names
        assert "marketing" in role_names

    def test_get_vendor_roles_returns_permissions(self, db, team_vendor):
        """Test roles include permissions."""
        roles = vendor_team_service.get_vendor_roles(db, team_vendor.id)

        for role in roles:
            assert "permissions" in role
            assert isinstance(role["permissions"], list)
