# app/modules/core/tests/integration/test_merchant_menu_routes.py
"""
Integration tests for merchant menu API routes.

Tests the merchant menu rendering endpoint at:
    GET /api/v1/merchants/core/menu/render/merchant

Verifies:
- Dynamic menu rendering based on module enablement
- Subscription gating (only modules from subscribed platforms)
- Auth requirement
- Response structure
- Fallback when merchant has no subscriptions
"""

import uuid
from datetime import UTC, datetime, timedelta

import pytest

from app.api.deps import get_current_merchant_from_cookie_or_header
from app.modules.billing.models import (
    MerchantSubscription,
    SubscriptionStatus,
    SubscriptionTier,
)
from app.modules.tenancy.models import Merchant, Platform, User
from app.modules.tenancy.models.platform_module import PlatformModule
from app.modules.tenancy.schemas.auth import UserContext
from main import app

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

BASE = "/api/v1/merchants/core/menu/render/merchant"


@pytest.fixture
def menu_owner(db):
    """Create a merchant owner user."""
    from middleware.auth import AuthManager

    auth = AuthManager()
    user = User(
        email=f"menuowner_{uuid.uuid4().hex[:8]}@test.com",
        username=f"menuowner_{uuid.uuid4().hex[:8]}",
        hashed_password=auth.hash_password("pass123"),
        role="merchant_owner",
        is_active=True,
    )
    db.add(user)
    db.commit()
    db.refresh(user)
    return user


@pytest.fixture
def menu_admin(db):
    """Create a super admin for enabling modules."""
    from middleware.auth import AuthManager

    auth = AuthManager()
    user = User(
        email=f"menuadmin_{uuid.uuid4().hex[:8]}@test.com",
        username=f"menuadmin_{uuid.uuid4().hex[:8]}",
        hashed_password=auth.hash_password("pass123"),
        role="super_admin",
        is_active=True,
    )
    db.add(user)
    db.commit()
    db.refresh(user)
    return user


@pytest.fixture
def menu_platform(db):
    """Create a platform."""
    platform = Platform(
        code=f"mp_{uuid.uuid4().hex[:8]}",
        name="Menu Test Platform",
        is_active=True,
    )
    db.add(platform)
    db.commit()
    db.refresh(platform)
    return platform


@pytest.fixture
def menu_merchant(db, menu_owner):
    """Create a merchant owned by menu_owner."""
    merchant = Merchant(
        name="Menu Test Merchant",
        owner_user_id=menu_owner.id,
        contact_email=menu_owner.email,
        is_active=True,
        is_verified=True,
    )
    db.add(merchant)
    db.commit()
    db.refresh(merchant)
    return merchant


@pytest.fixture
def menu_tier(db, menu_platform):
    """Create a subscription tier."""
    tier = SubscriptionTier(
        code=f"pro_{uuid.uuid4().hex[:8]}",
        name="Professional",
        description="Pro tier",
        price_monthly_cents=2900,
        price_annual_cents=29000,
        display_order=1,
        is_active=True,
        is_public=True,
        platform_id=menu_platform.id,
    )
    db.add(tier)
    db.commit()
    db.refresh(tier)
    return tier


@pytest.fixture
def menu_subscription(db, menu_merchant, menu_platform, menu_tier):
    """Create an active subscription for the merchant on the platform."""
    sub = MerchantSubscription(
        merchant_id=menu_merchant.id,
        platform_id=menu_platform.id,
        tier_id=menu_tier.id,
        status=SubscriptionStatus.ACTIVE.value,
        is_annual=False,
        period_start=datetime.now(UTC),
        period_end=datetime.now(UTC) + timedelta(days=30),
    )
    db.add(sub)
    db.commit()
    db.refresh(sub)
    return sub


