# tests/unit/services/test_feature_service.py
"""Unit tests for FeatureService."""

import pytest

from app.modules.billing.exceptions import FeatureNotFoundError, InvalidFeatureCodesError, TierNotFoundError
from app.modules.billing.services.feature_service import FeatureService, feature_service
from app.modules.billing.models import Feature
from app.modules.billing.models import SubscriptionTier, VendorSubscription


@pytest.mark.unit
@pytest.mark.features
class TestFeatureServiceAvailability:
    """Test suite for feature availability checking."""

    def setup_method(self):
        """Initialize service instance before each test."""
        self.service = FeatureService()

    def test_has_feature_true(self, db, test_vendor_with_subscription):
        """Test has_feature returns True for available feature."""
        vendor_id = test_vendor_with_subscription.id
        result = self.service.has_feature(db, vendor_id, "basic_reports")
        assert result is True

    def test_has_feature_false(self, db, test_vendor_with_subscription):
        """Test has_feature returns False for unavailable feature."""
        vendor_id = test_vendor_with_subscription.id
        result = self.service.has_feature(db, vendor_id, "api_access")
        assert result is False

    def test_has_feature_no_subscription(self, db, test_vendor):
        """Test has_feature returns False for vendor without subscription."""
        result = self.service.has_feature(db, test_vendor.id, "basic_reports")
        assert result is False

    def test_get_vendor_feature_codes(self, db, test_vendor_with_subscription):
        """Test getting all feature codes for vendor."""
        vendor_id = test_vendor_with_subscription.id
        features = self.service.get_vendor_feature_codes(db, vendor_id)

        assert isinstance(features, set)
        assert "basic_reports" in features
        assert "api_access" not in features


@pytest.mark.unit
@pytest.mark.features
class TestFeatureServiceListing:
    """Test suite for feature listing operations."""

    def setup_method(self):
        """Initialize service instance before each test."""
        self.service = FeatureService()

    def test_get_vendor_features(self, db, test_vendor_with_subscription, test_features):
        """Test getting all features with availability."""
        vendor_id = test_vendor_with_subscription.id
        features = self.service.get_vendor_features(db, vendor_id)

        assert len(features) > 0
        basic_reports = next((f for f in features if f.code == "basic_reports"), None)
        assert basic_reports is not None
        assert basic_reports.is_available is True

        api_access = next((f for f in features if f.code == "api_access"), None)
        assert api_access is not None
        assert api_access.is_available is False

    def test_get_vendor_features_by_category(
        self, db, test_vendor_with_subscription, test_features
    ):
        """Test filtering features by category."""
        vendor_id = test_vendor_with_subscription.id
        features = self.service.get_vendor_features(db, vendor_id, category="analytics")

        assert all(f.category == "analytics" for f in features)

    def test_get_vendor_features_available_only(
        self, db, test_vendor_with_subscription, test_features
    ):
        """Test getting only available features."""
        vendor_id = test_vendor_with_subscription.id
        features = self.service.get_vendor_features(
            db, vendor_id, include_unavailable=False
        )

        assert all(f.is_available for f in features)

    def test_get_available_feature_codes(self, db, test_vendor_with_subscription):
        """Test getting simple list of available codes."""
        vendor_id = test_vendor_with_subscription.id
        codes = self.service.get_available_feature_codes(db, vendor_id)

        assert isinstance(codes, list)
        assert "basic_reports" in codes


@pytest.mark.unit
@pytest.mark.features
class TestFeatureServiceMetadata:
    """Test suite for feature metadata operations."""

    def setup_method(self):
        """Initialize service instance before each test."""
        self.service = FeatureService()

    def test_get_feature_by_code(self, db, test_features):
        """Test getting feature by code."""
        feature = self.service.get_feature_by_code(db, "basic_reports")

        assert feature is not None
        assert feature.code == "basic_reports"
        assert feature.name == "Basic Reports"

    def test_get_feature_by_code_not_found(self, db, test_features):
        """Test getting non-existent feature returns None."""
        feature = self.service.get_feature_by_code(db, "nonexistent")
        assert feature is None

    def test_get_feature_upgrade_info(self, db, test_features, test_subscription_tiers):
        """Test getting upgrade info for locked feature."""
        info = self.service.get_feature_upgrade_info(db, "api_access")

        assert info is not None
        assert info.feature_code == "api_access"
        assert info.required_tier_code == "professional"

    def test_get_feature_upgrade_info_no_minimum_tier(self, db, test_features):
        """Test upgrade info for feature without minimum tier."""
        # basic_reports has no minimum tier in fixtures
        info = self.service.get_feature_upgrade_info(db, "basic_reports")
        assert info is None

    def test_get_all_features(self, db, test_features):
        """Test getting all features for admin."""
        features = self.service.get_all_features(db)
        assert len(features) >= 3

    def test_get_all_features_by_category(self, db, test_features):
        """Test filtering features by category."""
        features = self.service.get_all_features(db, category="analytics")
        assert all(f.category == "analytics" for f in features)

    def test_get_categories(self, db, test_features):
        """Test getting unique categories."""
        categories = self.service.get_categories(db)
        assert "analytics" in categories
        assert "integrations" in categories


