"""Phase 3 — Subscription API tests.

Covers:
- GET  /api/v1/subscription/            — no sub → 204, with sub → 200
- GET  /api/v1/subscription/plans/      — public plans
- POST /api/v1/subscription/start-trial/ — happy path + duplicate error
- POST /api/v1/subscription/change-plan/ — upgrade existing sub
- POST /api/v1/subscription/cancel/      — cancel subscription
- GET  /api/v1/subscription/features/   — enabled feature set
- GET  /api/v1/subscription/limits/     — plan limits
- GET  /api/v1/subscription/addons/     — list addons
- POST /api/v1/subscription/addons/     — purchase addon
- DELETE /api/v1/subscription/addons/<key>/ — revoke addon
- Celery tasks: expire_subscriptions, expire_addon_features, check_expiring_trials
- Tenant.current_plan_code property
"""

from __future__ import annotations

import datetime

import pytest
from django.utils import timezone

from simorgh.apps.subscription.models import (
    PlatformPlan,
    PlanFeature,
    SubscriptionStatus,
    TenantAddonFeature,
    TenantSubscription,
)


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def free_plan(db) -> PlatformPlan:
    plan = PlatformPlan.objects.create(
        code="free_api_test",
        name="Free API Test",
        price_monthly="0",
        price_yearly="0",
        trial_days=14,
        user_limit=3,
        storage_gb=1,
        is_active=True,
        is_public=True,
        sort_order=1,
    )
    PlanFeature.objects.create(plan=plan, feature_key="collab.chat", enabled_by_default=True)
    return plan


@pytest.fixture
def business_plan(db) -> PlatformPlan:
    plan = PlatformPlan.objects.create(
        code="business_api_test",
        name="Business API Test",
        price_monthly="100",
        price_yearly="1000",
        trial_days=14,
        user_limit=50,
        storage_gb=100,
        is_active=True,
        is_public=True,
        sort_order=2,
    )
    PlanFeature.objects.create(plan=plan, feature_key="crm.pipeline", enabled_by_default=True)
    PlanFeature.objects.create(plan=plan, feature_key="hr.payroll", enabled_by_default=True)
    return plan


@pytest.fixture
def active_sub(tenant_acme, free_plan, acme_tree) -> TenantSubscription:
    return TenantSubscription.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        plan=free_plan,
        status=SubscriptionStatus.ACTIVE,
        current_period_start=timezone.now(),
        current_period_end=timezone.now() + datetime.timedelta(days=30),
    )


@pytest.fixture
def auth_alice(api_client, alice, alice_membership):
    """APIClient logged in as alice (session-based, works with PermissionGateMiddleware)."""
    api_client.force_login(alice)
    return api_client


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _url(path: str) -> str:
    return f"/api/v1/subscription{path}"


def _tenant_headers(tenant_acme) -> dict:
    return {"HTTP_X_TENANT": tenant_acme.slug}


# ---------------------------------------------------------------------------
# GET /api/v1/subscription/
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_subscription_detail_no_sub(auth_alice, tenant_acme):
    resp = auth_alice.get(_url("/"), **_tenant_headers(tenant_acme))
    assert resp.status_code == 204


@pytest.mark.django_db
def test_subscription_detail_with_sub(auth_alice, tenant_acme, active_sub):
    resp = auth_alice.get(_url("/"), **_tenant_headers(tenant_acme))
    assert resp.status_code == 200
    data = resp.json()
    assert data["status"] == "active"
    assert data["plan"]["code"] == active_sub.plan.code


@pytest.mark.django_db
def test_subscription_detail_unauthenticated(api_client, tenant_acme):
    resp = api_client.get(_url("/"), **_tenant_headers(tenant_acme))
    assert resp.status_code == 401


# ---------------------------------------------------------------------------
# GET /api/v1/subscription/plans/
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_plan_list(auth_alice, tenant_acme, free_plan, business_plan):
    resp = auth_alice.get(_url("/plans/"), **_tenant_headers(tenant_acme))
    assert resp.status_code == 200
    codes = [p["code"] for p in resp.json()]
    assert free_plan.code in codes
    assert business_plan.code in codes


