"""Tests for the feature entitlement (commerce) layer of Phase 6."""

from __future__ import annotations

import pytest
from django.urls import reverse
from django.utils import timezone

from simorgh.apps.events.bus import clear_subscribers
from simorgh.apps.modules.models import (
    FeatureEntitlement,
    FeatureEntitlementSource,
)
from simorgh.apps.modules.registry import (
    FeatureSpec,
    ModuleError,
    ModuleManifest,
    deserialize,
    register_module,
    reset_registry_for_tests,
)
from simorgh.apps.modules.services import (
    enable_module,
    grant_feature,
    install_module,
    is_feature_available,
    list_feature_status,
    purchase_feature,
    revoke_feature,
    toggle_feature,
)


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def _registry():
    reset_registry_for_tests()
    clear_subscribers()
    yield
    reset_registry_for_tests()
    clear_subscribers()


def _crm_with_features():
    return ModuleManifest(
        name="crm",
        version=1,
        label_key="module.crm.label",
        features=(
            FeatureSpec(
                code="contacts",
                label_key="crm.feature.contacts",
                billing="included",
                scope="tenant",
            ),
            FeatureSpec(
                code="advanced_reports",
                label_key="crm.feature.reports",
                billing="paid",
                scope="tenant",
                price_hint="$9/mo",
            ),
            FeatureSpec(
                code="ai_assistant",
                label_key="crm.feature.ai",
                billing="paid",
                scope="user",
                depends_on=("contacts",),
            ),
            FeatureSpec(
                code="onboarding_pack",
                label_key="crm.feature.onb",
                billing="addon",
                scope="tenant",
            ),
        ),
    )


@pytest.fixture
def crm(_registry):
    m = _crm_with_features()
    register_module(m)
    return m


# ---------------------------------------------------------------------------
# Spec validation
# ---------------------------------------------------------------------------
def test_feature_spec_rejects_unknown_billing(_registry):
    with pytest.raises(ModuleError):
        FeatureSpec(code="x", label_key="x", billing="freemium")


def test_feature_spec_rejects_unknown_scope(_registry):
    with pytest.raises(ModuleError):
        FeatureSpec(code="x", label_key="x", scope="org")


def test_feature_spec_rejects_self_dependency(_registry):
    with pytest.raises(ModuleError):
        FeatureSpec(code="x", label_key="x", depends_on=("x",))


def test_manifest_rejects_feature_dep_to_unknown(_registry):
    with pytest.raises(ModuleError):
        ModuleManifest(
            name="bad",
            version=1,
            label_key="b",
            features=(
                FeatureSpec(code="a", label_key="a", depends_on=("missing",)),
            ),
        )


def test_manifest_rejects_duplicate_feature_codes(_registry):
    with pytest.raises(ModuleError):
        ModuleManifest(
            name="bad",
            version=1,
            label_key="b",
            features=(
                FeatureSpec(code="a", label_key="a"),
                FeatureSpec(code="a", label_key="a2"),
            ),
        )


def test_manifest_serialize_round_trip_with_features(crm):
    payload = crm.serialize()
    assert payload["features"][0]["code"] == "contacts"
    back = deserialize(payload)
    assert back == crm


# ---------------------------------------------------------------------------
# Auto-grant on enable
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_enable_auto_grants_included_tenant_features(
    crm, tenant_acme, acme_tree,
):
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    rows = FeatureEntitlement.objects.filter(tenant=tenant_acme, module__name="crm")
    codes = sorted(r.feature_code for r in rows)
    # Only the 'included'+'tenant' feature is auto-granted.
    assert codes == ["contacts"]
    assert rows.get(feature_code="contacts").source == FeatureEntitlementSource.INCLUDED


@pytest.mark.django_db
def test_enable_auto_grant_is_idempotent(crm, tenant_acme, acme_tree):
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    assert FeatureEntitlement.objects.filter(
        tenant=tenant_acme, module__name="crm", feature_code="contacts",
    ).count() == 1


# ---------------------------------------------------------------------------
# Grant / revoke / toggle
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_grant_feature_rejects_user_id_on_tenant_scope(
    crm, tenant_acme, acme_tree, alice,
):
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    with pytest.raises(ModuleError):
        grant_feature(
            tenant_acme, "crm", "advanced_reports", user_id=alice.pk,
        )


@pytest.mark.django_db
def test_grant_feature_dependency_must_be_available(
    crm, tenant_acme, acme_tree, alice,
):
    install_module(tenant_acme, "crm")
    # Module not yet enabled → 'contacts' is not available → dep check fails.
    with pytest.raises(ModuleError):
        grant_feature(
            tenant_acme, "crm", "ai_assistant", user_id=alice.pk,
        )


@pytest.mark.django_db
def test_revoke_feature_blocks_availability(crm, tenant_acme, acme_tree):
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    assert is_feature_available(tenant_acme, "crm", "contacts") is True
    revoke_feature(tenant_acme, "crm", "contacts")
    assert is_feature_available(tenant_acme, "crm", "contacts") is False


