# ============================================================
# C端消费者 — 全量端到端测试
# 覆盖 19 个 Controller，约 60 个 API 端点
# ============================================================
import pytest
import json
import random
from api_client import ApiClient
from config import TEST_PASSWORD, KEEP_TEST_DATA


# ============================================================
# 首页浏览 (HomeAppController) — 无需登录
# ============================================================
class TestConsumerHome:

    def test_45_home_banners(self, client, admin_token):
        """首页Banner列表"""
        client.clear_token()
        resp = client.get("/api/home/banners")
        client.assert_ok("查询Banner失败")
        banners = client.extract_data() or []
        print(f"  [OK] Banner count={len(banners)}")

    def test_46_home_categories(self, client):
        """首页分类导航"""
        resp = client.get("/api/home/categories")
        client.assert_ok("查询首页分类失败")
        cats = client.extract_data() or []
        print(f"  [OK] 首页分类 count={len(cats)}")

    def test_47_home_hot_goods(self, client):
        """热销商品"""
        resp = client.get("/api/home/hot-goods")
        client.assert_ok("查询热销商品失败")
        goods = client.extract_data() or []
        print(f"  [OK] 热销商品 count={len(goods)}")
        if goods:
            client._test_hot_goods_id = goods[0].get("goodsId") or goods[0].get("id")


# ============================================================
# 搜索 (SearchAppController)
# ============================================================
class TestConsumerSearch:

    def test_48_search(self, client):
        """全站搜索"""
        resp = client.get("/api/search", params={"keyword": "测试"})
        client.assert_ok("搜索失败")
        result = client.extract_data() or {}
        print(f"  [OK] 搜索正常: {json.dumps(result, ensure_ascii=False)[:100]}")

    def test_49_search_empty(self, client):
        """搜索空关键词"""
        resp = client.get("/api/search", params={"keyword": ""})
        client.assert_ok("空搜索失败")
        print(f"  [OK] 空搜索正常")


# ============================================================
# 商品详情 (GoodsAppController)
# ============================================================
class TestConsumerGoods:

    def test_50_goods_detail(self, client):
        """商品详情"""
        gid = getattr(client, '_test_hot_goods_id', None)
        if not gid:
            resp = client.get("/api/home/hot-goods")
            data = client.assert_ok()
            goods = data.get("data", [])
            if not goods:
                pytest.skip("无在售商品")
            gid = goods[0].get("goodsId") or goods[0].get("id")
        resp = client.get(f"/api/goods/{gid}")
        client.assert_ok("查询商品详情失败")
        detail = client.extract_data() or {}
        print(f"  [OK] 商品详情: {detail.get('goodsName','')[:30]}")
        client._test_goods_id = gid

    def test_51_goods_can_purchase(self, client):
        """商品可购校验"""
        gid = getattr(client, '_test_goods_id', None)
        if not gid:
            pytest.skip("无商品ID")
        resp = client.get(f"/api/goods/{gid}/can-purchase")
        client.assert_ok("查询可购校验失败")
        result = client.extract_data() or {}
        print(f"  [OK] 可购校验: {result}")

    def test_52_goods_reviews(self, client):
        """商品评价列表"""
        gid = getattr(client, '_test_goods_id', None)
        if not gid:
            pytest.skip("无商品ID")
        resp = client.get(f"/api/goods/{gid}/reviews")
        client.assert_ok("查询评价失败")
        data = client.extract_data() or {}
        total = data.get("total", 0) if isinstance(data, dict) else 0
        print(f"  [OK] 商品评价 total={total}")


