"""Phase 2 — Feature Resolution Engine tests.

Covers:
- is_feature_enabled: all 6 algorithm steps
- get_all_enabled_features: set aggregation
- is_always_on features
- TenantFeatureOverride: disable overrides plan
- TenantFeatureOverride: enable grants without plan
- TenantAddonFeature: active vs expired
- SubscriptionCheckMiddleware: suspended → 403, expired → 402
- modules.selectors.is_feature_enabled delegates to subscription
- check_feature helper
"""

from __future__ import annotations

import pytest
from django.utils import timezone

from simorgh.apps.subscription.models import (
    PlatformPlan,
    PlanFeature,
    SubscriptionStatus,
    TenantAddonFeature,
    TenantSubscription,
)
from simorgh.apps.subscription.selectors import (
    get_all_enabled_features,
    is_feature_enabled,
)
from simorgh.apps.subscription.guards import check_feature, FeatureNotAvailable
from simorgh.apps.modules.models import FeatureCatalog, TenantFeatureOverride


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def starter_plan(db) -> PlatformPlan:
    plan = PlatformPlan.objects.create(
        code="starter_test",
        name="Starter Test",
        price_monthly="0",
        price_yearly="0",
        is_active=True,
        is_public=False,
        sort_order=99,
    )
    # Include crm.contacts but NOT hr.payroll
    PlanFeature.objects.create(plan=plan, feature_key="crm.contacts", enabled_by_default=True)
    PlanFeature.objects.create(plan=plan, feature_key="crm.pipeline", enabled_by_default=True)
    return plan


@pytest.fixture
def tenant_sub(tenant_acme, starter_plan, acme_tree) -> TenantSubscription:
    return TenantSubscription.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        plan=starter_plan,
        status=SubscriptionStatus.ACTIVE,
    )


# ---------------------------------------------------------------------------
# Step 5 — Plan Features
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_plan_feature_enabled(tenant_sub, tenant_acme):
    """Tenant with starter plan can access crm.contacts (in plan)."""
    assert is_feature_enabled(tenant_acme, "crm.contacts") is True


@pytest.mark.django_db
def test_plan_feature_not_in_plan(tenant_sub, tenant_acme):
    """Tenant with starter plan cannot access hr.payroll (not in plan)."""
    assert is_feature_enabled(tenant_acme, "hr.payroll") is False


# ---------------------------------------------------------------------------
# Step 4 — Add-on Feature
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_addon_grants_access(tenant_sub, tenant_acme, acme_tree):
    """Tenant with starter + addon hr.payroll can access hr.payroll."""
    TenantAddonFeature.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        feature_key="hr.payroll",
    )
    assert is_feature_enabled(tenant_acme, "hr.payroll") is True


@pytest.mark.django_db
def test_expired_addon_denied(tenant_sub, tenant_acme, acme_tree):
    """An expired addon does not grant access."""
    TenantAddonFeature.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        feature_key="hr.payroll",
        expires_at=timezone.now() - timezone.timedelta(days=1),
    )
    assert is_feature_enabled(tenant_acme, "hr.payroll") is False


# ---------------------------------------------------------------------------
# Step 2 — Override DISABLED beats plan
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_override_disable_beats_plan(tenant_sub, tenant_acme, acme_tree):
    """TenantFeatureOverride(enabled=False) disables a plan-included feature."""
    TenantFeatureOverride.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        feature_key="crm.contacts",
        name="crm.contacts",
        enabled=False,
    )
    assert is_feature_enabled(tenant_acme, "crm.contacts") is False


# ---------------------------------------------------------------------------
# Step 3 — Override ENABLED grants without plan
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_override_enable_grants_without_plan(tenant_sub, tenant_acme, acme_tree):
    """TenantFeatureOverride(enabled=True) grants a feature not in the plan."""
    TenantFeatureOverride.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        feature_key="hr.payroll",
        name="hr.payroll",
        enabled=True,
    )
    assert is_feature_enabled(tenant_acme, "hr.payroll") is True


# ---------------------------------------------------------------------------
# Step 1 — is_always_on
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_always_on_feature_enabled_without_subscription(tenant_acme):
    """is_always_on features are enabled even with no subscription."""
    # Ensure no subscription exists for this tenant
    TenantSubscription.objects.filter(tenant=tenant_acme).delete()

    # Create or update an always_on feature catalog entry
    FeatureCatalog.objects.update_or_create(
        code="core.notifications",
        defaults={
            "label": "Notifications",
            "module_name": "notifications",
            "is_always_on": True,
            "is_platform_internal": True,
        },
    )
    assert is_feature_enabled(tenant_acme, "core.notifications") is True


# ---------------------------------------------------------------------------
# get_all_enabled_features
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_get_all_enabled_features_includes_plan(tenant_sub, tenant_acme):
    """get_all_enabled_features includes plan features."""
    features = get_all_enabled_features(tenant_acme.pk)
    assert "crm.contacts" in features
    assert "crm.pipeline" in features


