# tests/unit/services/test_admin_service.py
import pytest

from app.exceptions import ValidationException
from app.modules.tenancy.exceptions import (
    AdminOperationException,
    CannotModifySelfException,
    UserNotFoundException,
    UserStatusChangeException,
    VendorAlreadyExistsException,
    VendorNotFoundException,
)
from app.modules.tenancy.services.admin_service import AdminService
from app.modules.analytics.services.stats_service import stats_service
from app.modules.tenancy.schemas.vendor import VendorCreate


@pytest.mark.unit
@pytest.mark.admin
class TestAdminService:
    """Test suite for AdminService following the application's testing patterns"""

    def setup_method(self):
        """Setup method following the same pattern as product service tests"""
        self.service = AdminService()

    # User Management Tests
    def test_get_all_users(self, db, test_user, test_admin):
        """Test getting all users with pagination"""
        users = self.service.get_all_users(db, skip=0, limit=10)

        assert len(users) >= 2  # test_user + test_admin
        user_ids = [user.id for user in users]
        assert test_user.id in user_ids
        assert test_admin.id in user_ids

    def test_get_all_users_with_pagination(self, db, test_user, test_admin):
        """Test user pagination works correctly"""
        users = self.service.get_all_users(db, skip=0, limit=1)
        assert len(users) == 1

        users_second_page = self.service.get_all_users(db, skip=1, limit=1)
        assert len(users_second_page) == 1
        assert users[0].id != users_second_page[0].id

    def test_toggle_user_status_deactivate(self, db, test_user, test_admin):
        """Test deactivating a user"""
        assert test_user.is_active is True

        user, message = self.service.toggle_user_status(db, test_user.id, test_admin.id)

        assert user.id == test_user.id
        assert user.is_active is False
        assert test_user.username in message
        assert "deactivated" in message

    def test_toggle_user_status_activate(self, db, test_user, test_admin):
        """Test activating a user"""
        from app.modules.tenancy.models import User

        # Re-query user to get fresh instance
        user_to_deactivate = db.query(User).filter(User.id == test_user.id).first()
        user_to_deactivate.is_active = False
        db.commit()

        user, message = self.service.toggle_user_status(db, test_user.id, test_admin.id)

        assert user.id == test_user.id
        assert user.is_active is True
        assert "activated" in message

    def test_toggle_user_status_user_not_found(self, db, test_admin):
        """Test toggle user status when user not found"""
        with pytest.raises(UserNotFoundException) as exc_info:
            self.service.toggle_user_status(db, 99999, test_admin.id)

        exception = exc_info.value
        assert exception.error_code == "USER_NOT_FOUND"
        assert "99999" in exception.message

    def test_toggle_user_status_cannot_modify_self(self, db, test_admin):
        """Test that admin cannot modify their own account"""
        with pytest.raises(CannotModifySelfException) as exc_info:
            self.service.toggle_user_status(db, test_admin.id, test_admin.id)

        exception = exc_info.value
        assert exception.error_code == "CANNOT_MODIFY_SELF"
        assert "deactivate account" in exception.message

    def test_toggle_user_status_cannot_modify_admin(
        self, db, test_admin, another_admin
    ):
        """Test that admin cannot modify another admin"""
        with pytest.raises(UserStatusChangeException) as exc_info:
            self.service.toggle_user_status(db, another_admin.id, test_admin.id)

        exception = exc_info.value
        assert exception.error_code == "USER_STATUS_CHANGE_FAILED"
        assert "Cannot modify another admin user" in exception.message

    # Vendor Management Tests
    def test_get_all_vendors(self, db, test_vendor):
        """Test getting all vendors with total count"""
        vendors, total = self.service.get_all_vendors(db, skip=0, limit=10)

        assert total >= 1
        assert len(vendors) >= 1
        vendor_codes = [vendor.vendor_code for vendor in vendors]
        assert test_vendor.vendor_code in vendor_codes

    def test_get_all_vendors_with_pagination(self, db, test_vendor, verified_vendor):
        """Test vendor pagination works correctly"""
        vendors, total = self.service.get_all_vendors(db, skip=0, limit=1)

        assert total >= 2
        assert len(vendors) == 1

        vendors_second_page, _ = self.service.get_all_vendors(db, skip=1, limit=1)
        assert len(vendors_second_page) >= 0
        if len(vendors_second_page) > 0:
            assert vendors[0].id != vendors_second_page[0].id

    def test_verify_vendor_mark_verified(self, db, test_vendor):
        """Test marking vendor as verified"""
        from app.modules.tenancy.models import Vendor

        # Re-query vendor to get fresh instance
        vendor_to_unverify = (
            db.query(Vendor).filter(Vendor.id == test_vendor.id).first()
        )
        vendor_to_unverify.is_verified = False
        db.commit()

        vendor, message = self.service.verify_vendor(db, test_vendor.id)

        assert vendor.id == test_vendor.id
        assert vendor.is_verified is True
        assert "verified" in message

    def test_verify_vendor_mark_unverified(self, db, verified_vendor):
        """Test marking verified vendor as unverified"""
        vendor, message = self.service.verify_vendor(db, verified_vendor.id)

        assert vendor.id == verified_vendor.id
        assert vendor.is_verified is False
        assert verified_vendor.vendor_code in message
        assert "unverified" in message

    def test_verify_vendor_not_found(self, db):
        """Test verify vendor when vendor not found"""
        with pytest.raises(VendorNotFoundException) as exc_info:
            self.service.verify_vendor(db, 99999)

        exception = exc_info.value
        assert exception.error_code == "VENDOR_NOT_FOUND"
        assert "99999" in exception.message

    def test_toggle_vendor_status_deactivate(self, db, test_vendor):
        """Test deactivating a vendor"""
        original_status = test_vendor.is_active

        vendor, message = self.service.toggle_vendor_status(db, test_vendor.id)

        assert vendor.id == test_vendor.id
        assert vendor.is_active != original_status
        assert test_vendor.vendor_code in message
        if original_status:
            assert "deactivated" in message
        else:
            assert "activated" in message

    def test_toggle_vendor_status_not_found(self, db):
        """Test toggle vendor status when vendor not found"""
        with pytest.raises(VendorNotFoundException) as exc_info:
            self.service.toggle_vendor_status(db, 99999)

        exception = exc_info.value
        assert exception.error_code == "VENDOR_NOT_FOUND"

    # NOTE: Marketplace Import Jobs tests have been moved to the marketplace module.
    # See tests/unit/services/test_marketplace_import_job_service.py

    # Statistics Tests
    def test_get_user_statistics(self, db, test_user, test_admin):
        """Test getting user statistics"""
        stats = stats_service.get_user_statistics(db)

        assert "total_users" in stats
        assert "active_users" in stats
        assert "inactive_users" in stats
        assert "activation_rate" in stats

        assert isinstance(stats["total_users"], int)
        assert isinstance(stats["active_users"], int)
        assert isinstance(stats["inactive_users"], int)
        assert isinstance(stats["activation_rate"], (int, float))

        assert stats["total_users"] >= 2  # test_user + test_admin
        assert stats["active_users"] + stats["inactive_users"] == stats["total_users"]

    def test_get_vendor_statistics(self, db, test_vendor):
        """Test getting vendor statistics"""
        stats = stats_service.get_vendor_statistics(db)

        assert "total_vendors" in stats
        assert "active_vendors" in stats
        assert "verified_vendors" in stats
        assert "verification_rate" in stats

        assert isinstance(stats["total_vendors"], int)
        assert isinstance(stats["active_vendors"], int)
        assert isinstance(stats["verified_vendors"], int)
        assert isinstance(stats["verification_rate"], (int, float))

        assert stats["total_vendors"] >= 1

    # Error Handling Tests
    def test_get_all_users_database_error(self, db_with_error, test_admin):
        """Test handling database errors in get_all_users"""
        with pytest.raises(AdminOperationException) as exc_info:
            self.service.get_all_users(db_with_error, skip=0, limit=10)

        exception = exc_info.value
        assert exception.error_code == "ADMIN_OPERATION_FAILED"
        assert "get_all_users" in exception.message

    def test_get_all_vendors_database_error(self, db_with_error):
        """Test handling database errors in get_all_vendors"""
        with pytest.raises(AdminOperationException) as exc_info:
            self.service.get_all_vendors(db_with_error, skip=0, limit=10)

        exception = exc_info.value
        assert exception.error_code == "ADMIN_OPERATION_FAILED"
        assert "get_all_vendors" in exception.message

    # Edge Cases
    def test_get_all_users_empty_database(self, empty_db):
        """Test getting users when database is empty"""
        users = self.service.get_all_users(empty_db, skip=0, limit=10)
        assert len(users) == 0

    def test_get_all_vendors_empty_database(self, empty_db):
        """Test getting vendors when database is empty"""
        vendors, total = self.service.get_all_vendors(empty_db, skip=0, limit=10)
        assert len(vendors) == 0
        assert total == 0

    def test_user_statistics_empty_database(self, empty_db):
        """Test user statistics when no users exist"""
        stats = stats_service.get_user_statistics(empty_db)

        assert stats["total_users"] == 0
        assert stats["active_users"] == 0
        assert stats["inactive_users"] == 0
        assert stats["activation_rate"] == 0

    def test_vendor_statistics_empty_database(self, empty_db):
        """Test vendor statistics when no vendors exist"""
        stats = stats_service.get_vendor_statistics(empty_db)

        assert stats["total_vendors"] == 0
        assert stats["active_vendors"] == 0
        assert stats["verified_vendors"] == 0
        assert stats["verification_rate"] == 0


