# app/modules/billing/tests/integration/test_store_routes.py
"""
Integration tests for store billing API routes.

Tests the store billing endpoints at:
    /api/v1/store/billing/*

Authentication: Uses real JWT tokens via store login endpoint.
The store routes require `require_module_access("billing", FrontendType.STORE)`
as a router-level dependency, which calls auth functions directly — so
dependency overrides don't work. Real tokens are needed.
"""

import uuid
from datetime import UTC, datetime, timedelta
from unittest.mock import patch

import pytest

from app.modules.billing.models import (
    BillingHistory,
    MerchantSubscription,
    SubscriptionStatus,
    SubscriptionTier,
)
from app.modules.tenancy.models import Merchant, Platform, Store, User
from app.modules.tenancy.models.store import StoreUser
from app.modules.tenancy.models.store_platform import StorePlatform

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

BASE = "/api/v1/store/billing"


@pytest.fixture
def store_platform(db):
    """Create a platform for store tests."""
    platform = Platform(
        code=f"store_{uuid.uuid4().hex[:8]}",
        name="Store Test Platform",
        is_active=True,
    )
    db.add(platform)
    db.commit()
    db.refresh(platform)
    return platform


@pytest.fixture
def store_full_setup(db, store_platform):
    """
    Create the full store setup: user, merchant, store, StoreUser, StorePlatform.

    Returns a dict with all created objects for easy access.
    """
    from middleware.auth import AuthManager

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

    # 1. Create store owner user
    owner = User(
        email=f"storeowner_{uid}@test.com",
        username=f"storeowner_{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)

    # 2. Create merchant
    merchant = Merchant(
        name=f"Store 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)

    # 3. Create store
    store = Store(
        merchant_id=merchant.id,
        store_code=f"STORETEST_{uid.upper()}",
        subdomain=f"storetest{uid}",
        name=f"Store Test {uid}",
        is_active=True,
        is_verified=True,
    )
    db.add(store)
    db.commit()
    db.refresh(store)

    # 4. Create StoreUser (ownership determined via Merchant.owner_user_id)
    store_user = StoreUser(
        store_id=store.id,
        user_id=owner.id,
        is_active=True,
    )
    db.add(store_user)
    db.commit()

    # 5. Link store to platform
    sp = StorePlatform(
        store_id=store.id,
        platform_id=store_platform.id,
    )
    db.add(sp)
    db.commit()

    return {
        "owner": owner,
        "merchant": merchant,
        "store": store,
        "platform": store_platform,
    }


@pytest.fixture
def store_auth_headers(client, store_full_setup):
    """
    Get real JWT auth headers by logging in via store auth endpoint.

    Uses the store login flow which generates a JWT with store context
    (token_store_id, token_store_code, token_store_role).
    """
    owner = store_full_setup["owner"]
    response = client.post(
        "/api/v1/store/auth/login",
        json={
            "email_or_username": owner.username,
            "password": "storepass123",
        },
    )
    assert response.status_code == 200, f"Store login failed: {response.text}"
    token = response.json()["access_token"]
    return {"Authorization": f"Bearer {token}"}


@pytest.fixture
def store_tiers(db, store_platform):
    """Create tiers for store tests."""
    tiers = []
    for i, (code, name, price) in enumerate([
        ("essential", "Essential", 0),
        ("professional", "Professional", 2900),
        ("business", "Business", 7900),
    ]):
        tier = SubscriptionTier(
            code=code,
            name=name,
            description=f"{name} tier",
            price_monthly_cents=price,
            price_annual_cents=price * 10 if price > 0 else 0,
            display_order=i,
            is_active=True,
            is_public=True,
            platform_id=store_platform.id,
        )
        db.add(tier)
        tiers.append(tier)
    db.commit()
    for t in tiers:
        db.refresh(t)
    return tiers


@pytest.fixture
def store_subscription(db, store_full_setup, store_tiers):
    """Create a subscription for the store's merchant."""
    sub = MerchantSubscription(
        merchant_id=store_full_setup["merchant"].id,
        platform_id=store_full_setup["platform"].id,
        tier_id=store_tiers[1].id,  # professional
        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 store_invoices(db, store_full_setup):
    """Create invoice records for the store's merchant."""
    records = []
    for i in range(2):
        record = BillingHistory(
            merchant_id=store_full_setup["merchant"].id,
            invoice_number=f"SINV-{3000 + i}",
            invoice_date=datetime.now(UTC) - timedelta(days=30 * i),
            subtotal_cents=2900,
            tax_cents=493,
            total_cents=3393,
            amount_paid_cents=3393,
            currency="EUR",
            status="paid",
            description=f"Store invoice {i}",
        )
        db.add(record)  # noqa: PERF006
        records.append(record)
    db.commit()
    for r in records:
        db.refresh(r)
    return records


# ============================================================================
# Core Billing Endpoints
# ============================================================================


class TestStoreGetSubscription:
    """Tests for GET /api/v1/store/billing/subscription."""

    def test_get_subscription_success(
        self, client, store_auth_headers, store_subscription, store_tiers
    ):
        response = client.get(
            f"{BASE}/subscription", headers=store_auth_headers
        )
        assert response.status_code == 200
        data = response.json()
        assert "tier_code" in data
        assert "tier_name" in data
        assert "status" in data
        assert data["status"] == "active"
        assert data["tier_code"] == "professional"


class TestStoreGetTiers:
    """Tests for GET /api/v1/store/billing/tiers."""

    def test_get_tiers_success(
        self, client, store_auth_headers, store_subscription, store_tiers
    ):
        response = client.get(f"{BASE}/tiers", headers=store_auth_headers)
        assert response.status_code == 200
        data = response.json()
        assert "tiers" in data
        assert "current_tier" in data
        assert data["current_tier"] == "professional"
        assert len(data["tiers"]) >= 3


class TestStoreGetInvoices:
    """Tests for GET /api/v1/store/billing/invoices."""

    def test_get_invoices_success(
        self, client, store_auth_headers, store_invoices
    ):
        response = client.get(f"{BASE}/invoices", headers=store_auth_headers)
        assert response.status_code == 200
        data = response.json()
        assert "invoices" in data
        assert "total" in data
        assert data["total"] >= 2

    def test_get_invoices_pagination(
        self, client, store_auth_headers, store_invoices
    ):
        response = client.get(
            f"{BASE}/invoices",
            params={"skip": 0, "limit": 1},
            headers=store_auth_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert len(data["invoices"]) <= 1

    def test_get_invoices_empty(
        self, client, store_auth_headers, store_full_setup
    ):
        response = client.get(f"{BASE}/invoices", headers=store_auth_headers)
        assert response.status_code == 200
        assert response.json()["total"] == 0


# ============================================================================
# Checkout / Management Endpoints
# ============================================================================


class TestStoreChangeTier:
    """Tests for POST /api/v1/store/billing/change-tier."""

    @patch("app.modules.billing.routes.api.store_checkout.billing_service")
    def test_change_tier_success(
        self, mock_billing, client, store_auth_headers, store_subscription, store_tiers
    ):
        mock_billing.change_tier.return_value = {
            "message": "Tier changed to business",
            "new_tier": "business",
            "effective_immediately": True,
        }
        response = client.post(
            f"{BASE}/change-tier",
            json={"tier_code": "business", "is_annual": False},
            headers=store_auth_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert "message" in data
        assert "new_tier" in data

    def test_change_tier_no_stripe_returns_error(
        self, client, store_auth_headers, store_subscription
    ):
        """Without Stripe subscription, change_tier returns 400."""
        response = client.post(
            f"{BASE}/change-tier",
            json={"tier_code": "business", "is_annual": False},
            headers=store_auth_headers,
        )
        assert response.status_code == 400


class TestStoreCancelSubscription:
    """Tests for POST /api/v1/store/billing/cancel."""

    @patch("app.modules.billing.routes.api.store_checkout.billing_service")
    def test_cancel_success(
        self, mock_billing, client, store_auth_headers, store_subscription
    ):
        mock_billing.cancel_subscription.return_value = {
            "message": "Subscription cancelled",
            "effective_date": (datetime.now(UTC) + timedelta(days=30)).isoformat(),
        }
        response = client.post(
            f"{BASE}/cancel",
            json={"reason": "Too expensive", "immediately": False},
            headers=store_auth_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert "message" in data
        assert "effective_date" in data


class TestStoreReactivateSubscription:
    """Tests for POST /api/v1/store/billing/reactivate."""

    @patch("app.modules.billing.routes.api.store_checkout.billing_service")
    def test_reactivate_cancelled(
        self, mock_billing, client, store_auth_headers, store_subscription, db
    ):
        store_subscription.status = SubscriptionStatus.CANCELLED.value
        store_subscription.cancelled_at = datetime.now(UTC)
        db.commit()

        mock_billing.reactivate_subscription.return_value = {
            "message": "Subscription reactivated",
            "status": "active",
        }

        response = client.post(
            f"{BASE}/reactivate", headers=store_auth_headers
        )
        assert response.status_code == 200


class TestStoreUpcomingInvoice:
    """Tests for GET /api/v1/store/billing/upcoming-invoice."""

    @patch("app.modules.billing.routes.api.store_checkout.billing_service")
    def test_upcoming_invoice(
        self, mock_billing, client, store_auth_headers, store_subscription
    ):
        mock_billing.get_upcoming_invoice.return_value = {
            "amount_due_cents": 2900,
            "currency": "EUR",
            "next_payment_date": None,
            "line_items": [],
        }

        response = client.get(
            f"{BASE}/upcoming-invoice", headers=store_auth_headers
        )
        assert response.status_code == 200
        data = response.json()
        assert "amount_due_cents" in data
        assert "currency" in data


# ============================================================================
# Unauthorized Access
# ============================================================================


class TestStoreUnauthorized:
    """Tests for unauthorized store access."""

    def test_subscription_no_auth(self, client):
        response = client.get(f"{BASE}/subscription")
        assert response.status_code in (401, 403)

    def test_tiers_no_auth(self, client):
        response = client.get(f"{BASE}/tiers")
        assert response.status_code in (401, 403)

    def test_invoices_no_auth(self, client):
        response = client.get(f"{BASE}/invoices")
        assert response.status_code in (401, 403)