@pytest.mark.django_db
def test_get_all_enabled_features_excludes_non_plan(tenant_sub, tenant_acme):
    """get_all_enabled_features excludes features not in the plan."""
    features = get_all_enabled_features(tenant_acme.pk)
    assert "hr.payroll" not in features


@pytest.mark.django_db
def test_get_all_enabled_features_addon_included(tenant_sub, tenant_acme, acme_tree):
    """get_all_enabled_features includes active addons."""
    TenantAddonFeature.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        feature_key="bi.forecasting",
    )
    features = get_all_enabled_features(tenant_acme.pk)
    assert "bi.forecasting" in features


@pytest.mark.django_db
def test_get_all_enabled_features_override_removes(tenant_sub, tenant_acme, acme_tree):
    """Override(disabled) removes a plan feature from the set."""
    TenantFeatureOverride.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        feature_key="crm.pipeline",
        name="crm.pipeline",
        enabled=False,
    )
    features = get_all_enabled_features(tenant_acme.pk)
    assert "crm.pipeline" not in features


# ---------------------------------------------------------------------------
# check_feature helper
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_check_feature_returns_bool(tenant_sub, tenant_acme):
    assert check_feature(tenant_acme.pk, "crm.contacts") is True
    assert check_feature(tenant_acme.pk, "hr.payroll") is False


@pytest.mark.django_db
def test_check_feature_unknown_tenant():
    """check_feature returns False for a non-existent tenant."""
    assert check_feature(9999999, "crm.contacts") is False


# ---------------------------------------------------------------------------
# modules.selectors delegation
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_modules_selectors_delegates(tenant_sub, tenant_acme):
    """modules.selectors.is_feature_enabled delegates to subscription layer."""
    from simorgh.apps.modules.selectors import is_feature_enabled as modules_ife

    assert modules_ife(tenant_acme.pk, "crm.contacts") is True
    assert modules_ife(tenant_acme.pk, "hr.payroll") is False


# ---------------------------------------------------------------------------
# SubscriptionCheckMiddleware
# ---------------------------------------------------------------------------


@pytest.fixture
def middleware_factory():
    """Return a configured SubscriptionCheckMiddleware around a dummy view."""
    from simorgh.apps.subscription.middleware import SubscriptionCheckMiddleware
    from django.http import HttpResponse

    def dummy_view(request):
        return HttpResponse("OK", status=200)

    return SubscriptionCheckMiddleware(dummy_view)


def _make_request(tenant):
    """Build a minimal fake request with a .tenant attribute."""
    from django.test import RequestFactory

    req = RequestFactory().get("/api/v1/crm/contacts/")
    req.tenant = tenant
    return req


@pytest.mark.django_db
def test_middleware_allows_active_subscription(
    middleware_factory, tenant_sub, tenant_acme
):
    """Active subscription — request passes through."""
    req = _make_request(tenant_acme)
    resp = middleware_factory(req)
    assert resp.status_code == 200


@pytest.mark.django_db
def test_middleware_blocks_suspended(middleware_factory, tenant_sub, tenant_acme):
    """Suspended subscription → 403."""
    tenant_sub.status = SubscriptionStatus.SUSPENDED
    tenant_sub.save(update_fields=["status"])
    req = _make_request(tenant_acme)
    resp = middleware_factory(req)
    assert resp.status_code == 403


@pytest.mark.django_db
def test_middleware_blocks_expired(middleware_factory, tenant_sub, tenant_acme):
    """Expired subscription → 402."""
    tenant_sub.status = SubscriptionStatus.EXPIRED
    tenant_sub.save(update_fields=["status"])
    req = _make_request(tenant_acme)
    resp = middleware_factory(req)
    assert resp.status_code == 402


@pytest.mark.django_db
def test_middleware_exempts_auth_path(middleware_factory, tenant_sub, tenant_acme):
    """Suspended tenant can still reach /api/v1/auth/ paths."""
    from django.test import RequestFactory

    tenant_sub.status = SubscriptionStatus.SUSPENDED
    tenant_sub.save(update_fields=["status"])

    req = RequestFactory().post("/api/v1/auth/login/")
    req.tenant = tenant_acme
    resp = middleware_factory(req)
    assert resp.status_code == 200


@pytest.mark.django_db
def test_middleware_exempts_subscription_path(middleware_factory, tenant_sub, tenant_acme):
    """Expired tenant can still reach /api/v1/subscription/ to renew."""
    from django.test import RequestFactory

    tenant_sub.status = SubscriptionStatus.EXPIRED
    tenant_sub.save(update_fields=["status"])

    req = RequestFactory().get("/api/v1/subscription/plans/")
    req.tenant = tenant_acme
    resp = middleware_factory(req)
    assert resp.status_code == 200


@pytest.mark.django_db
def test_middleware_allows_no_subscription(middleware_factory, tenant_acme):
    """Tenant with no subscription row is allowed through (onboarding grace)."""
    TenantSubscription.objects.filter(tenant=tenant_acme).delete()
    req = _make_request(tenant_acme)
    resp = middleware_factory(req)
    assert resp.status_code == 200
