"""Unit tests for the INDL-59 Plan Feature Matrix (src/core/constants.py,
src/core/features.py).

Covers:
  - has_feature() returns the correct boolean for every matrix key, across
    all three plans (parametrized against PLAN_FEATURES itself — the matrix
    is the single source of truth, so these tests fail if a future edit to
    the matrix accidentally drifts from the documented PRD table).
  - has_feature() accepts either an Account-like object or a raw plan string.
  - Professional and Enterprise are identical across every gated feature —
    aiRecordExtraction was Enterprise-only in an earlier iteration but the
    stakeholder corrected that on manual review; the only remaining
    Professional/Enterprise difference is the numeric records limit.
  - feature_limit() returns the documented records limits (5000 / 25000 /
    None) and is None for an unrecognized plan.
  - feature_gate_message() matches the "<Feature> is available on the
    <Plan(s)> plan(s)." style of the pre-existing ai-search 403 message.
"""
from types import SimpleNamespace

import pytest

from src.core.constants import PLAN_FEATURES, PLAN_LIMITS, SubscriptionPlan, TRIAL_RECORD_LIMIT
from src.core.features import (
    effective_records_limit,
    feature_gate_message,
    feature_limit,
    has_feature,
    records_limit_reached_message,
)

_ALL_KEYS = sorted({key for plan_map in PLAN_FEATURES.values() for key in plan_map})


@pytest.mark.parametrize("plan", list(SubscriptionPlan))
@pytest.mark.parametrize("key", _ALL_KEYS)
def test_has_feature_matches_matrix_for_every_key_and_plan(plan, key):
    account = SimpleNamespace(plan=plan.value)
    assert has_feature(account, key) is PLAN_FEATURES[plan][key]


def test_has_feature_accepts_raw_plan_string():
    assert has_feature("professional", "sales") is True
    assert has_feature("starter", "sales") is False


def test_has_feature_accepts_subscription_plan_enum():
    assert has_feature(SubscriptionPlan.ENTERPRISE, "aiRecordExtraction") is True


def test_has_feature_unknown_key_is_false():
    assert has_feature("enterprise", "notARealFeature") is False


def test_has_feature_unknown_plan_is_false():
    assert has_feature("ultimate", "sales") is False


def test_has_feature_none_account_is_false():
    assert has_feature(None, "sales") is False


def test_professional_and_enterprise_are_identical_across_every_gated_feature():
    """Corrected on stakeholder review: aiRecordExtraction was Enterprise-only
    in an earlier iteration of this matrix; it's now available on both
    Professional and Enterprise, matching the Billing plan-card mockup (same
    checkmarks for both tiers). Enterprise's only remaining differentiator is
    the numeric records limit (PLAN_LIMITS), not any boolean feature gate."""
    pro = PLAN_FEATURES[SubscriptionPlan.PROFESSIONAL]
    ent = PLAN_FEATURES[SubscriptionPlan.ENTERPRISE]
    diffs = {k for k in pro if pro[k] != ent.get(k)}
    assert diffs == set()
    assert pro["aiRecordExtraction"] is True
    assert ent["aiRecordExtraction"] is True


def test_starter_has_no_gated_features_only_always_on_ones():
    starter = PLAN_FEATURES[SubscriptionPlan.STARTER]
    always_on = {"cemeteryMap", "publicMemorialPages", "qrCodes"}
    assert {k for k, v in starter.items() if v} == always_on


@pytest.mark.parametrize(
    "plan,expected",
    [
        (SubscriptionPlan.STARTER, 5000),
        (SubscriptionPlan.PROFESSIONAL, 25000),
        (SubscriptionPlan.ENTERPRISE, None),
    ],
)
def test_feature_limit_records(plan, expected):
    account = SimpleNamespace(plan=plan.value)
    assert feature_limit(account, "records") == expected


def test_feature_limit_unrecognized_plan_is_none():
    assert feature_limit("not-a-plan", "records") is None


def test_plan_limits_staff_values_match_prd():
    assert PLAN_LIMITS[SubscriptionPlan.STARTER]["staff"] == 2
    assert PLAN_LIMITS[SubscriptionPlan.PROFESSIONAL]["staff"] == 10
    assert PLAN_LIMITS[SubscriptionPlan.ENTERPRISE]["staff"] == 10


def test_feature_gate_message_two_plans():
    # aiRecordExtraction and sales are both examples of the common
    # Starter-excluded, Professional+Enterprise-included shape every gated
    # feature in this matrix now follows.
    assert feature_gate_message("aiRecordExtraction") == "AI record extraction is available on the Professional and Enterprise plans."
    assert feature_gate_message("sales") == "Sales is available on the Professional and Enterprise plans."


@pytest.mark.parametrize("plan", list(SubscriptionPlan))
def test_effective_records_limit_capped_at_trial_limit_while_trialing(plan):
    """Every plan — including unlimited Enterprise — is capped at
    TRIAL_RECORD_LIMIT while the tenant is on its free trial."""
    account = SimpleNamespace(plan=plan.value)
    assert effective_records_limit(account, is_trialing=True) == TRIAL_RECORD_LIMIT


@pytest.mark.parametrize(
    "plan,expected",
    [
        (SubscriptionPlan.STARTER, 5000),
        (SubscriptionPlan.PROFESSIONAL, 25000),
        (SubscriptionPlan.ENTERPRISE, None),
    ],
)
def test_effective_records_limit_matches_plan_once_not_trialing(plan, expected):
    account = SimpleNamespace(plan=plan.value)
    assert effective_records_limit(account, is_trialing=False) == expected


def test_records_limit_reached_message_mentions_trial_and_upgrade_path():
    account = SimpleNamespace(plan=SubscriptionPlan.ENTERPRISE.value)
    msg = records_limit_reached_message(account, is_trialing=True)
    assert str(TRIAL_RECORD_LIMIT) in msg
    assert "trial" in msg.lower()


def test_records_limit_reached_message_mentions_plan_once_paid():
    account = SimpleNamespace(plan=SubscriptionPlan.STARTER.value)
    msg = records_limit_reached_message(account, is_trialing=False)
    assert "5000" in msg
    assert "Starter" in msg


def test_feature_gate_message_unfeatured_key_falls_back_gracefully():
    # No key in PLAN_FEATURES is `False` on every plan today, but the
    # fallback branch of feature_gate_message() must still degrade sanely
    # (not raise, not produce an empty "available on the plans." string) if
    # one ever is — e.g. mid-rollout of a not-yet-launched gate.
    assert feature_gate_message("notARealFeature") == "notARealFeature is not available on your plan."
