"""Unit tests for FeatureService."""

import pytest

from app.modules.billing.models import SubscriptionTier
from app.modules.billing.models.tier_feature_limit import TierFeatureLimit
from app.modules.billing.services.feature_service import FeatureService
from app.modules.tenancy.models import Platform


@pytest.mark.unit
@pytest.mark.billing
class TestFeatureService:
    """Test suite for FeatureService."""

    def setup_method(self):
        self.service = FeatureService()

    def test_service_instantiation(self):
        """Service can be instantiated."""
        assert self.service is not None


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


@pytest.fixture
def fs_platform(db):
    """Create a platform for feature service tests."""
    platform = Platform(code="fs_test", name="FS Test Platform", is_active=True)
    db.add(platform)
    db.commit()
    db.refresh(platform)
    return platform


@pytest.fixture
def fs_second_platform(db):
    """Create a second platform to test cross-platform isolation."""
    platform = Platform(code="fs_test2", name="FS Test Platform 2", is_active=True)
    db.add(platform)
    db.commit()
    db.refresh(platform)
    return platform


@pytest.fixture
def fs_tier(db, fs_platform):
    """Create a tier for feature service tests."""
    tier = SubscriptionTier(
        code="essential",
        name="Essential",
        price_monthly_cents=1000,
        display_order=0,
        is_active=True,
        is_public=True,
        platform_id=fs_platform.id,
    )
    db.add(tier)
    db.commit()
    db.refresh(tier)
    return tier


@pytest.fixture
def fs_same_code_tier(db, fs_second_platform):
    """Create a tier with the SAME code but different platform."""
    tier = SubscriptionTier(
        code="essential",
        name="Essential",
        price_monthly_cents=2000,
        display_order=0,
        is_active=True,
        is_public=True,
        platform_id=fs_second_platform.id,
    )
    db.add(tier)
    db.commit()
    db.refresh(tier)
    return tier


@pytest.fixture
def fs_tier_with_features(db, fs_tier):
    """Create a tier with pre-existing feature limits."""
    features = [
        TierFeatureLimit(tier_id=fs_tier.id, feature_code="feature_a", limit_value=None),
        TierFeatureLimit(tier_id=fs_tier.id, feature_code="feature_b", limit_value=100),
        TierFeatureLimit(tier_id=fs_tier.id, feature_code="feature_c", limit_value=50),
    ]
    db.add_all(features)
    db.commit()
    return features


# ============================================================================
# get_tier_feature_limits
# ============================================================================


@pytest.mark.unit
@pytest.mark.billing
class TestGetTierFeatureLimits:
    """Tests for FeatureService.get_tier_feature_limits."""

    def test_returns_limits_for_tier(self, db, fs_tier_with_features, fs_tier):
        """Returns all feature limit rows for the given tier ID."""
        service = FeatureService()
        rows = service.get_tier_feature_limits(db, fs_tier.id)
        assert len(rows) == 3
        codes = {r.feature_code for r in rows}
        assert codes == {"feature_a", "feature_b", "feature_c"}

    def test_returns_empty_for_tier_without_features(self, db, fs_tier):
        """Returns empty list for a tier with no feature limits."""
        service = FeatureService()
        rows = service.get_tier_feature_limits(db, fs_tier.id)
        assert rows == []

    def test_returns_empty_for_nonexistent_tier(self, db):
        """Returns empty list for a tier ID that doesn't exist."""
        service = FeatureService()
        rows = service.get_tier_feature_limits(db, 999999)
        assert rows == []

    def test_isolates_by_tier_id(self, db, fs_tier, fs_same_code_tier, fs_tier_with_features):
        """Features for one tier don't leak to another with the same code."""
        service = FeatureService()

        # fs_tier has 3 features
        rows_tier1 = service.get_tier_feature_limits(db, fs_tier.id)
        assert len(rows_tier1) == 3

        # fs_same_code_tier (same code, different platform) has 0
        rows_tier2 = service.get_tier_feature_limits(db, fs_same_code_tier.id)
        assert len(rows_tier2) == 0