@pytest.fixture
def menu_loyalty_module(db, menu_platform, menu_admin):
    """Enable loyalty module on the platform."""
    pm = PlatformModule(
        platform_id=menu_platform.id,
        module_code="loyalty",
        is_enabled=True,
        enabled_at=datetime.now(UTC),
        enabled_by_user_id=menu_admin.id,
        config={},
    )
    db.add(pm)
    db.commit()
    db.refresh(pm)
    return pm


@pytest.fixture
def menu_auth(menu_owner):
    """Override auth dependency for merchant cookie/header auth."""
    user_context = UserContext(
        id=menu_owner.id,
        email=menu_owner.email,
        username=menu_owner.username,
        role="merchant_owner",
        is_active=True,
    )

    def _override():
        return user_context

    app.dependency_overrides[get_current_merchant_from_cookie_or_header] = _override
    yield {"Authorization": "Bearer fake-token"}
    app.dependency_overrides.pop(get_current_merchant_from_cookie_or_header, None)


# ============================================================================
# Tests
# ============================================================================


@pytest.mark.integration
@pytest.mark.core
class TestMerchantMenuRendering:
    """Tests for GET /api/v1/merchants/core/menu/render/merchant."""

    def test_requires_auth(self, client):
        """Returns 401 without auth."""
        app.dependency_overrides.pop(get_current_merchant_from_cookie_or_header, None)
        response = client.get(BASE)
        assert response.status_code == 401

    def test_returns_valid_structure(
        self, client, db, menu_auth, menu_merchant, menu_subscription
    ):
        """Response has correct top-level structure."""
        response = client.get(BASE, headers=menu_auth)
        assert response.status_code == 200
        data = response.json()
        assert "frontend_type" in data
        assert data["frontend_type"] == "merchant"
        assert "sections" in data
        assert isinstance(data["sections"], list)

    def test_returns_core_sections(
        self, client, db, menu_auth, menu_merchant, menu_subscription
    ):
        """Menu includes core sections: main, billing, account."""
        response = client.get(BASE, headers=menu_auth)
        assert response.status_code == 200
        data = response.json()
        section_ids = {s["id"] for s in data["sections"]}
        assert "main" in section_ids
        assert "billing" in section_ids
        assert "account" in section_ids

    def test_main_section_has_dashboard(
        self, client, db, menu_auth, menu_merchant, menu_subscription
    ):
        """Main section contains the dashboard item."""
        response = client.get(BASE, headers=menu_auth)
        data = response.json()
        main = next(s for s in data["sections"] if s["id"] == "main")
        item_ids = [i["id"] for i in main["items"]]
        assert "dashboard" in item_ids

    def test_section_items_have_required_fields(
        self, client, db, menu_auth, menu_merchant, menu_subscription
    ):
        """Each menu item has id, label, icon, and url."""
        response = client.get(BASE, headers=menu_auth)
        data = response.json()
        for section in data["sections"]:
            for item in section["items"]:
                assert "id" in item
                assert "label" in item
                assert "icon" in item
                assert "url" in item
                assert item["label"] is not None
                assert item["url"].startswith("/merchants/")

    def test_sections_have_correct_labels(
        self, client, db, menu_auth, menu_merchant, menu_subscription
    ):
        """Labeled sections have non-null labels, main section has null label."""
        response = client.get(BASE, headers=menu_auth)
        data = response.json()
        for section in data["sections"]:
            if section["id"] == "main":
                assert section["label"] is None
            else:
                assert section["label"] is not None


