# tests/unit/services/test_admin_platform_service.py
"""
Unit tests for AdminPlatformService.

Tests the admin platform assignment service operations.
"""

import pytest

from app.modules.tenancy.exceptions import (
    AdminOperationException,
    CannotModifySelfException,
    PlatformNotFoundException,
    UserAlreadyExistsException,
    UserNotFoundException,
)
from app.modules.tenancy.services.admin_platform_service import AdminPlatformService


@pytest.mark.unit
@pytest.mark.admin
class TestAdminPlatformServiceAssign:
    """Test AdminPlatformService.assign_admin_to_platform."""

    def test_assign_admin_to_platform_success(
        self, db, test_platform_admin, test_platform, test_super_admin
    ):
        """Test successfully assigning an admin to a platform."""
        service = AdminPlatformService()

        assignment = service.assign_admin_to_platform(
            db=db,
            admin_user_id=test_platform_admin.id,
            platform_id=test_platform.id,
            assigned_by_user_id=test_super_admin.id,
        )

        assert assignment is not None
        assert assignment.user_id == test_platform_admin.id
        assert assignment.platform_id == test_platform.id
        assert assignment.is_active is True
        assert assignment.assigned_by_user_id == test_super_admin.id

    def test_assign_admin_user_not_found(self, db, test_platform, test_super_admin):
        """Test assigning non-existent user raises error."""
        service = AdminPlatformService()

        with pytest.raises(UserNotFoundException) as exc:
            service.assign_admin_to_platform(
                db=db,
                admin_user_id=99999,
                platform_id=test_platform.id,
                assigned_by_user_id=test_super_admin.id,
            )
        assert "not found" in str(exc.value)

    def test_assign_admin_not_admin_role(
        self, db, test_store_user, test_platform, test_super_admin
    ):
        """Test assigning non-admin user raises error."""
        service = AdminPlatformService()

        with pytest.raises(AdminOperationException) as exc:
            service.assign_admin_to_platform(
                db=db,
                admin_user_id=test_store_user.id,
                platform_id=test_platform.id,
                assigned_by_user_id=test_super_admin.id,
            )
        assert "must be an admin" in str(exc.value)

    def test_assign_super_admin_raises_error(
        self, db, test_super_admin, test_platform
    ):
        """Test assigning super admin raises error."""
        service = AdminPlatformService()

        with pytest.raises(AdminOperationException) as exc:
            service.assign_admin_to_platform(
                db=db,
                admin_user_id=test_super_admin.id,
                platform_id=test_platform.id,
                assigned_by_user_id=test_super_admin.id,
            )
        assert "Super admins don't need platform assignments" in str(exc.value)

    def test_assign_platform_not_found(
        self, db, test_platform_admin, test_super_admin
    ):
        """Test assigning to non-existent platform raises error."""
        service = AdminPlatformService()

        with pytest.raises(PlatformNotFoundException) as exc:
            service.assign_admin_to_platform(
                db=db,
                admin_user_id=test_platform_admin.id,
                platform_id=99999,
                assigned_by_user_id=test_super_admin.id,
            )
        assert "not found" in str(exc.value)

    def test_assign_admin_already_assigned(
        self, db, test_platform_admin, test_platform, test_super_admin
    ):
        """Test assigning already assigned admin raises error."""
        service = AdminPlatformService()

        # First assignment
        service.assign_admin_to_platform(
            db=db,
            admin_user_id=test_platform_admin.id,
            platform_id=test_platform.id,
            assigned_by_user_id=test_super_admin.id,
        )
        db.commit()

        # Try to assign again
        with pytest.raises(AdminOperationException) as exc:
            service.assign_admin_to_platform(
                db=db,
                admin_user_id=test_platform_admin.id,
                platform_id=test_platform.id,
                assigned_by_user_id=test_super_admin.id,
            )
        assert "already assigned" in str(exc.value)

    def test_reactivate_inactive_assignment(
        self, db, test_platform_admin, test_platform, test_super_admin
    ):
        """Test reactivating an inactive assignment."""
        from app.modules.tenancy.models import AdminPlatform

        service = AdminPlatformService()

        # Create inactive assignment directly
        assignment = AdminPlatform(
            user_id=test_platform_admin.id,
            platform_id=test_platform.id,
            is_active=False,
            assigned_by_user_id=test_super_admin.id,
        )
        db.add(assignment)
        db.commit()

        # Assign again - should reactivate
        result = service.assign_admin_to_platform(
            db=db,
            admin_user_id=test_platform_admin.id,
            platform_id=test_platform.id,
            assigned_by_user_id=test_super_admin.id,
        )

        assert result.is_active is True