# ============================================================
# 店铺 (ShopAppController)
# ============================================================
class TestConsumerShop:

    def test_53_shop_profile(self, client):
        """店铺主页"""
        # 从首页热销商品中找店铺ID
        resp = client.get("/api/home/hot-goods")
        data = client.assert_ok()
        goods = data.get("data", [])
        shop_id = None
        for g in goods:
            sid = g.get("shopId")
            if sid:
                shop_id = sid
                break
        if not shop_id:
            pytest.skip("无店铺数据")
        client._test_shop_id = shop_id
        resp = client.get(f"/api/shop/{shop_id}")
        client.assert_ok("查询店铺主页失败")
        profile = client.extract_data() or {}
        print(f"  [OK] 店铺主页: {profile.get('shopName','')[:20] if isinstance(profile, dict) else 'ok'}")

    def test_54_shop_categories(self, client):
        """店铺分类"""
        sid = getattr(client, '_test_shop_id', None)
        if not sid:
            pytest.skip("无店铺ID")
        resp = client.get(f"/api/shop/{sid}/categories")
        client.assert_ok("查询店铺分类失败")
        cats = client.extract_data() or []
        print(f"  [OK] 店铺分类 count={len(cats)}")

    def test_55_shop_goods(self, client):
        """店铺商品列表"""
        sid = getattr(client, '_test_shop_id', None)
        if not sid:
            pytest.skip("无店铺ID")
        resp = client.get(f"/api/shop/{sid}/goods")
        client.assert_ok("查询店铺商品失败")
        data = client.extract_data() or {}
        print(f"  [OK] 店铺商品列表正常")


# ============================================================
# C端分类 (CategoryAppController)
# ============================================================
class TestConsumerCategory:

    def test_56_category_tree(self, client):
        """C端分类树"""
        resp = client.get("/api/category/tree")
        client.assert_ok("查询分类树失败")
        tree = client.extract_data() or []
        print(f"  [OK] C端分类树 nodes={len(tree)}")


# ============================================================
# C端地区 (RegionAppController)
# ============================================================
class TestConsumerRegion:

    def test_57_region_tree(self, client):
        """C端地区树"""
        resp = client.get("/api/region/tree")
        client.assert_ok("查询地区树失败")
        tree = client.extract_data() or []
        print(f"  [OK] C端地区树 nodes={len(tree)}")


# ============================================================
# 商城配置 (MallConfigAppController)
# ============================================================
class TestConsumerMallConfig:

    def test_58_mall_config(self, client):
        """商城配置"""
        resp = client.get("/api/mall/config")
        client.assert_ok("查询商城配置失败")
        config = client.extract_data() or {}
        print(f"  [OK] 商城配置: {json.dumps(config, ensure_ascii=False)[:120]}")


# ============================================================
# 协议查询 (MallServiceAgreement + MerchantEntryAgreement)
# ============================================================
class TestConsumerAgreement:

    def test_59_service_agreement_status(self, client):
        """服务协议状态"""
        resp = client.get("/api/member/serviceAgreement/status")
        client.assert_ok("查询服务协议状态失败")
        status = client.extract_data() or {}
        print(f"  [OK] 服务协议状态: {status}")

    def test_60_entry_agreement(self, client):
        """入驻协议内容"""
        resp = client.get("/api/merchant/entry/agreement")
        client.assert_ok("查询入驻协议失败")
        agmt = client.extract_data() or {}
        print(f"  [OK] 入驻协议: {str(agmt)[:80]}")

    def test_61_entry_agreement_status(self, client):
        """入驻协议状态"""
        resp = client.get("/api/merchant/entry/status")
        client.assert_ok("查询入驻协议状态失败")
        status = client.extract_data() or {}
        print(f"  [OK] 入驻协议状态: {status}")