@pytest.mark.integration
@pytest.mark.core
class TestMerchantMenuModuleGating:
    """Tests for module-based menu filtering."""

    def test_loyalty_appears_when_module_enabled(
        self, client, db, menu_auth, menu_merchant, menu_subscription,
        menu_loyalty_module, menu_platform,
    ):
        """Loyalty items appear under platform section when module is enabled."""
        response = client.get(BASE, headers=menu_auth)
        assert response.status_code == 200
        data = response.json()
        # Loyalty now appears under a platform-{code} section
        platform_section_id = f"platform-{menu_platform.code}"
        section_ids = {s["id"] for s in data["sections"]}
        assert platform_section_id in section_ids
        # Check loyalty item exists in that platform section
        platform_section = next(
            s for s in data["sections"] if s["id"] == platform_section_id
        )
        item_ids = {i["id"] for i in platform_section["items"]}
        assert "program" in item_ids

    def test_loyalty_hidden_when_module_not_enabled(
        self, client, db, menu_auth, menu_merchant, menu_subscription,
        menu_platform,
    ):
        """No platform section when no non-core modules are enabled."""
        response = client.get(BASE, headers=menu_auth)
        assert response.status_code == 200
        data = response.json()
        platform_section_id = f"platform-{menu_platform.code}"
        section_ids = {s["id"] for s in data["sections"]}
        assert platform_section_id not in section_ids

    def test_loyalty_item_has_correct_route(
        self, client, db, menu_auth, menu_merchant, menu_subscription,
        menu_loyalty_module, menu_platform,
    ):
        """Loyalty program item has the correct URL."""
        response = client.get(BASE, headers=menu_auth)
        data = response.json()
        platform_section_id = f"platform-{menu_platform.code}"
        platform_section = next(
            s for s in data["sections"] if s["id"] == platform_section_id
        )
        overview = next(
            i for i in platform_section["items"] if i["id"] == "program"
        )
        assert overview["url"] == "/merchants/loyalty/program"


@pytest.mark.integration
@pytest.mark.core
class TestMerchantMenuNoSubscription:
    """Tests for menu when merchant has no subscriptions."""

    def test_empty_menu_when_no_merchant(self, client, db, menu_auth):
        """Returns empty sections when user has no merchant."""
        # menu_auth provides a user, but no merchant fixture is loaded
        # The endpoint queries for merchant by owner_user_id — none exists for this user
        # unless menu_merchant is requested
        response = client.get(BASE, headers=menu_auth)
        assert response.status_code == 200
        data = response.json()
        assert data["frontend_type"] == "merchant"
        assert data["sections"] == []

    def test_core_sections_with_merchant_no_subscription(
        self, client, db, menu_auth, menu_merchant,
    ):
        """With merchant but no subscription, core sections still appear."""
        response = client.get(BASE, headers=menu_auth)
        assert response.status_code == 200
        data = response.json()
        section_ids = {s["id"] for s in data["sections"]}
        # Core modules (core, billing, tenancy) are always included
        assert "main" in section_ids
        assert "billing" in section_ids
        assert "account" in section_ids
        # Non-core modules require subscription
        assert "loyalty" not in section_ids


@pytest.mark.integration
@pytest.mark.core
class TestMerchantMenuSubscriptionStatus:
    """Tests for subscription status filtering."""

    def test_trial_subscription_shows_modules(
        self, client, db, menu_auth, menu_merchant, menu_platform, menu_tier,
        menu_loyalty_module,
    ):
        """Trial subscription shows modules from that platform."""
        sub = MerchantSubscription(
            merchant_id=menu_merchant.id,
            platform_id=menu_platform.id,
            tier_id=menu_tier.id,
            status=SubscriptionStatus.TRIAL.value,
            is_annual=False,
            period_start=datetime.now(UTC),
            period_end=datetime.now(UTC) + timedelta(days=14),
            trial_ends_at=datetime.now(UTC) + timedelta(days=14),
        )
        db.add(sub)
        db.commit()

        response = client.get(BASE, headers=menu_auth)
        assert response.status_code == 200
        section_ids = {s["id"] for s in response.json()["sections"]}
        platform_section_id = f"platform-{menu_platform.code}"
        assert platform_section_id in section_ids

    def test_expired_subscription_hides_non_core_modules(
        self, client, db, menu_auth, menu_merchant, menu_platform, menu_tier,
        menu_loyalty_module,
    ):
        """Expired subscription does not show modules from that platform."""
        sub = MerchantSubscription(
            merchant_id=menu_merchant.id,
            platform_id=menu_platform.id,
            tier_id=menu_tier.id,
            status=SubscriptionStatus.EXPIRED.value,
            is_annual=False,
            period_start=datetime.now(UTC) - timedelta(days=60),
            period_end=datetime.now(UTC) - timedelta(days=30),
        )
        db.add(sub)
        db.commit()

        response = client.get(BASE, headers=menu_auth)
        assert response.status_code == 200
        section_ids = {s["id"] for s in response.json()["sections"]}
        platform_section_id = f"platform-{menu_platform.code}"
        # Platform section should NOT appear because subscription is expired
        assert platform_section_id not in section_ids
        # Core sections always appear
        assert "main" in section_ids
        assert "billing" in section_ids