@pytest.mark.unit
@pytest.mark.admin
class TestAdminPlatformServiceRemove:
    """Test AdminPlatformService.remove_admin_from_platform."""

    def test_remove_admin_from_platform_success(
        self, db, test_platform_admin, test_platform, test_super_admin
    ):
        """Test successfully removing an admin from a platform."""
        from app.modules.tenancy.models import AdminPlatform

        service = AdminPlatformService()

        # Create assignment first
        assignment = AdminPlatform(
            user_id=test_platform_admin.id,
            platform_id=test_platform.id,
            is_active=True,
            assigned_by_user_id=test_super_admin.id,
        )
        db.add(assignment)
        db.commit()

        # Remove
        service.remove_admin_from_platform(
            db=db,
            admin_user_id=test_platform_admin.id,
            platform_id=test_platform.id,
            removed_by_user_id=test_super_admin.id,
        )
        db.commit()
        db.refresh(assignment)

        assert assignment.is_active is False

    def test_remove_admin_not_assigned(
        self, db, test_platform_admin, test_platform, test_super_admin
    ):
        """Test removing non-existent assignment raises error."""
        service = AdminPlatformService()

        with pytest.raises(AdminOperationException) as exc:
            service.remove_admin_from_platform(
                db=db,
                admin_user_id=test_platform_admin.id,
                platform_id=test_platform.id,
                removed_by_user_id=test_super_admin.id,
            )
        assert "not assigned" in str(exc.value)


@pytest.mark.unit
@pytest.mark.admin
class TestAdminPlatformServiceQueries:
    """Test AdminPlatformService query methods."""

    def test_get_platforms_for_admin(
        self, db, test_platform_admin, test_platform, another_platform, test_super_admin
    ):
        """Test getting platforms for an admin."""
        from app.modules.tenancy.models import AdminPlatform

        service = AdminPlatformService()

        # Create assignments
        for platform in [test_platform, another_platform]:
            assignment = AdminPlatform(
                user_id=test_platform_admin.id,
                platform_id=platform.id,
                is_active=True,
                assigned_by_user_id=test_super_admin.id,
            )
            db.add(assignment)  # noqa: PERF006
        db.commit()

        platforms = service.get_platforms_for_admin(db, test_platform_admin.id)

        assert len(platforms) == 2
        platform_ids = [p.id for p in platforms]
        assert test_platform.id in platform_ids
        assert another_platform.id in platform_ids

    def test_get_platforms_for_admin_no_assignments(self, db, test_platform_admin):
        """Test getting platforms when no assignments exist."""
        service = AdminPlatformService()

        platforms = service.get_platforms_for_admin(db, test_platform_admin.id)

        assert platforms == []

    def test_get_admins_for_platform(
        self, db, test_platform_admin, test_platform, test_super_admin, auth_manager
    ):
        """Test getting admins for a platform."""
        from app.modules.tenancy.models import AdminPlatform, User

        service = AdminPlatformService()

        # Create another platform admin
        another_admin = User(
            email="another_padmin@example.com",
            username="another_padmin",
            hashed_password=auth_manager.hash_password("pass"),  # noqa: SEC001
            role="platform_admin",
            is_active=True,
        )
        db.add(another_admin)
        db.flush()

        # Create assignments for both admins
        for admin in [test_platform_admin, another_admin]:
            assignment = AdminPlatform(
                user_id=admin.id,
                platform_id=test_platform.id,
                is_active=True,
                assigned_by_user_id=test_super_admin.id,
            )
            db.add(assignment)  # noqa: PERF006
        db.commit()

        admins = service.get_admins_for_platform(db, test_platform.id)

        assert len(admins) == 2
        admin_ids = [a.id for a in admins]
        assert test_platform_admin.id in admin_ids
        assert another_admin.id in admin_ids

    def test_get_admin_assignments(
        self, db, test_platform_admin, test_platform, another_platform, test_super_admin
    ):
        """Test getting admin assignments with platform details."""
        from app.modules.tenancy.models import AdminPlatform

        service = AdminPlatformService()

        # Create assignments
        for platform in [test_platform, another_platform]:
            assignment = AdminPlatform(
                user_id=test_platform_admin.id,
                platform_id=platform.id,
                is_active=True,
                assigned_by_user_id=test_super_admin.id,
            )
            db.add(assignment)  # noqa: PERF006
        db.commit()

        assignments = service.get_admin_assignments(db, test_platform_admin.id)

        assert len(assignments) == 2
        # Verify platform relationship is loaded
        for assignment in assignments:
            assert assignment.platform is not None
            assert assignment.platform.code is not None