# ---------------------------------------------------------------------------
# POST /api/v1/subscription/start-trial/
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_start_trial_success(auth_alice, tenant_acme, acme_tree, free_plan):
    resp = auth_alice.post(
        _url("/start-trial/"),
        data={"plan_code": free_plan.code},
        format="json",
        **_tenant_headers(tenant_acme),
    )
    assert resp.status_code == 201
    data = resp.json()
    assert data["status"] == "trial"
    assert data["plan"]["code"] == free_plan.code


@pytest.mark.django_db
def test_start_trial_duplicate_raises_400(auth_alice, tenant_acme, active_sub, free_plan):
    resp = auth_alice.post(
        _url("/start-trial/"),
        data={"plan_code": free_plan.code},
        format="json",
        **_tenant_headers(tenant_acme),
    )
    assert resp.status_code == 400


# ---------------------------------------------------------------------------
# POST /api/v1/subscription/change-plan/
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_change_plan_upgrades_subscription(
    auth_alice, tenant_acme, active_sub, business_plan
):
    resp = auth_alice.post(
        _url("/change-plan/"),
        data={"plan_code": business_plan.code, "billing_cycle": "monthly"},
        format="json",
        **_tenant_headers(tenant_acme),
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["plan"]["code"] == business_plan.code


@pytest.mark.django_db
def test_change_plan_invalid_code_returns_400(auth_alice, tenant_acme, active_sub):
    resp = auth_alice.post(
        _url("/change-plan/"),
        data={"plan_code": "nonexistent_plan"},
        format="json",
        **_tenant_headers(tenant_acme),
    )
    assert resp.status_code == 400


# ---------------------------------------------------------------------------
# POST /api/v1/subscription/cancel/
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_cancel_subscription(auth_alice, tenant_acme, active_sub):
    resp = auth_alice.post(
        _url("/cancel/"),
        data={"reason": "No longer needed"},
        format="json",
        **_tenant_headers(tenant_acme),
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["status"] == "cancelled"
    assert data["cancellation_reason"] == "No longer needed"


@pytest.mark.django_db
def test_cancel_no_subscription_returns_404(auth_alice, tenant_acme):
    resp = auth_alice.post(
        _url("/cancel/"),
        format="json",
        **_tenant_headers(tenant_acme),
    )
    assert resp.status_code == 404


# ---------------------------------------------------------------------------
# GET /api/v1/subscription/features/
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_feature_list_returns_set(auth_alice, tenant_acme, active_sub):
    resp = auth_alice.get(_url("/features/"), **_tenant_headers(tenant_acme))
    assert resp.status_code == 200
    data = resp.json()
    assert "features" in data
    assert isinstance(data["features"], list)
    # Plan features should be in the list
    assert "collab.chat" in data["features"]


# ---------------------------------------------------------------------------
# GET /api/v1/subscription/limits/
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_limits_returns_plan_limits(auth_alice, tenant_acme, active_sub):
    resp = auth_alice.get(_url("/limits/"), **_tenant_headers(tenant_acme))
    assert resp.status_code == 200
    data = resp.json()
    assert "user_limit" in data
    assert "storage_gb" in data
    assert data["user_limit"] == active_sub.plan.user_limit


@pytest.mark.django_db
def test_limits_no_sub_returns_defaults(auth_alice, tenant_acme):
    resp = auth_alice.get(_url("/limits/"), **_tenant_headers(tenant_acme))
    assert resp.status_code == 200
    data = resp.json()
    # Default limits when no subscription
    assert data["user_limit"] == 3


# ---------------------------------------------------------------------------
# GET /api/v1/subscription/addons/ & POST
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_addon_list_empty(auth_alice, tenant_acme):
    resp = auth_alice.get(_url("/addons/"), **_tenant_headers(tenant_acme))
    assert resp.status_code == 200
    assert resp.json() == []


@pytest.mark.django_db
def test_purchase_addon_success(auth_alice, tenant_acme, acme_tree):
    resp = auth_alice.post(
        _url("/addons/"),
        data={"feature_key": "ai.assistant", "price_paid": "50.00"},
        format="json",
        **_tenant_headers(tenant_acme),
    )
    assert resp.status_code == 201
    data = resp.json()
    assert data["feature_key"] == "ai.assistant"
    assert data["is_active"] is True


@pytest.mark.django_db
def test_addon_appears_in_list_after_purchase(auth_alice, tenant_acme, acme_tree):
    # Purchase
    auth_alice.post(
        _url("/addons/"),
        data={"feature_key": "bi.reports", "price_paid": "30.00"},
        format="json",
        **_tenant_headers(tenant_acme),
    )
    # List
    resp = auth_alice.get(_url("/addons/"), **_tenant_headers(tenant_acme))
    assert resp.status_code == 200
    keys = [a["feature_key"] for a in resp.json()]
    assert "bi.reports" in keys


# ---------------------------------------------------------------------------
# DELETE /api/v1/subscription/addons/<feature_key>/
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_revoke_addon(auth_alice, tenant_acme, acme_tree):
    # Purchase first
    TenantAddonFeature.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        feature_key="crm.forecast",
    )
    resp = auth_alice.delete(
        _url("/addons/crm.forecast/"), **_tenant_headers(tenant_acme)
    )
    assert resp.status_code == 204
    assert not TenantAddonFeature.objects.filter(
        tenant=tenant_acme, feature_key="crm.forecast"
    ).exists()


# ---------------------------------------------------------------------------
# Celery tasks
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_expire_subscriptions_task(tenant_acme, free_plan, acme_tree):
    from simorgh.apps.subscription.tasks import expire_subscriptions

    sub = TenantSubscription.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        plan=free_plan,
        status=SubscriptionStatus.ACTIVE,
        current_period_start=timezone.now() - datetime.timedelta(days=31),
        current_period_end=timezone.now() - datetime.timedelta(days=1),
    )
    result = expire_subscriptions()
    assert result["expired"] >= 1
    sub.refresh_from_db()
    assert sub.status == SubscriptionStatus.EXPIRED


