# app/modules/loyalty/tests/integration/test_store_api.py
"""
Integration tests for store loyalty API endpoints.

Tests:
- GET /cards/lookup (route ordering fix)
- GET /cards/{card_id} (card detail)
- GET /transactions (customer_name in response)
- POST /points/earn (endpoint path rename)
- Store program CRUD endpoints removed (POST/PATCH /program)

Authentication: Uses real JWT tokens via store login endpoint.
"""

import uuid
from datetime import UTC, datetime

import pytest

from app.modules.loyalty.models import LoyaltyCard, LoyaltyProgram, LoyaltyTransaction
from app.modules.loyalty.models.loyalty_program import LoyaltyType


@pytest.fixture(autouse=True)
def _clear_rate_limiter():
    """Reset the in-memory rate limiter before each test to prevent bleed."""
    from middleware.decorators import rate_limiter

    rate_limiter.clients.clear()
    yield
    rate_limiter.clients.clear()
from app.modules.loyalty.models.loyalty_transaction import TransactionType
from app.modules.tenancy.models import User

BASE = "/api/v1/store/loyalty"


# ============================================================================
# Card Lookup Tests
# ============================================================================


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestCardLookup:
    """Tests for GET /cards/lookup endpoint."""

    def test_lookup_by_card_number(
        self, client, loyalty_store_headers, loyalty_store_setup
    ):
        """Find card by card number."""
        card = loyalty_store_setup["card"]
        response = client.get(
            f"{BASE}/cards/lookup",
            params={"q": card.card_number},
            headers=loyalty_store_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert data["card_number"] == card.card_number

    def test_lookup_by_email(
        self, client, loyalty_store_headers, loyalty_store_setup
    ):
        """Find card by customer email."""
        customer = loyalty_store_setup["customer"]
        response = client.get(
            f"{BASE}/cards/lookup",
            params={"q": customer.email},
            headers=loyalty_store_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert data["customer_email"] == customer.email

    def test_lookup_not_found(self, client, loyalty_store_headers):
        """Non-existent search returns 404."""
        response = client.get(
            f"{BASE}/cards/lookup",
            params={"q": "nonexistent@nowhere.com"},
            headers=loyalty_store_headers,
        )
        assert response.status_code == 404

    def test_lookup_route_not_captured_by_card_id(
        self, client, loyalty_store_headers
    ):
        """GET /cards/lookup is NOT matched by /cards/{card_id} (route ordering fix)."""
        response = client.get(
            f"{BASE}/cards/lookup",
            params={"q": "test"},
            headers=loyalty_store_headers,
        )
        # Should be 404 (not found) not 422 (validation error from parsing "lookup" as int)
        assert response.status_code != 422


# ============================================================================
# Card Detail Tests
# ============================================================================


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestCardDetail:
    """Tests for GET /cards/{card_id} endpoint."""

    def test_get_card_detail(
        self, client, loyalty_store_headers, loyalty_store_setup
    ):
        """Card detail includes customer_name."""
        card = loyalty_store_setup["card"]
        customer = loyalty_store_setup["customer"]
        response = client.get(
            f"{BASE}/cards/{card.id}",
            headers=loyalty_store_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert data["card_number"] == card.card_number
        assert data["customer_name"] == f"{customer.first_name} {customer.last_name}"

    def test_get_card_not_found(self, client, loyalty_store_headers):
        """Non-existent card returns 404."""
        response = client.get(
            f"{BASE}/cards/999999",
            headers=loyalty_store_headers,
        )
        assert response.status_code == 404


# ============================================================================
# Store Transactions Tests
# ============================================================================


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestStoreTransactions:
    """Tests for GET /transactions endpoint."""

    def test_list_transactions(
        self, client, loyalty_store_headers, loyalty_store_setup, db
    ):
        """Transactions endpoint returns data."""
        card = loyalty_store_setup["card"]
        store = loyalty_store_setup["store"]

        # Create a transaction
        tx = LoyaltyTransaction(
            merchant_id=card.merchant_id,
            card_id=card.id,
            store_id=store.id,
            transaction_type=TransactionType.POINTS_EARNED.value,
            points_delta=50,
            points_balance_after=150,
            transaction_at=datetime.now(UTC),
        )
        db.add(tx)
        db.commit()

        response = client.get(
            f"{BASE}/transactions",
            headers=loyalty_store_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert data["total"] >= 1
        assert len(data["transactions"]) >= 1

    def test_transactions_include_customer_name(
        self, client, loyalty_store_headers, loyalty_store_setup, db
    ):
        """Transaction responses include customer_name (not 'Unknown')."""
        card = loyalty_store_setup["card"]
        customer = loyalty_store_setup["customer"]
        store = loyalty_store_setup["store"]

        tx = LoyaltyTransaction(
            merchant_id=card.merchant_id,
            card_id=card.id,
            store_id=store.id,
            transaction_type=TransactionType.POINTS_EARNED.value,
            points_delta=25,
            transaction_at=datetime.now(UTC),
        )
        db.add(tx)
        db.commit()

        response = client.get(
            f"{BASE}/transactions",
            headers=loyalty_store_headers,
        )
        assert response.status_code == 200
        transactions = response.json()["transactions"]
        assert len(transactions) >= 1
        # Customer name should be populated, not None
        assert transactions[0]["customer_name"] == f"{customer.first_name} {customer.last_name}"


# ============================================================================
# Earn Points Tests
# ============================================================================


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestEarnPoints:
    """Tests for POST /points/earn endpoint."""

    def test_earn_points_endpoint_exists(
        self, client, loyalty_store_headers, loyalty_store_setup
    ):
        """POST /points/earn returns success (not 404)."""
        card = loyalty_store_setup["card"]
        response = client.post(
            f"{BASE}/points/earn",
            json={
                "card_id": card.id,
                "purchase_amount_cents": 1000,
            },
            headers=loyalty_store_headers,
        )
        # Should not be 404 (endpoint exists after rename)
        assert response.status_code != 404


# ============================================================================
# Store Program CRUD
# ============================================================================


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestStoreProgramCrud:
    """Tests for store program CRUD endpoints (merchant_owner only)."""

    def test_get_program_works(
        self, client, loyalty_store_headers, loyalty_store_setup
    ):
        """GET /program returns program."""
        response = client.get(
            f"{BASE}/program",
            headers=loyalty_store_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == loyalty_store_setup["program"].id

    def test_update_program_via_put(
        self, client, loyalty_store_headers, loyalty_store_setup
    ):
        """PUT /program updates the program (merchant_owner)."""
        response = client.put(
            f"{BASE}/program",
            json={"card_name": "Updated Card"},
            headers=loyalty_store_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert data["card_name"] == "Updated Card"

    def test_stats_still_works(
        self, client, loyalty_store_headers, loyalty_store_setup
    ):
        """GET /stats still works."""
        response = client.get(
            f"{BASE}/stats",
            headers=loyalty_store_headers,
        )
        assert response.status_code == 200

    def test_stats_has_new_fields(
        self, client, loyalty_store_headers, loyalty_store_setup
    ):
        """GET /stats returns all new fields."""
        response = client.get(
            f"{BASE}/stats",
            headers=loyalty_store_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert "new_this_month" in data
        assert "total_points_balance" in data
        assert "avg_points_per_member" in data
        assert "transactions_30d" in data
        assert "points_issued_30d" in data
        assert "points_redeemed_30d" in data


# ============================================================================
# Stamp Earn/Redeem Integration Tests
# ============================================================================


@pytest.fixture
def stamp_store_setup(db, loyalty_platform):
    """Setup with a stamps-type program for integration tests."""
    from app.modules.customers.models.customer import Customer
    from app.modules.tenancy.models.store import StoreUser
    from app.modules.tenancy.models.store_platform import StorePlatform
    from middleware.auth import AuthManager

    auth = AuthManager()
    uid = uuid.uuid4().hex[:8]

    owner = User(
        email=f"stampint_{uid}@test.com",
        username=f"stampint_{uid}",
        hashed_password=auth.hash_password("storepass123"),
        role="merchant_owner",
        is_active=True,
        is_email_verified=True,
    )
    db.add(owner)
    db.commit()
    db.refresh(owner)

    from app.modules.tenancy.models import Merchant, Store

    merchant = Merchant(
        name=f"Stamp Int Merchant {uid}",
        owner_user_id=owner.id,
        contact_email=owner.email,
        is_active=True,
        is_verified=True,
    )
    db.add(merchant)
    db.commit()
    db.refresh(merchant)

    store = Store(
        merchant_id=merchant.id,
        store_code=f"STINT_{uid.upper()}",
        subdomain=f"stint{uid}",
        name=f"Stamp Int Store {uid}",
        is_active=True,
        is_verified=True,
    )
    db.add(store)
    db.commit()
    db.refresh(store)

    store_user = StoreUser(store_id=store.id, user_id=owner.id, is_active=True)
    db.add(store_user)
    db.commit()

    sp = StorePlatform(store_id=store.id, platform_id=loyalty_platform.id)
    db.add(sp)
    db.commit()

    customer = Customer(
        email=f"stampcust_{uid}@test.com",
        first_name="Stamp",
        last_name="IntCustomer",
        hashed_password="!unused!",  # noqa: SEC001
        customer_number=f"SIC-{uid.upper()}",
        store_id=store.id,
        is_active=True,
    )
    db.add(customer)
    db.commit()
    db.refresh(customer)

    program = LoyaltyProgram(
        merchant_id=merchant.id,
        loyalty_type=LoyaltyType.HYBRID.value,
        stamps_target=3,
        stamps_reward_description="Free item",
        stamps_reward_value_cents=500,
        points_per_euro=10,
        welcome_bonus_points=0,
        minimum_redemption_points=100,
        minimum_purchase_cents=0,
        cooldown_minutes=0,
        max_daily_stamps=50,
        require_staff_pin=False,
        card_name="Int Stamp Card",
        card_color="#FF0000",
        is_active=True,
        points_rewards=[
            {"id": "r1", "name": "5 EUR off", "points_required": 100, "is_active": True},
        ],
    )
    db.add(program)
    db.commit()
    db.refresh(program)

    card = LoyaltyCard(
        merchant_id=merchant.id,
        program_id=program.id,
        customer_id=customer.id,
        enrolled_at_store_id=store.id,
        card_number=f"SINTCARD-{uid.upper()}",
        stamp_count=0,
        total_stamps_earned=0,
        stamps_redeemed=0,
        points_balance=0,
        total_points_earned=0,
        points_redeemed=0,
        is_active=True,
        last_activity_at=datetime.now(UTC),
    )
    db.add(card)
    db.commit()
    db.refresh(card)

    return {
        "owner": owner,
        "merchant": merchant,
        "store": store,
        "platform": loyalty_platform,
        "customer": customer,
        "program": program,
        "card": card,
    }


@pytest.fixture
def stamp_store_headers(client, stamp_store_setup):
    """JWT auth headers for stamp store setup."""
    owner = stamp_store_setup["owner"]
    response = client.post(
        "/api/v1/store/auth/login",
        json={"email_or_username": owner.username, "password": "storepass123"},
    )
    assert response.status_code == 200
    token = response.json()["access_token"]
    return {"Authorization": f"Bearer {token}"}


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestStampEarnRedeem:
    """Integration tests for stamp earn/redeem via store API."""

    def test_stamp_earn(self, client, stamp_store_headers, stamp_store_setup):
        """POST /stamp adds a stamp."""
        card = stamp_store_setup["card"]
        response = client.post(
            f"{BASE}/stamp",
            json={"card_id": card.id},
            headers=stamp_store_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert data["success"] is True
        assert data["stamp_count"] == 1

    def test_stamp_redeem(self, client, stamp_store_headers, stamp_store_setup, db):
        """POST /stamp/redeem redeems stamps."""
        card = stamp_store_setup["card"]
        program = stamp_store_setup["program"]

        # Give enough stamps
        card.stamp_count = program.stamps_target
        card.total_stamps_earned = program.stamps_target
        db.commit()

        response = client.post(
            f"{BASE}/stamp/redeem",
            json={"card_id": card.id},
            headers=stamp_store_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert data["success"] is True
        assert data["stamp_count"] == 0

    def test_stamp_endpoint_is_rate_limited(
        self, client, stamp_store_headers, stamp_store_setup
    ):
        """POST /stamp returns 429 once the per-IP cap is exceeded."""
        from unittest.mock import patch

        from middleware.decorators import rate_limiter

        rate_limiter.clients.clear()

        card = stamp_store_setup["card"]
        # Mock notification dispatch so 60 requests complete fast enough
        # to stay within the rate limiter's 60-second window.
        with patch(
            "app.modules.loyalty.tasks.notifications.send_notification_email"
        ):
            for _ in range(60):
                client.post(
                    f"{BASE}/stamp",
                    json={"card_id": card.id},
                    headers=stamp_store_headers,
                )

            response = client.post(
                f"{BASE}/stamp",
                json={"card_id": card.id},
                headers=stamp_store_headers,
            )
        assert response.status_code == 429

        rate_limiter.clients.clear()


# ============================================================================
# Item 2: PIN Ownership Checks
# ============================================================================


@pytest.fixture
def pin_ownership_setup(db, loyalty_platform):
    """Setup two stores under same merchant for PIN ownership tests."""
    from app.modules.customers.models.customer import Customer
    from app.modules.loyalty.models import StaffPin
    from app.modules.tenancy.models.store import StoreUser
    from app.modules.tenancy.models.store_platform import StorePlatform
    from middleware.auth import AuthManager

    auth = AuthManager()
    uid = uuid.uuid4().hex[:8]

    owner = User(
        email=f"pinown_{uid}@test.com",
        username=f"pinown_{uid}",
        hashed_password=auth.hash_password("storepass123"),
        role="merchant_owner",
        is_active=True,
        is_email_verified=True,
    )
    db.add(owner)
    db.commit()
    db.refresh(owner)

    from app.modules.tenancy.models import Merchant, Store

    merchant = Merchant(
        name=f"Pin Own Merchant {uid}",
        owner_user_id=owner.id,
        contact_email=owner.email,
        is_active=True,
        is_verified=True,
    )
    db.add(merchant)
    db.commit()
    db.refresh(merchant)

    # Store A (owner's store)
    store_a = Store(
        merchant_id=merchant.id,
        store_code=f"PINA_{uid.upper()}",
        subdomain=f"pina{uid}",
        name=f"Pin Store A {uid}",
        is_active=True,
        is_verified=True,
    )
    db.add(store_a)
    db.commit()
    db.refresh(store_a)

    store_user = StoreUser(store_id=store_a.id, user_id=owner.id, is_active=True)
    db.add(store_user)
    db.commit()

    sp = StorePlatform(store_id=store_a.id, platform_id=loyalty_platform.id)
    db.add(sp)
    db.commit()

    # Store B (different store)
    store_b = Store(
        merchant_id=merchant.id,
        store_code=f"PINB_{uid.upper()}",
        subdomain=f"pinb{uid}",
        name=f"Pin Store B {uid}",
        is_active=True,
        is_verified=True,
    )
    db.add(store_b)
    db.commit()
    db.refresh(store_b)

    program = LoyaltyProgram(
        merchant_id=merchant.id,
        loyalty_type=LoyaltyType.POINTS.value,
        points_per_euro=10,
        cooldown_minutes=0,
        max_daily_stamps=10,
        require_staff_pin=True,
        card_name="Pin Own Card",
        card_color="#00FF00",
        is_active=True,
    )
    db.add(program)
    db.commit()
    db.refresh(program)

    # Create PIN on store B
    pin_b = StaffPin(
        merchant_id=merchant.id,
        program_id=program.id,
        store_id=store_b.id,
        name="Other Store Staff",
    )
    pin_b.set_pin("5678")
    db.add(pin_b)
    db.commit()
    db.refresh(pin_b)

    return {
        "owner": owner,
        "merchant": merchant,
        "store_a": store_a,
        "store_b": store_b,
        "program": program,
        "pin_b": pin_b,
    }


@pytest.fixture
def pin_ownership_headers(client, pin_ownership_setup):
    """JWT auth headers for pin ownership setup (logged into store A)."""
    owner = pin_ownership_setup["owner"]
    response = client.post(
        "/api/v1/store/auth/login",
        json={"email_or_username": owner.username, "password": "storepass123"},
    )
    assert response.status_code == 200
    token = response.json()["access_token"]
    return {"Authorization": f"Bearer {token}"}


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestPinOwnershipCheck:
    """Tests for PIN ownership verification (Item 2)."""

    def test_update_pin_wrong_store_returns_404(
        self, client, pin_ownership_headers, pin_ownership_setup
    ):
        """Updating a PIN belonging to another store returns 404."""
        pin_b = pin_ownership_setup["pin_b"]
        response = client.patch(
            f"{BASE}/pins/{pin_b.id}",
            json={"name": "Hacked Name"},
            headers=pin_ownership_headers,
        )
        assert response.status_code == 404

    def test_delete_pin_wrong_store_returns_404(
        self, client, pin_ownership_headers, pin_ownership_setup
    ):
        """Deleting a PIN belonging to another store returns 404."""
        pin_b = pin_ownership_setup["pin_b"]
        response = client.delete(
            f"{BASE}/pins/{pin_b.id}",
            headers=pin_ownership_headers,
        )
        assert response.status_code == 404

    def test_unlock_pin_wrong_store_returns_404(
        self, client, pin_ownership_headers, pin_ownership_setup
    ):
        """Unlocking a PIN belonging to another store returns 404."""
        pin_b = pin_ownership_setup["pin_b"]
        response = client.post(
            f"{BASE}/pins/{pin_b.id}/unlock",
            headers=pin_ownership_headers,
        )
        assert response.status_code == 404


# ============================================================================
# Item 3: adjust_points role gate
# ============================================================================


@pytest.fixture
def staff_user_setup(db, loyalty_platform):
    """Setup with a staff user (not merchant_owner) for role gate tests."""
    from app.modules.customers.models.customer import Customer
    from app.modules.tenancy.models.store import StoreUser
    from app.modules.tenancy.models.store_platform import StorePlatform
    from middleware.auth import AuthManager

    auth = AuthManager()
    uid = uuid.uuid4().hex[:8]

    # merchant_owner creates the merchant
    owner = User(
        email=f"adjowner_{uid}@test.com",
        username=f"adjowner_{uid}",
        hashed_password=auth.hash_password("storepass123"),
        role="merchant_owner",
        is_active=True,
        is_email_verified=True,
    )
    db.add(owner)
    db.commit()
    db.refresh(owner)

    from app.modules.tenancy.models import Merchant, Store

    merchant = Merchant(
        name=f"Adjust Merchant {uid}",
        owner_user_id=owner.id,
        contact_email=owner.email,
        is_active=True,
        is_verified=True,
    )
    db.add(merchant)
    db.commit()
    db.refresh(merchant)

    store = Store(
        merchant_id=merchant.id,
        store_code=f"ADJ_{uid.upper()}",
        subdomain=f"adj{uid}",
        name=f"Adjust Store {uid}",
        is_active=True,
        is_verified=True,
    )
    db.add(store)
    db.commit()
    db.refresh(store)

    # Staff user (not merchant_owner)
    staff = User(
        email=f"adjstaff_{uid}@test.com",
        username=f"adjstaff_{uid}",
        hashed_password=auth.hash_password("storepass123"),
        role="store_member",
        is_active=True,
        is_email_verified=True,
    )
    db.add(staff)
    db.commit()
    db.refresh(staff)

    store_user = StoreUser(store_id=store.id, user_id=staff.id, is_active=True)
    db.add(store_user)
    db.commit()

    sp = StorePlatform(store_id=store.id, platform_id=loyalty_platform.id)
    db.add(sp)
    db.commit()

    customer = Customer(
        email=f"adjcust_{uid}@test.com",
        first_name="Adj",
        last_name="Customer",
        hashed_password="!unused!",  # noqa: SEC001
        customer_number=f"AC-{uid.upper()}",
        store_id=store.id,
        is_active=True,
    )
    db.add(customer)
    db.commit()
    db.refresh(customer)

    program = LoyaltyProgram(
        merchant_id=merchant.id,
        loyalty_type=LoyaltyType.POINTS.value,
        points_per_euro=10,
        cooldown_minutes=0,
        max_daily_stamps=10,
        require_staff_pin=False,
        card_name="Adjust Card",
        card_color="#FF0000",
        is_active=True,
    )
    db.add(program)
    db.commit()
    db.refresh(program)

    card = LoyaltyCard(
        merchant_id=merchant.id,
        program_id=program.id,
        customer_id=customer.id,
        enrolled_at_store_id=store.id,
        card_number=f"ADJCARD-{uid.upper()}",
        points_balance=500,
        total_points_earned=500,
        is_active=True,
        last_activity_at=datetime.now(UTC),
    )
    db.add(card)
    db.commit()
    db.refresh(card)

    return {
        "staff": staff,
        "merchant": merchant,
        "store": store,
        "program": program,
        "card": card,
    }


@pytest.fixture
def staff_headers(client, staff_user_setup):
    """JWT auth headers for staff user (not merchant_owner)."""
    staff = staff_user_setup["staff"]
    response = client.post(
        "/api/v1/store/auth/login",
        json={"email_or_username": staff.username, "password": "storepass123"},
    )
    assert response.status_code == 200
    token = response.json()["access_token"]
    return {"Authorization": f"Bearer {token}"}


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestAdjustPointsRoleGate:
    """Tests for adjust_points merchant_owner role requirement (Item 3)."""

    def test_staff_cannot_adjust_points(
        self, client, staff_headers, staff_user_setup
    ):
        """Staff (non-owner) gets 403 when adjusting points."""
        card = staff_user_setup["card"]
        response = client.post(
            f"{BASE}/cards/{card.id}/points/adjust",
            json={"points_delta": 100, "reason": "Staff trying to adjust"},
            headers=staff_headers,
        )
        assert response.status_code == 403

    def test_owner_can_adjust_points(
        self, client, loyalty_store_headers, loyalty_store_setup
    ):
        """Merchant owner can adjust points."""
        card = loyalty_store_setup["card"]
        response = client.post(
            f"{BASE}/cards/{card.id}/points/adjust",
            json={"points_delta": 50, "reason": "Owner adjustment test"},
            headers=loyalty_store_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert data["success"] is True


# ============================================================================
# Item 4: Cross-store enrollment
# ============================================================================


@pytest.fixture
def cross_store_setup(db, loyalty_platform):
    """Setup with two stores under the same merchant for cross-store tests.

    Creates: merchant → store1 + store2 (each with its own user),
    program, customer at store1 with card.
    """
    from app.modules.customers.models.customer import Customer
    from app.modules.tenancy.models import Merchant, Store
    from app.modules.tenancy.models.store import StoreUser
    from app.modules.tenancy.models.store_platform import StorePlatform
    from middleware.auth import AuthManager

    auth = AuthManager()
    uid = uuid.uuid4().hex[:8]

    # Owner for store1
    owner1 = User(
        email=f"xs1own_{uid}@test.com",
        username=f"xs1own_{uid}",
        hashed_password=auth.hash_password("storepass123"),
        role="merchant_owner",
        is_active=True,
        is_email_verified=True,
    )
    db.add(owner1)
    db.commit()
    db.refresh(owner1)

    merchant = Merchant(
        name=f"Cross-Store Merchant {uid}",
        owner_user_id=owner1.id,
        contact_email=owner1.email,
        is_active=True,
        is_verified=True,
    )
    db.add(merchant)
    db.commit()
    db.refresh(merchant)

    # Store 1
    store1 = Store(
        merchant_id=merchant.id,
        store_code=f"XS1_{uid.upper()}",
        subdomain=f"xs1{uid}",
        name=f"Cross Store 1 {uid}",
        is_active=True,
        is_verified=True,
    )
    db.add(store1)
    db.commit()
    db.refresh(store1)

    su1 = StoreUser(store_id=store1.id, user_id=owner1.id, is_active=True)
    db.add(su1)
    sp1 = StorePlatform(store_id=store1.id, platform_id=loyalty_platform.id)
    db.add(sp1)
    db.commit()

    # Separate user for store2 (login always binds to user's first store)
    owner2 = User(
        email=f"xs2own_{uid}@test.com",
        username=f"xs2own_{uid}",
        hashed_password=auth.hash_password("storepass123"),
        role="merchant_owner",
        is_active=True,
        is_email_verified=True,
    )
    db.add(owner2)
    db.commit()
    db.refresh(owner2)

    # Store 2
    store2 = Store(
        merchant_id=merchant.id,
        store_code=f"XS2_{uid.upper()}",
        subdomain=f"xs2{uid}",
        name=f"Cross Store 2 {uid}",
        is_active=True,
        is_verified=True,
    )
    db.add(store2)
    db.commit()
    db.refresh(store2)

    su2 = StoreUser(store_id=store2.id, user_id=owner2.id, is_active=True)
    db.add(su2)
    sp2 = StorePlatform(store_id=store2.id, platform_id=loyalty_platform.id)
    db.add(sp2)
    db.commit()

    # Customer at store1
    customer_email = f"xscust_{uid}@test.com"
    customer = Customer(
        email=customer_email,
        first_name="Cross",
        last_name="StoreCustomer",
        hashed_password="!unused!",  # noqa: SEC001
        customer_number=f"XSC-{uid.upper()}",
        store_id=store1.id,
        is_active=True,
    )
    db.add(customer)
    db.commit()
    db.refresh(customer)

    # Program
    program = LoyaltyProgram(
        merchant_id=merchant.id,
        loyalty_type=LoyaltyType.POINTS.value,
        points_per_euro=10,
        welcome_bonus_points=0,
        minimum_redemption_points=100,
        minimum_purchase_cents=0,
        cooldown_minutes=0,
        max_daily_stamps=10,
        require_staff_pin=False,
        card_name="Cross Store Rewards",
        card_color="#4F46E5",
        is_active=True,
        points_rewards=[],
    )
    db.add(program)
    db.commit()
    db.refresh(program)

    # Card enrolled at store1
    card = LoyaltyCard(
        merchant_id=merchant.id,
        program_id=program.id,
        customer_id=customer.id,
        enrolled_at_store_id=store1.id,
        is_active=True,
        last_activity_at=datetime.now(UTC),
    )
    db.add(card)
    db.commit()
    db.refresh(card)

    return {
        "owner1": owner1,
        "owner2": owner2,
        "merchant": merchant,
        "store1": store1,
        "store2": store2,
        "customer": customer,
        "customer_email": customer_email,
        "program": program,
        "card": card,
    }


@pytest.fixture
def cross_store_headers_store2(client, cross_store_setup):
    """JWT auth headers bound to store2 (via owner2 who only belongs to store2)."""
    owner2 = cross_store_setup["owner2"]
    response = client.post(
        "/api/v1/store/auth/login",
        json={"email_or_username": owner2.username, "password": "storepass123"},
    )
    assert response.status_code == 200
    token = response.json()["access_token"]
    return {"Authorization": f"Bearer {token}"}


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestCrossStoreEnrollment:
    """Integration tests for enrollment across stores under the same merchant."""

    def test_enroll_same_email_at_store2_returns_409(
        self, client, cross_store_headers_store2, cross_store_setup
    ):
        """With cross-location enabled (default), enrolling the same email
        at store2 returns 409 because the customer already has a card under
        this merchant."""
        response = client.post(
            f"{BASE}/cards/enroll",
            json={"email": cross_store_setup["customer_email"]},
            headers=cross_store_headers_store2,
        )
        assert response.status_code == 409

    def test_enroll_new_customer_at_store2_succeeds(
        self, client, cross_store_headers_store2, cross_store_setup, db
    ):
        """A fresh customer at store2 (no existing card) enrolls normally."""
        from app.modules.customers.models.customer import Customer

        # Pre-create a customer at store2 (store API requires existing customer)
        store2 = cross_store_setup["store2"]
        new_customer = Customer(
            email=f"newcust_{uuid.uuid4().hex[:8]}@test.com",
            first_name="New",
            last_name="Customer",
            hashed_password="!unused!",  # noqa: SEC001
            customer_number=f"NEW-{uuid.uuid4().hex[:6].upper()}",
            store_id=store2.id,
            is_active=True,
        )
        db.add(new_customer)
        db.commit()
        db.refresh(new_customer)

        response = client.post(
            f"{BASE}/cards/enroll",
            json={"customer_id": new_customer.id},
            headers=cross_store_headers_store2,
        )
        assert response.status_code == 201
        data = response.json()
        assert data["card_number"] is not None

    def test_enroll_cross_location_disabled_allows_store2_card(
        self, client, cross_store_headers_store2, cross_store_setup, db
    ):
        """With cross-location disabled, enrolling the same email at store2
        creates a second card for that store."""
        # Disable cross-location
        from app.modules.loyalty.services.program_service import program_service

        settings = program_service.get_or_create_merchant_settings(
            db, cross_store_setup["merchant"].id
        )
        settings.allow_cross_location_redemption = False
        db.commit()

        response = client.post(
            f"{BASE}/cards/enroll",
            json={"email": cross_store_setup["customer_email"]},
            headers=cross_store_headers_store2,
        )
        assert response.status_code == 201
        data = response.json()
        assert data["card_number"] is not None
        # Different card from the original
        assert data["id"] != cross_store_setup["card"].id