@pytest.mark.unit
@pytest.mark.admin
class TestAdminServiceVendorCreation:
    """Test suite for AdminService.create_vendor with platform assignments."""

    def setup_method(self):
        """Setup method following the same pattern as other tests."""
        self.service = AdminService()

    def test_create_vendor_without_platforms(self, db, test_company):
        """Test creating a vendor without platform assignments."""
        import uuid

        unique_id = str(uuid.uuid4())[:8]
        vendor_data = VendorCreate(
            company_id=test_company.id,
            vendor_code=f"NOPLATFORM_{unique_id}",
            subdomain=f"noplatform{unique_id}",
            name=f"No Platform Vendor {unique_id}",
        )

        vendor = self.service.create_vendor(db, vendor_data)
        db.commit()

        assert vendor is not None
        assert vendor.vendor_code == vendor_data.vendor_code.upper()
        assert vendor.company_id == test_company.id
        assert vendor.is_active is True

    def test_create_vendor_with_single_platform(
        self, db, test_company, test_platform
    ):
        """Test creating a vendor with one platform assignment."""
        import uuid

        unique_id = str(uuid.uuid4())[:8]
        vendor_data = VendorCreate(
            company_id=test_company.id,
            vendor_code=f"SINGLEPLAT_{unique_id}",
            subdomain=f"singleplat{unique_id}",
            name=f"Single Platform Vendor {unique_id}",
            platform_ids=[test_platform.id],
        )

        vendor = self.service.create_vendor(db, vendor_data)
        db.commit()
        db.refresh(vendor)

        assert vendor is not None
        assert vendor.vendor_code == vendor_data.vendor_code.upper()

        # Verify platform assignment
        from app.modules.tenancy.models import VendorPlatform

        assignment = (
            db.query(VendorPlatform)
            .filter(
                VendorPlatform.vendor_id == vendor.id,
                VendorPlatform.platform_id == test_platform.id,
            )
            .first()
        )
        assert assignment is not None
        assert assignment.is_active is True

    def test_create_vendor_with_multiple_platforms(
        self, db, test_company, test_platform, another_platform
    ):
        """Test creating a vendor with multiple platform assignments."""
        import uuid

        unique_id = str(uuid.uuid4())[:8]
        vendor_data = VendorCreate(
            company_id=test_company.id,
            vendor_code=f"MULTIPLAT_{unique_id}",
            subdomain=f"multiplat{unique_id}",
            name=f"Multi Platform Vendor {unique_id}",
            platform_ids=[test_platform.id, another_platform.id],
        )

        vendor = self.service.create_vendor(db, vendor_data)
        db.commit()
        db.refresh(vendor)

        assert vendor is not None

        # Verify both platform assignments
        from app.modules.tenancy.models import VendorPlatform

        assignments = (
            db.query(VendorPlatform)
            .filter(VendorPlatform.vendor_id == vendor.id)
            .all()
        )
        assert len(assignments) == 2

        platform_ids = [a.platform_id for a in assignments]
        assert test_platform.id in platform_ids
        assert another_platform.id in platform_ids

    def test_create_vendor_with_invalid_platform_id(self, db, test_company):
        """Test creating a vendor with non-existent platform ID (should ignore)."""
        import uuid

        unique_id = str(uuid.uuid4())[:8]
        vendor_data = VendorCreate(
            company_id=test_company.id,
            vendor_code=f"INVALIDPLAT_{unique_id}",
            subdomain=f"invalidplat{unique_id}",
            name=f"Invalid Platform Vendor {unique_id}",
            platform_ids=[99999],  # Non-existent platform
        )

        # Should succeed but not create assignment for invalid platform
        vendor = self.service.create_vendor(db, vendor_data)
        db.commit()
        db.refresh(vendor)

        assert vendor is not None

        # Verify no platform assignments created
        from app.modules.tenancy.models import VendorPlatform

        assignments = (
            db.query(VendorPlatform)
            .filter(VendorPlatform.vendor_id == vendor.id)
            .all()
        )
        assert len(assignments) == 0

    def test_create_vendor_duplicate_code_fails(self, db, test_company, test_vendor):
        """Test creating a vendor with duplicate vendor code fails."""
        vendor_data = VendorCreate(
            company_id=test_company.id,
            vendor_code=test_vendor.vendor_code,  # Duplicate
            subdomain="uniquesubdomain",
            name="Duplicate Code Vendor",
        )

        with pytest.raises(VendorAlreadyExistsException):
            self.service.create_vendor(db, vendor_data)

    def test_create_vendor_duplicate_subdomain_fails(self, db, test_company, test_vendor):
        """Test creating a vendor with duplicate subdomain fails."""
        import uuid

        unique_id = str(uuid.uuid4())[:8]
        vendor_data = VendorCreate(
            company_id=test_company.id,
            vendor_code=f"UNIQUECODE_{unique_id}",
            subdomain=test_vendor.subdomain,  # Duplicate
            name="Duplicate Subdomain Vendor",
        )

        with pytest.raises(ValidationException) as exc_info:
            self.service.create_vendor(db, vendor_data)

        assert "already taken" in str(exc_info.value)

    def test_create_vendor_invalid_company_fails(self, db):
        """Test creating a vendor with non-existent company fails."""
        import uuid

        unique_id = str(uuid.uuid4())[:8]
        vendor_data = VendorCreate(
            company_id=99999,  # Non-existent
            vendor_code=f"NOCOMPANY_{unique_id}",
            subdomain=f"nocompany{unique_id}",
            name="No Company Vendor",
        )

        with pytest.raises(ValidationException) as exc_info:
            self.service.create_vendor(db, vendor_data)

        assert "not found" in str(exc_info.value)