@pytest.mark.django_db
def test_toggle_feature_flips_enabled(crm, tenant_acme, acme_tree):
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    toggle_feature(tenant_acme, "crm", "contacts", enabled=False)
    assert is_feature_available(tenant_acme, "crm", "contacts") is False
    toggle_feature(tenant_acme, "crm", "contacts", enabled=True)
    assert is_feature_available(tenant_acme, "crm", "contacts") is True


# ---------------------------------------------------------------------------
# Purchase
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_purchase_paid_feature(crm, tenant_acme, acme_tree):
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    ent = purchase_feature(tenant_acme, "crm", "advanced_reports")
    assert ent.source == FeatureEntitlementSource.PURCHASED
    assert is_feature_available(tenant_acme, "crm", "advanced_reports") is True


@pytest.mark.django_db
def test_purchase_user_scoped_feature(crm, tenant_acme, acme_tree, alice):
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    purchase_feature(tenant_acme, "crm", "ai_assistant", user_id=alice.pk)
    assert is_feature_available(
        tenant_acme, "crm", "ai_assistant", user_id=alice.pk,
    ) is True
    # Another user did not buy → not available for them.
    assert is_feature_available(
        tenant_acme, "crm", "ai_assistant", user_id=alice.pk + 9999,
    ) is False


@pytest.mark.django_db
def test_purchase_rejects_included_feature(crm, tenant_acme, acme_tree):
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    with pytest.raises(ModuleError):
        purchase_feature(tenant_acme, "crm", "contacts")


# ---------------------------------------------------------------------------
# Availability resolution edges
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_feature_not_available_when_module_disabled(
    crm, tenant_acme, acme_tree,
):
    install_module(tenant_acme, "crm")
    # Never enabled → no entitlements + module not ENABLED → False.
    assert is_feature_available(tenant_acme, "crm", "contacts") is False


@pytest.mark.django_db
def test_expired_entitlement_not_available(crm, tenant_acme, acme_tree):
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    past = timezone.now() - timezone.timedelta(days=1)
    grant_feature(
        tenant_acme, "crm", "advanced_reports", expires_at=past,
    )
    assert is_feature_available(tenant_acme, "crm", "advanced_reports") is False


@pytest.mark.django_db
def test_list_feature_status_reports_all(crm, tenant_acme, acme_tree):
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    rows = list_feature_status(tenant_acme, "crm")
    by_code = {r["code"]: r for r in rows}
    assert by_code["contacts"]["available"] is True
    assert by_code["advanced_reports"]["available"] is False
    assert by_code["onboarding_pack"]["billing"] == "addon"


# ---------------------------------------------------------------------------
# HTTP API
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_feature_list_endpoint(
    api_client, crm, tenant_acme, acme_tree, alice,
):
    alice.is_superuser = True
    alice.save()
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    api_client.force_login(alice)
    resp = api_client.get(
        reverse("platform_modules:feature-list", args=["crm"]),
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 200
    codes = sorted(r["code"] for r in resp.json()["results"])
    assert codes == ["advanced_reports", "ai_assistant", "contacts", "onboarding_pack"]


@pytest.mark.django_db
def test_feature_grant_revoke_endpoints(
    api_client, crm, tenant_acme, acme_tree, alice,
):
    alice.is_superuser = True
    alice.save()
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    api_client.force_login(alice)
    headers = {"HTTP_X_TENANT": tenant_acme.slug}
    resp = api_client.post(
        reverse("platform_modules:feature-grant", args=["crm", "advanced_reports"]),
        data={},
        format="json",
        **headers,
    )
    assert resp.status_code == 200
    assert resp.json()["source"] == "granted"
    resp = api_client.post(
        reverse("platform_modules:feature-revoke", args=["crm", "advanced_reports"]),
        data={},
        format="json",
        **headers,
    )
    assert resp.status_code == 200
    assert resp.json()["revoked_at"] is not None


@pytest.mark.django_db
def test_feature_purchase_endpoint(
    api_client, crm, tenant_acme, acme_tree, alice,
):
    alice.is_superuser = True
    alice.save()
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    api_client.force_login(alice)
    resp = api_client.post(
        reverse("platform_modules:feature-purchase", args=["crm", "ai_assistant"]),
        data={},
        format="json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 201
    body = resp.json()
    assert body["source"] == "purchased"
    assert body["user_id"] == alice.pk


@pytest.mark.django_db
def test_feature_toggle_endpoint(
    api_client, crm, tenant_acme, acme_tree, alice,
):
    alice.is_superuser = True
    alice.save()
    install_module(tenant_acme, "crm")
    enable_module(tenant_acme, "crm")
    api_client.force_login(alice)
    resp = api_client.post(
        reverse("platform_modules:feature-toggle", args=["crm", "contacts"]),
        data={"enabled": False},
        format="json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 200
    assert resp.json()["enabled"] is False