@pytest.mark.unit
@pytest.mark.admin
class TestAdminPlatformServiceSuperAdmin:
    """Test AdminPlatformService super admin operations."""

    def test_toggle_super_admin_promote(
        self, db, test_platform_admin, test_super_admin
    ):
        """Test promoting admin to super admin."""
        service = AdminPlatformService()

        result = service.toggle_super_admin(
            db=db,
            user_id=test_platform_admin.id,
            is_super_admin=True,
            current_admin_id=test_super_admin.id,
        )
        db.commit()

        assert result.is_super_admin is True

    def test_toggle_super_admin_demote(
        self, db, test_super_admin, auth_manager
    ):
        """Test demoting super admin to platform admin."""
        from app.modules.tenancy.models import User

        service = AdminPlatformService()

        # Create another super admin to demote
        another_super = User(
            email="another_super@example.com",
            username="another_super",
            hashed_password=auth_manager.hash_password("pass"),  # noqa: SEC001
            role="super_admin",
            is_active=True,
        )
        db.add(another_super)
        db.commit()

        result = service.toggle_super_admin(
            db=db,
            user_id=another_super.id,
            is_super_admin=False,
            current_admin_id=test_super_admin.id,
        )
        db.commit()

        assert result.is_super_admin is False

    def test_toggle_super_admin_cannot_demote_self(self, db, test_super_admin):
        """Test that super admin cannot demote themselves."""
        service = AdminPlatformService()

        with pytest.raises(CannotModifySelfException):
            service.toggle_super_admin(
                db=db,
                user_id=test_super_admin.id,
                is_super_admin=False,
                current_admin_id=test_super_admin.id,
            )

    def test_toggle_super_admin_user_not_found(self, db, test_super_admin):
        """Test toggling non-existent user raises error."""
        service = AdminPlatformService()

        with pytest.raises(UserNotFoundException) as exc:
            service.toggle_super_admin(
                db=db,
                user_id=99999,
                is_super_admin=True,
                current_admin_id=test_super_admin.id,
            )
        assert "not found" in str(exc.value)

    def test_toggle_super_admin_not_admin(
        self, db, test_store_user, test_super_admin
    ):
        """Test toggling non-admin user raises error."""
        service = AdminPlatformService()

        with pytest.raises(AdminOperationException) as exc:
            service.toggle_super_admin(
                db=db,
                user_id=test_store_user.id,
                is_super_admin=True,
                current_admin_id=test_super_admin.id,
            )
        assert "must be an admin" in str(exc.value)


@pytest.mark.unit
@pytest.mark.admin
class TestAdminPlatformServiceCreatePlatformAdmin:
    """Test AdminPlatformService.create_platform_admin."""

    def test_create_platform_admin_success(
        self, db, test_platform, another_platform, test_super_admin
    ):
        """Test creating a new platform admin with assignments."""
        service = AdminPlatformService()

        user, assignments = service.create_platform_admin(
            db=db,
            email="new_padmin@example.com",
            username="new_padmin",
            password="securepass123",  # noqa: SEC001
            platform_ids=[test_platform.id, another_platform.id],
            created_by_user_id=test_super_admin.id,
            first_name="New",
            last_name="Admin",
        )
        db.commit()

        assert user is not None
        assert user.email == "new_padmin@example.com"
        assert user.username == "new_padmin"
        assert user.role == "platform_admin"
        assert user.is_super_admin is False  # platform_admin, not super_admin
        assert user.first_name == "New"
        assert user.last_name == "Admin"
        assert len(assignments) == 2

    def test_create_platform_admin_duplicate_email(
        self, db, test_platform, test_super_admin, test_platform_admin
    ):
        """Test creating platform admin with duplicate email fails."""
        service = AdminPlatformService()

        with pytest.raises(UserAlreadyExistsException) as exc:
            service.create_platform_admin(
                db=db,
                email=test_platform_admin.email,  # Duplicate
                username="unique_username",
                password="securepass123",  # noqa: SEC001
                platform_ids=[test_platform.id],
                created_by_user_id=test_super_admin.id,
            )
        assert "Email already exists" in str(exc.value)

    def test_create_platform_admin_duplicate_username(
        self, db, test_platform, test_super_admin, test_platform_admin
    ):
        """Test creating platform admin with duplicate username fails."""
        service = AdminPlatformService()

        with pytest.raises(UserAlreadyExistsException) as exc:
            service.create_platform_admin(
                db=db,
                email="unique@example.com",
                username=test_platform_admin.username,  # Duplicate
                password="securepass123",  # noqa: SEC001
                platform_ids=[test_platform.id],
                created_by_user_id=test_super_admin.id,
            )
        assert "Username already exists" in str(exc.value)