@pytest.mark.integration
@pytest.mark.core
class TestMerchantMenuMultiPlatform:
    """Tests for merchants subscribed to multiple platforms."""

    def test_union_of_modules_from_multiple_platforms(
        self, client, db, menu_auth, menu_merchant, menu_admin,
    ):
        """Modules enabled on different platforms are unioned together."""
        # Platform A with loyalty enabled
        platform_a = Platform(
            code=f"mpa_{uuid.uuid4().hex[:8]}",
            name="Platform A",
            is_active=True,
        )
        db.add(platform_a)
        db.flush()

        tier_a = SubscriptionTier(
            code=f"ta_{uuid.uuid4().hex[:8]}",
            name="Tier A",
            description="T",
            price_monthly_cents=1000,
            display_order=1,
            is_active=True,
            is_public=True,
            platform_id=platform_a.id,
        )
        db.add(tier_a)
        db.flush()

        db.add(PlatformModule(
            platform_id=platform_a.id,
            module_code="loyalty",
            is_enabled=True,
            enabled_at=datetime.now(UTC),
            enabled_by_user_id=menu_admin.id,
            config={},
        ))

        sub_a = MerchantSubscription(
            merchant_id=menu_merchant.id,
            platform_id=platform_a.id,
            tier_id=tier_a.id,
            status=SubscriptionStatus.ACTIVE.value,
            is_annual=False,
            period_start=datetime.now(UTC),
            period_end=datetime.now(UTC) + timedelta(days=30),
        )
        db.add(sub_a)

        # Platform B without loyalty
        platform_b = Platform(
            code=f"mpb_{uuid.uuid4().hex[:8]}",
            name="Platform B",
            is_active=True,
        )
        db.add(platform_b)
        db.flush()

        tier_b = SubscriptionTier(
            code=f"tb_{uuid.uuid4().hex[:8]}",
            name="Tier B",
            description="T",
            price_monthly_cents=1000,
            display_order=1,
            is_active=True,
            is_public=True,
            platform_id=platform_b.id,
        )
        db.add(tier_b)
        db.flush()

        sub_b = MerchantSubscription(
            merchant_id=menu_merchant.id,
            platform_id=platform_b.id,
            tier_id=tier_b.id,
            status=SubscriptionStatus.ACTIVE.value,
            is_annual=False,
            period_start=datetime.now(UTC),
            period_end=datetime.now(UTC) + timedelta(days=30),
        )
        db.add(sub_b)
        db.commit()

        response = client.get(BASE, headers=menu_auth)
        assert response.status_code == 200
        data = response.json()
        section_ids = {s["id"] for s in data["sections"]}
        # Loyalty enabled on Platform A appears under platform-a's section
        platform_a_section_id = f"platform-{platform_a.code}"
        assert platform_a_section_id in section_ids
        # Platform B has no non-core modules, so no section
        platform_b_section_id = f"platform-{platform_b.code}"
        assert platform_b_section_id not in section_ids
        # Check loyalty item exists in Platform A section
        pa_section = next(
            s for s in data["sections"] if s["id"] == platform_a_section_id
        )
        item_ids = {i["id"] for i in pa_section["items"]}
        assert "program" in item_ids
        # Core sections always present
        assert "main" in section_ids
        assert "billing" in section_ids
        assert "account" in section_ids