@pytest.mark.django_db
def test_expire_addon_features_task(tenant_acme, acme_tree):
    from simorgh.apps.subscription.tasks import expire_addon_features

    TenantAddonFeature.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        feature_key="hr.payroll",
        expires_at=timezone.now() - datetime.timedelta(hours=1),
    )
    result = expire_addon_features()
    assert result["removed"] >= 1
    assert not TenantAddonFeature.objects.filter(
        tenant=tenant_acme, feature_key="hr.payroll"
    ).exists()


@pytest.mark.django_db
def test_check_expiring_trials_task(tenant_acme, free_plan, acme_tree):
    from simorgh.apps.subscription.tasks import check_expiring_trials

    TenantSubscription.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        plan=free_plan,
        status=SubscriptionStatus.TRIAL,
        trial_ends_at=timezone.now() + datetime.timedelta(days=2),
        current_period_start=timezone.now(),
        current_period_end=timezone.now() + datetime.timedelta(days=2),
    )
    result = check_expiring_trials()
    assert result["notified"] >= 1


# ---------------------------------------------------------------------------
# Tenant.current_plan_code property
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_current_plan_code_from_subscription(tenant_acme, free_plan, acme_tree):
    TenantSubscription.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        plan=free_plan,
        status=SubscriptionStatus.ACTIVE,
    )
    assert tenant_acme.current_plan_code == free_plan.code


@pytest.mark.django_db
def test_current_plan_code_falls_back_to_plan_ref(tenant_acme):
    tenant_acme.plan_ref = "legacy_plan"
    tenant_acme.save(update_fields=["plan_ref"])
    assert tenant_acme.current_plan_code == "legacy_plan"


@pytest.mark.django_db
def test_current_plan_code_empty_without_sub_or_ref(tenant_acme):
    assert tenant_acme.current_plan_code == ""