# ============================================================
# 会员注册登录 (MemberAppController)
# ============================================================
class TestConsumerMember:

    @pytest.fixture(autouse=True)
    def setup_member(self, client, member_token):
        """复用 conftest 中的 member_token fixture"""
        pass

    def test_62_member_profile(self, client, member_token):
        """会员基本信息"""
        resp = client.get("/api/member/profile")
        client.assert_ok("查询会员信息失败")
        profile = client.extract_data() or {}
        print(f"  [OK] 会员信息: {profile.get('nickName','')[:20]}, mobile={profile.get('mobile','')}")

    def test_63_member_update_profile(self, client, member_token):
        """会员更新信息"""
        resp = client.put("/api/member/profile", json={
            "nickName": f"E2E用户_{random.randint(100,999)}"
        })
        client.assert_ok("更新会员信息失败")
        print(f"  [OK] 会员信息更新成功")

    def test_64_member_address_list(self, client, member_token):
        """会员地址列表"""
        resp = client.get("/api/member/address/list")
        client.assert_ok("查询地址列表失败")
        addrs = client.extract_data() or []
        print(f"  [OK] 地址列表 count={len(addrs)}")

    def test_65_member_add_address(self, client, member_token):
        """新增收货地址"""
        resp = client.post("/api/member/address", json={
            "receiverName": "张三",
            "receiverMobile": "13800138000",
            "province": "北京市",
            "city": "北京市",
            "district": "朝阳区",
            "detailAddress": "测试街道100号",
            "fullAddress": "北京市北京市朝阳区测试街道100号",
            "isDefault": True,
        })
        data = client.assert_ok("新增地址失败")
        addr_id = data.get("data")
        client._test_address_id = addr_id
        print(f"  [OK] 新增地址 addressId={addr_id}")

    def test_66_member_set_default_address(self, client, member_token):
        """设置默认地址"""
        addr_id = getattr(client, '_test_address_id', None)
        if not addr_id:
            pytest.skip("无地址ID")
        resp = client.put(f"/api/member/address/{addr_id}/default")
        client.assert_ok("设置默认地址失败")
        print(f"  [OK] 设置默认地址 {addr_id}")

    def test_67_member_update_address(self, client, member_token):
        """更新收货地址"""
        addr_id = getattr(client, '_test_address_id', None)
        if not addr_id:
            pytest.skip("无地址ID")
        resp = client.put("/api/member/address", json={
            "addressId": addr_id,
            "receiverName": "张三(改)",
            "receiverMobile": "13800138000",
            "detailAddress": "测试街道200号",
        })
        client.assert_ok("更新地址失败")
        print(f"  [OK] 更新地址 {addr_id}")


# ============================================================
# 购物车 (CartAppController) — 需登录
# ============================================================
class TestConsumerCart:

    def test_68_cart_list_empty(self, client, member_token):
        """购物车列表（空）"""
        resp = client.get("/api/cart")
        client.assert_ok("查询购物车失败")
        items = client.extract_data() or {}
        print(f"  [OK] 购物车列表正常")

    def test_69_cart_add_item(self, client, member_token):
        """添加商品到购物车"""
        gid = getattr(client, '_test_goods_id', None)
        if not gid:
            resp = client.get("/api/home/hot-goods")
            data = client.assert_ok()
            goods = data.get("data", [])
            if not goods:
                pytest.skip("无商品")
            gid = goods[0].get("goodsId") or goods[0].get("id")
        resp = client.post("/api/cart/items", json={
            "goodsId": gid,
            "quantity": 1,
        })
        client.assert_ok("加购失败")
        print(f"  [OK] 加购成功 goodsId={gid}")

    def test_70_cart_update_quantity(self, client, member_token):
        """更新购物车数量"""
        resp = client.get("/api/cart")
        data = client.assert_ok()
        cart_data = client.extract_data() or {}
        items = []
        if isinstance(cart_data, dict):
            items = cart_data.get("items", []) or cart_data.get("cartItems", [])
            if not items:
                items = cart_data.get("data", [])
        if not items:
            # 找任何列表
            for v in cart_data.values():
                if isinstance(v, list) and len(v) > 0:
                    items = v
                    break
        if not items:
            pytest.skip("购物车无商品")
        ci = items[0]
        ciid = ci.get("cartItemId") or ci.get("id")
        if not ciid:
            pytest.skip("购物车项无ID")
        resp = client.put(f"/api/cart/items/{ciid}/quantity", json={"quantity": 2})
        client.assert_ok("更新数量失败")
        print(f"  [OK] 更新数量 cartItemId={ciid} qty=2")
        client._test_cart_item_id = ciid

    def test_71_cart_remove_item(self, client, member_token):
        """删除购物车商品"""
        ciid = getattr(client, '_test_cart_item_id', None)
        if not ciid:
            # 先加一个再到购物车查看
            gid = getattr(client, '_test_goods_id', None)
            if not gid:
                pytest.skip("无商品ID")
            client.post("/api/cart/items", json={"goodsId": gid, "quantity": 1})
            resp = client.get("/api/cart")
            data = client.assert_ok()
            cart_data = client.extract_data() or {}
            items = []
            if isinstance(cart_data, dict):
                for v in cart_data.values():
                    if isinstance(v, list) and len(v) > 0:
                        items = v
                        break
            if not items:
                pytest.skip("购物车无商品")
            ciid = items[0].get("cartItemId") or items[0].get("id")
        resp = client.delete(f"/api/cart/items/{ciid}")
        client.assert_ok("删除购物车商品失败")
        print(f"  [OK] 删除购物车商品 {ciid}")