# ============================================================================
# upsert_tier_feature_limits
# ============================================================================


@pytest.mark.unit
@pytest.mark.billing
class TestUpsertTierFeatureLimits:
    """Tests for FeatureService.upsert_tier_feature_limits."""

    def test_inserts_new_features(self, db, fs_tier):
        """Creates feature limit rows for a tier."""
        service = FeatureService()
        entries = [
            {"feature_code": "feat_x", "limit_value": None, "enabled": True},
            {"feature_code": "feat_y", "limit_value": 200, "enabled": True},
        ]
        rows = service.upsert_tier_feature_limits(db, fs_tier.id, entries)
        db.commit()

        assert len(rows) == 2
        assert {r.feature_code for r in rows} == {"feat_x", "feat_y"}

    def test_replaces_existing_features(self, db, fs_tier, fs_tier_with_features):
        """Upsert deletes old features and inserts new ones."""
        service = FeatureService()
        entries = [
            {"feature_code": "new_feature", "limit_value": None, "enabled": True},
        ]
        rows = service.upsert_tier_feature_limits(db, fs_tier.id, entries)
        db.commit()

        assert len(rows) == 1
        assert rows[0].feature_code == "new_feature"

        # Old features should be gone
        remaining = service.get_tier_feature_limits(db, fs_tier.id)
        assert len(remaining) == 1
        assert remaining[0].feature_code == "new_feature"

    def test_skips_disabled_entries(self, db, fs_tier):
        """Entries with enabled=False are not persisted."""
        service = FeatureService()
        entries = [
            {"feature_code": "enabled_feat", "limit_value": None, "enabled": True},
            {"feature_code": "disabled_feat", "limit_value": None, "enabled": False},
        ]
        rows = service.upsert_tier_feature_limits(db, fs_tier.id, entries)
        db.commit()

        assert len(rows) == 1
        assert rows[0].feature_code == "enabled_feat"

    def test_saves_to_correct_tier_by_id(self, db, fs_tier, fs_same_code_tier):
        """
        Regression test: saving by tier_id targets the exact tier,
        not another tier that happens to share the same code.
        """
        service = FeatureService()
        entries = [
            {"feature_code": "platform_specific", "limit_value": None, "enabled": True},
        ]

        # Save to the second tier (same code "essential", different platform)
        service.upsert_tier_feature_limits(db, fs_same_code_tier.id, entries)
        db.commit()

        # First tier should have 0 features
        rows_tier1 = service.get_tier_feature_limits(db, fs_tier.id)
        assert len(rows_tier1) == 0

        # Second tier should have 1 feature
        rows_tier2 = service.get_tier_feature_limits(db, fs_same_code_tier.id)
        assert len(rows_tier2) == 1
        assert rows_tier2[0].feature_code == "platform_specific"

    def test_clears_all_features_with_empty_list(self, db, fs_tier, fs_tier_with_features):
        """Passing an empty list removes all features."""
        service = FeatureService()
        rows = service.upsert_tier_feature_limits(db, fs_tier.id, [])
        db.commit()

        assert len(rows) == 0
        remaining = service.get_tier_feature_limits(db, fs_tier.id)
        assert len(remaining) == 0

    def test_preserves_limit_values(self, db, fs_tier):
        """Limit values (including None for unlimited) are stored correctly."""
        service = FeatureService()
        entries = [
            {"feature_code": "unlimited", "limit_value": None, "enabled": True},
            {"feature_code": "limited", "limit_value": 42, "enabled": True},
        ]
        service.upsert_tier_feature_limits(db, fs_tier.id, entries)
        db.commit()

        rows = service.get_tier_feature_limits(db, fs_tier.id)
        limits = {r.feature_code: r.limit_value for r in rows}
        assert limits["unlimited"] is None
        assert limits["limited"] == 42