@pytest.mark.unit
@pytest.mark.features
class TestFeatureServiceCache:
    """Test suite for cache operations."""

    def setup_method(self):
        """Initialize service instance before each test."""
        self.service = FeatureService()

    def test_cache_invalidation(self, db, test_vendor_with_subscription):
        """Test cache invalidation for vendor."""
        vendor_id = test_vendor_with_subscription.id

        # Prime the cache
        self.service.get_vendor_feature_codes(db, vendor_id)
        assert self.service._cache.get(vendor_id) is not None

        # Invalidate
        self.service.invalidate_vendor_cache(vendor_id)
        assert self.service._cache.get(vendor_id) is None

    def test_cache_invalidate_all(self, db, test_vendor_with_subscription):
        """Test invalidating entire cache."""
        vendor_id = test_vendor_with_subscription.id

        # Prime the cache
        self.service.get_vendor_feature_codes(db, vendor_id)

        # Invalidate all
        self.service.invalidate_all_cache()
        assert self.service._cache.get(vendor_id) is None


@pytest.mark.unit
@pytest.mark.features
class TestFeatureServiceAdmin:
    """Test suite for admin operations."""

    def setup_method(self):
        """Initialize service instance before each test."""
        self.service = FeatureService()

    def test_get_all_tiers_with_features(self, db, test_subscription_tiers):
        """Test getting all tiers."""
        tiers = self.service.get_all_tiers_with_features(db)

        assert len(tiers) == 2
        assert tiers[0].code == "essential"
        assert tiers[1].code == "professional"

    def test_update_tier_features(self, db, test_subscription_tiers, test_features):
        """Test updating tier features."""
        tier = self.service.update_tier_features(
            db, "essential", ["basic_reports", "api_access"]
        )
        db.commit()

        assert "api_access" in tier.features

    def test_update_tier_features_invalid_codes(
        self, db, test_subscription_tiers, test_features
    ):
        """Test updating tier with invalid feature codes."""
        with pytest.raises(InvalidFeatureCodesError) as exc_info:
            self.service.update_tier_features(
                db, "essential", ["basic_reports", "nonexistent_feature"]
            )

        assert "nonexistent_feature" in exc_info.value.invalid_codes

    def test_update_tier_features_tier_not_found(self, db, test_features):
        """Test updating non-existent tier."""
        with pytest.raises(TierNotFoundError) as exc_info:
            self.service.update_tier_features(db, "nonexistent", ["basic_reports"])

        assert exc_info.value.tier_code == "nonexistent"

    def test_update_feature(self, db, test_features):
        """Test updating feature metadata."""
        feature = self.service.update_feature(
            db, "basic_reports", name="Updated Reports", description="New description"
        )
        db.commit()

        assert feature.name == "Updated Reports"
        assert feature.description == "New description"

    def test_update_feature_not_found(self, db, test_features):
        """Test updating non-existent feature."""
        with pytest.raises(FeatureNotFoundError) as exc_info:
            self.service.update_feature(db, "nonexistent", name="Test")

        assert exc_info.value.feature_code == "nonexistent"

    def test_update_feature_minimum_tier(
        self, db, test_features, test_subscription_tiers
    ):
        """Test updating feature minimum tier."""
        feature = self.service.update_feature(
            db, "basic_reports", minimum_tier_code="professional"
        )
        db.commit()
        db.refresh(feature)

        assert feature.minimum_tier is not None
        assert feature.minimum_tier.code == "professional"


# ==================== Fixtures ====================


@pytest.fixture
def test_subscription_tiers(db):
    """Create multiple subscription tiers."""
    tiers = [
        SubscriptionTier(
            code="essential",
            name="Essential",
            description="Essential plan",
            price_monthly_cents=4900,
            price_annual_cents=49000,
            orders_per_month=100,
            products_limit=500,
            team_members=2,
            features=["basic_reports"],
            is_active=True,
            display_order=1,
        ),
        SubscriptionTier(
            code="professional",
            name="Professional",
            description="Professional plan",
            price_monthly_cents=9900,
            price_annual_cents=99000,
            orders_per_month=500,
            products_limit=2000,
            team_members=5,
            features=["basic_reports", "api_access", "analytics_dashboard"],
            is_active=True,
            display_order=2,
        ),
    ]
    for tier in tiers:
        db.add(tier)
    db.commit()
    for tier in tiers:
        db.refresh(tier)
    return tiers


@pytest.fixture
def test_vendor_with_subscription(db, test_vendor, test_subscription_tiers):
    """Create a vendor with an active subscription."""
    from datetime import datetime, timezone

    essential_tier = test_subscription_tiers[0]  # Use the essential tier from tiers list
    now = datetime.now(timezone.utc)
    subscription = VendorSubscription(
        vendor_id=test_vendor.id,
        tier="essential",
        tier_id=essential_tier.id,
        status="active",
        period_start=now,
        period_end=now.replace(month=now.month + 1 if now.month < 12 else 1),
        orders_this_period=10,
    )
    db.add(subscription)
    db.commit()
    db.refresh(test_vendor)
    return test_vendor


@pytest.fixture
def test_features(db, test_subscription_tiers):
    """Create test features."""
    features = [
        Feature(
            code="basic_reports",
            name="Basic Reports",
            description="View basic analytics reports",
            category="analytics",
            ui_location="sidebar",
            ui_icon="chart-bar",
            is_active=True,
            display_order=1,
        ),
        Feature(
            code="api_access",
            name="API Access",
            description="Access the REST API",
            category="integrations",
            ui_location="settings",
            ui_icon="code",
            minimum_tier_id=test_subscription_tiers[1].id,  # Professional
            is_active=True,
            display_order=2,
        ),
        Feature(
            code="analytics_dashboard",
            name="Analytics Dashboard",
            description="Advanced analytics dashboard",
            category="analytics",
            ui_location="sidebar",
            ui_icon="presentation-chart-line",
            minimum_tier_id=test_subscription_tiers[1].id,
            is_active=True,
            display_order=3,
        ),
    ]
    for feature in features:
        db.add(feature)
    db.commit()
    for feature in features:
        db.refresh(feature)
    return features