# ============================================================
# 结算下单 (CheckoutAppController + OrderAppController)
# ============================================================
class TestConsumerCheckout:

    def test_72_preview_and_submit_order(self, client, member_token):
        """预览并提交订单"""
        gid = getattr(client, '_test_goods_id', None)
        if not gid:
            pytest.skip("无商品")
        # 先有地址
        resp = client.get("/api/member/address/list")
        data = client.assert_ok()
        addrs = client.extract_data() or []
        if not addrs:
            pytest.skip("无收货地址")
        addr_id = addrs[0].get("addressId")

        # 预览
        resp = client.post("/api/checkout/preview", json={
            "source": "BUY_NOW",
            "items": [{"goodsId": gid, "quantity": 1}],
        })
        client.assert_ok("预览订单失败")
        preview = client.extract_data() or {}
        print(f"  [OK] 订单预览正常")

        # 提交
        resp = client.post("/api/checkout/submit", json={
            "source": "BUY_NOW",
            "addressId": addr_id,
            "payType": "WEIXIN",
            "items": [{"goodsId": gid, "quantity": 1}],
        })
        data = client.assert_ok("提交订单失败")
        order_data = data.get("data", {}) if isinstance(data.get("data"), dict) else data
        order_id = order_data.get("orderId")
        if not order_id:
            order_ids = order_data.get("orderIds", [])
            order_id = order_ids[0] if order_ids else None
        if not order_id:
            print(f"  [WARN] 提交订单返回结构: {json.dumps(data, ensure_ascii=False)[:150]}")
            pytest.skip("未获取到orderId")
        client._test_order_id = order_id
        print(f"  [OK] 订单提交成功 orderId={order_id}")


# ============================================================
# 订单管理 (OrderAppController)
# ============================================================
class TestConsumerOrder:

    def test_73_order_badges(self, client, member_token):
        """订单角标"""
        resp = client.get("/api/order/badges")
        client.assert_ok("查询订单角标失败")
        badges = client.extract_data() or {}
        print(f"  [OK] 订单角标: {badges}")

    def test_74_order_list(self, client, member_token):
        """订单列表"""
        resp = client.get("/api/order/list")
        client.assert_ok("查询订单列表失败")
        data = client.extract_data() or {}
        total = data.get("total", 0) if isinstance(data, dict) else 0
        print(f"  [OK] 订单列表 total={total}")

    def test_75_order_detail(self, client, member_token):
        """订单详情"""
        oid = getattr(client, '_test_order_id', None)
        if not oid:
            pytest.skip("无订单ID")
        resp = client.get(f"/api/order/{oid}")
        client.assert_ok("查询订单详情失败")
        detail = client.extract_data() or {}
        status = detail.get("orderStatus", "")
        print(f"  [OK] 订单详情 orderId={oid} status={status}")

    def test_76_order_pay(self, client, member_token):
        """订单支付（mock模式）"""
        oid = getattr(client, '_test_order_id', None)
        if not oid:
            pytest.skip("无订单ID")
        resp = client.post(f"/api/order/{oid}/pay")
        data = client.assert_ok("支付失败")
        print(f"  [OK] 支付调用成功")

    def test_77_order_confirm_receive(self, client, member_token):
        """确认收货"""
        oid = getattr(client, '_test_order_id', None)
        if not oid:
            pytest.skip("无订单ID")
        resp = client.post(f"/api/order/{oid}/confirm-receive")
        data = client.assert_ok("确认收货失败")
        print(f"  [OK] 确认收货 orderId={oid}")


# ============================================================
# 开放统计 (MallStatsOpenController)
# ============================================================
class TestConsumerStats:

    def test_78_stats_hot_category(self, client):
        """热销类目统计"""
        resp = client.get("/api/open/stats/hotCategory")
        client.assert_ok("查询热销类目失败")
        data = client.extract_data() or {}
        print(f"  [OK] 热销类目: {json.dumps(data, ensure_ascii=False)[:100]}")

    def test_79_stats_overview(self, client):
        """商城概览统计"""
        resp = client.get("/api/open/stats/overview")
        client.assert_ok("查询商城概览失败")
        data = client.extract_data() or {}
        print(f"  [OK] 商城概览: {json.dumps(data, ensure_ascii=False)[:100]}")
