"""/api/v1/coupon/step 透传端点测试。

mock 掉对 pricebot 的 httpx 调用,验证:
  1. 无 token → 401
  2. 带 token + pricebot 200 → 响应原样透传,请求 body 原样转发
  3. pricebot 5xx → 502
  4. pricebot 网络错误 → 502
"""
from __future__ import annotations

import time
from unittest.mock import MagicMock, patch

import httpx
import pytest


def _stub_screen_state() -> dict:
    """最简的 screen_state(领券业务用,跟外卖/电商同结构)。"""
    return {
        "screen": {"width": 1080, "height": 2340, "density": 3.0},
        "foreground": {"package": "x", "activity": ""},
        "windows": [],
    }


def _stub_request_body() -> dict:
    return {
        "device_id": "test-device",
        "trace_id": "test-trace-1",
        "step": 0,
        "screen_state": _stub_screen_state(),
    }


@pytest.fixture()
def access_token(client) -> str:
    """sms 登录拿一个 access_token。每次新 phone 避免冷却 429。"""
    phone = f"139{int(time.time() * 1000) % 100000000:08d}"
    r = client.post("/api/v1/auth/sms/send", json={"phone": phone})
    assert r.status_code == 200, r.text
    r = client.post("/api/v1/auth/sms/login", json={"phone": phone, "code": "123456"})
    assert r.status_code == 200, r.text
    return r.json()["access_token"]


def test_coupon_step_no_auth_required(client) -> None:
    """MVP 不鉴权:不带 token 也能转发(已去掉 CurrentUser,device_id 透传)。

    历史:本测试原断言"无 token → 401",但 coupon/step 已去鉴权(见 docs/待办与
    技术债.md「已解决」),401 断言已过时,改为验证不带 token 也能正常透传。
    """
    async def fake_post(self, url, json=None, **kw):
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json = lambda: {"success": True}
        return mock_resp

    with patch.object(httpx.AsyncClient, "post", fake_post):
        r = client.post("/api/v1/coupon/step", json=_stub_request_body())
    assert r.status_code == 200, r.text


def test_coupon_step_passes_body_through(client, access_token) -> None:
    """带 token + pricebot 200 → 响应原样透传,请求 body 原样转发到 /api/coupon/step。"""
    fake_pricebot_resp = {
        "success": True,
        "action": {
            "command": "launch",
            "params": {"app": "美团", "deeplink": "imeituan://test"},
        },
        "continue": True,
        "status": {
            "platform": "coupon",
            "progress": {
                "current": 1,
                "total": 2,
                "current_coupon_id": "mt_banjia",
                "current_coupon_name": "美团半价周末",
            },
        },
    }

    captured: dict = {}

    async def fake_post(self, url, json=None, **kw):
        captured["url"] = url
        captured["json"] = json
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json = lambda: fake_pricebot_resp
        return mock_resp

    with patch.object(httpx.AsyncClient, "post", fake_post):
        r = client.post(
            "/api/v1/coupon/step",
            headers={"Authorization": f"Bearer {access_token}"},
            json=_stub_request_body(),
        )

    assert r.status_code == 200, r.text
    assert r.json() == fake_pricebot_resp
    # 验证请求被原样转发(body 不动 + URL 指向 pricebot)
    assert captured["json"] == _stub_request_body()
    assert captured["url"].endswith("/api/coupon/step")


def test_coupon_step_pricebot_5xx_returns_502(client, access_token) -> None:
    """pricebot 返 503 → 我们返 502。"""
    async def fake_post(self, url, json=None, **kw):
        mock_resp = MagicMock()
        mock_resp.status_code = 503
        mock_resp.text = "service unavailable"
        return mock_resp

    with patch.object(httpx.AsyncClient, "post", fake_post):
        r = client.post(
            "/api/v1/coupon/step",
            headers={"Authorization": f"Bearer {access_token}"},
            json=_stub_request_body(),
        )

    assert r.status_code == 502
    assert "pricebot upstream returned 503" in r.json()["detail"]


def test_coupon_step_pricebot_unreachable_returns_502(client, access_token) -> None:
    """pricebot 网络不可达 → 502。"""
    async def fake_post(self, url, json=None, **kw):
        raise httpx.ConnectError("connection refused")

    with patch.object(httpx.AsyncClient, "post", fake_post):
        r = client.post(
            "/api/v1/coupon/step",
            headers={"Authorization": f"Bearer {access_token}"},
            json=_stub_request_body(),
        )

    assert r.status_code == 502
    assert "pricebot upstream unreachable" in r.json()["detail"]


def test_coupon_step_invalid_json_body(client, access_token) -> None:
    """非 JSON body → 400(在转发前就拒绝)。"""
    r = client.post(
        "/api/v1/coupon/step",
        headers={
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        },
        content=b"not-json",
    )
    assert r.status_code == 400
    assert "invalid json body" in r.json()["detail"]
