# app/modules/billing/tests/integration/test_merchant_routes.py
"""
Integration tests for merchant billing API routes.

Tests the merchant portal billing endpoints at:
    /api/v1/merchants/billing/*

Authentication: Overrides get_current_merchant_from_cookie_or_header with a
mock that returns a UserContext for the merchant owner.
"""

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

import pytest

from app.api.deps import get_current_merchant_api, get_merchant_for_current_user
from app.modules.billing.models import (
    BillingHistory,
    MerchantSubscription,
    SubscriptionStatus,
    SubscriptionTier,
)
from app.modules.tenancy.models import Merchant, Platform, User
from app.modules.tenancy.schemas.auth import UserContext
from main import app

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

BASE = "/api/v1/merchants/billing"


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

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


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


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


@pytest.fixture
def merch_tiers(db, merch_platform):
    """Create tiers for merchant 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=merch_platform.id,
        )
        db.add(tier)
        tiers.append(tier)
    db.commit()
    for t in tiers:
        db.refresh(t)
    return tiers


@pytest.fixture
def merch_subscription(db, merch_merchant, merch_platform, merch_tiers):
    """Create a subscription for the merchant."""
    sub = MerchantSubscription(
        merchant_id=merch_merchant.id,
        platform_id=merch_platform.id,
        tier_id=merch_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 merch_invoices(db, merch_merchant):
    """Create invoice records for the merchant."""
    records = []
    for i in range(3):
        record = BillingHistory(
            merchant_id=merch_merchant.id,
            invoice_number=f"MINV-{2000 + 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"Merchant invoice {i}",
        )
        db.add(record)  # noqa: PERF006
        records.append(record)
    db.commit()
    for r in records:
        db.refresh(r)
    return records


@pytest.fixture
def merch_auth_headers(merch_owner, merch_merchant):
    """Override auth dependencies to return merchant/user for the merchant owner."""
    user_context = UserContext(
        id=merch_owner.id,
        email=merch_owner.email,
        username=merch_owner.username,
        role="merchant_owner",
        is_active=True,
    )

    def _override_merchant():
        return merch_merchant

    def _override_user():
        return user_context

    app.dependency_overrides[get_merchant_for_current_user] = _override_merchant
    app.dependency_overrides[get_current_merchant_api] = _override_user
    yield {"Authorization": "Bearer fake-token"}
    app.dependency_overrides.pop(get_merchant_for_current_user, None)
    app.dependency_overrides.pop(get_current_merchant_api, None)


# ============================================================================
# Subscription Endpoints
# ============================================================================


class TestMerchantListSubscriptions:
    """Tests for GET /api/v1/merchants/billing/subscriptions."""

    def test_list_subscriptions_success(
        self, client, merch_auth_headers, merch_subscription, merch_merchant
    ):
        response = client.get(
            f"{BASE}/subscriptions", headers=merch_auth_headers
        )
        assert response.status_code == 200
        data = response.json()
        assert "subscriptions" in data
        assert "total" in data
        assert data["total"] >= 1

    def test_list_subscriptions_includes_tier_info(
        self, client, merch_auth_headers, merch_subscription
    ):
        response = client.get(
            f"{BASE}/subscriptions", headers=merch_auth_headers
        )
        assert response.status_code == 200
        sub = response.json()["subscriptions"][0]
        assert "tier" in sub
        assert "tier_name" in sub
        assert sub["tier"] == "professional"

    def test_list_subscriptions_empty(
        self, client, merch_auth_headers, merch_merchant
    ):
        response = client.get(
            f"{BASE}/subscriptions", headers=merch_auth_headers
        )
        assert response.status_code == 200
        assert response.json()["total"] == 0


class TestMerchantGetSubscription:
    """Tests for GET /api/v1/merchants/billing/subscriptions/{platform_id}."""

    def test_get_subscription_success(
        self,
        client,
        merch_auth_headers,
        merch_subscription,
        merch_platform,
    ):
        response = client.get(
            f"{BASE}/subscriptions/{merch_platform.id}",
            headers=merch_auth_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert "subscription" in data
        assert "tier" in data
        assert data["subscription"]["status"] == "active"

    def test_get_subscription_with_tier_details(
        self,
        client,
        merch_auth_headers,
        merch_subscription,
        merch_platform,
    ):
        response = client.get(
            f"{BASE}/subscriptions/{merch_platform.id}",
            headers=merch_auth_headers,
        )
        assert response.status_code == 200
        tier = response.json()["tier"]
        assert tier is not None
        assert tier["code"] == "professional"
        assert tier["price_monthly_cents"] == 2900

    def test_get_subscription_not_found(
        self, client, merch_auth_headers, merch_merchant
    ):
        response = client.get(
            f"{BASE}/subscriptions/99999",
            headers=merch_auth_headers,
        )
        assert response.status_code == 404


class TestMerchantGetAvailableTiers:
    """Tests for GET /api/v1/merchants/billing/subscriptions/{platform_id}/tiers."""

    def test_get_tiers_success(
        self,
        client,
        merch_auth_headers,
        merch_subscription,
        merch_platform,
        merch_tiers,
    ):
        response = client.get(
            f"{BASE}/subscriptions/{merch_platform.id}/tiers",
            headers=merch_auth_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert "tiers" in data
        assert "current_tier" in data
        assert data["current_tier"] == "professional"

    def test_get_tiers_includes_upgrade_info(
        self,
        client,
        merch_auth_headers,
        merch_subscription,
        merch_platform,
        merch_tiers,
    ):
        response = client.get(
            f"{BASE}/subscriptions/{merch_platform.id}/tiers",
            headers=merch_auth_headers,
        )
        assert response.status_code == 200
        tiers = response.json()["tiers"]
        assert len(tiers) >= 3


class TestMerchantChangeTier:
    """Tests for POST /api/v1/merchants/billing/subscriptions/{platform_id}/change-tier."""

    @patch("app.modules.billing.routes.api.merchant.billing_service")
    def test_change_tier_success(
        self,
        mock_billing,
        client,
        merch_auth_headers,
        merch_subscription,
        merch_platform,
        merch_tiers,
    ):
        mock_billing.change_tier.return_value = {
            "message": "Tier changed to business",
            "new_tier": "business",
            "effective_immediately": True,
        }

        response = client.post(
            f"{BASE}/subscriptions/{merch_platform.id}/change-tier",
            json={"tier_code": "business", "is_annual": False},
            headers=merch_auth_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert "new_tier" in data or "message" in data
        mock_billing.change_tier.assert_called_once()

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


class TestMerchantCheckout:
    """Tests for POST /api/v1/merchants/billing/subscriptions/{platform_id}/checkout."""

    @patch("app.modules.billing.routes.api.merchant.billing_service")
    def test_create_checkout_with_stripe(
        self,
        mock_billing,
        client,
        merch_auth_headers,
        merch_subscription,
        merch_platform,
        merch_tiers,
    ):
        mock_billing.create_checkout_session.return_value = {
            "checkout_url": "https://checkout.stripe.com/test",
            "session_id": "cs_test_123",
        }

        response = client.post(
            f"{BASE}/subscriptions/{merch_platform.id}/checkout",
            json={"tier_code": "business", "is_annual": False},
            headers=merch_auth_headers,
        )
        assert response.status_code == 200
        data = response.json()
        assert data["checkout_url"] == "https://checkout.stripe.com/test"
        assert data["session_id"] == "cs_test_123"
        mock_billing.create_checkout_session.assert_called_once()


# ============================================================================
# Invoice Endpoints
# ============================================================================


class TestMerchantInvoices:
    """Tests for GET /api/v1/merchants/billing/invoices."""

    def test_list_invoices_success(
        self, client, merch_auth_headers, merch_invoices
    ):
        response = client.get(
            f"{BASE}/invoices", headers=merch_auth_headers
        )
        assert response.status_code == 200
        data = response.json()
        assert "invoices" in data
        assert "total" in data
        assert data["total"] >= 3

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

    def test_list_invoices_response_shape(
        self, client, merch_auth_headers, merch_invoices
    ):
        response = client.get(
            f"{BASE}/invoices", headers=merch_auth_headers
        )
        assert response.status_code == 200
        inv = response.json()["invoices"][0]
        assert "id" in inv
        assert "invoice_number" in inv
        assert "invoice_date" in inv
        assert "total_cents" in inv
        assert "currency" in inv
        assert "status" in inv

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