"""测试微信支付：服务层 + API 端点"""

import uuid
from datetime import datetime

from httpx import AsyncClient

from app.compat import UTC
from app.models.operations import PaymentOrder
from app.services.wechat_pay import (
    _build_xml,
    _md5,
    _parse_xml,
    _sign_params,
    create_unified_order,
    fail_xml,
    generate_out_trade_no,
    is_dev_mode,
    success_xml,
    verify_notify,
)

# ═══════════════════════════════════════
# 服务层单元测试
# ═══════════════════════════════════════

class TestSignAndXml:
    def test_md5(self):
        h = _md5("abc")
        assert h == "900150983CD24FB0D6963F7D28E17F72"  # 标准 MD5 大写

    def test_sign_params(self):
        params = {"appid": "wx123", "mch_id": "456", "nonce_str": "abc", "total_fee": 100}
        api_key = "testkey"
        # 按文档签名: 字典序排列后拼接 key
        sign = _sign_params(params, api_key)
        # 验证: key 出现在签名串末尾
        expected_raw = "appid=wx123&mch_id=456&nonce_str=abc&total_fee=100&key=testkey"
        assert sign == _md5(expected_raw)

    def test_sign_skips_none(self):
        params = {"appid": "wx123", "mch_id": None, "nonce_str": "abc"}
        sign = _sign_params(params, "key")
        assert "mch_id=" not in sign  # None 字段不应参与签名

    def test_build_xml_simple(self):
        xml = _build_xml({"return_code": "SUCCESS", "total_fee": 100})
        assert "<return_code><![CDATA[SUCCESS]]></return_code>" in xml
        assert "<total_fee>100</total_fee>" in xml  # int 不用 CDATA
        assert xml.startswith("<xml>")
        assert xml.endswith("</xml>")

    def test_build_xml_skips_none(self):
        xml = _build_xml({"a": "b", "c": None})
        assert "<a>" in xml
        assert "<c>" not in xml

    def test_parse_xml(self):
        xml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><total_fee>100</total_fee></xml>"
        result = _parse_xml(xml)
        assert result == {"return_code": "SUCCESS", "total_fee": "100"}

    def test_xml_roundtrip(self):
        """build_xml → parse_xml 应还原"""
        params = {"appid": "wx123", "mch_id": "456", "total_fee": 3500}
        xml = _build_xml(params)
        parsed = _parse_xml(xml)
        assert parsed["appid"] == "wx123"
        assert parsed["total_fee"] == "3500"


class TestOrderGeneration:
    def test_generate_out_trade_no_format(self):
        no = generate_out_trade_no()
        assert no.startswith("SCL")
        assert len(no) > 10
        # 检查包含数字和字母
        assert no[3:].isalnum()

    def test_generate_out_trade_no_unique(self):
        nos = {generate_out_trade_no() for _ in range(100)}
        assert len(nos) == 100

    def test_success_xml(self):
        xml = success_xml()
        result = _parse_xml(xml)
        assert result["return_code"] == "SUCCESS"

    def test_fail_xml(self):
        xml = fail_xml("custom_error")
        result = _parse_xml(xml)
        assert result["return_code"] == "FAIL"
        assert result["return_msg"] == "custom_error"

    def test_fail_xml_default(self):
        xml = fail_xml()
        result = _parse_xml(xml)
        assert result["return_msg"] == "FAIL"


class TestDevMode:
    def test_is_dev_mode(self):
        """测试环境没有 WECHAT 配置，应该返回 True"""
        assert is_dev_mode() is True

    async def test_create_unified_order_dev_mode(self):
        """开发模式应返回模拟二维码 URL"""
        code_url, prepay_id = await create_unified_order("TEST001", "pro", "127.0.0.1")
        assert "qrserver.com" in code_url
        assert "PAY_pro" in code_url
        assert prepay_id is None

    async def test_create_unified_order_dev_mode_team(self):
        code_url, _ = await create_unified_order("TEST002", "team", "127.0.0.1")
        assert "PAY_team" in code_url

    def test_verify_notify_dev_mode(self):
        """开发模式跳过验签，解析 XML 字段后直接返回 verified"""
        xml = """<xml>
            <return_code><![CDATA[SUCCESS]]></return_code>
            <out_trade_no><![CDATA[SCL123]]></out_trade_no>
            <transaction_id><![CDATA[wx123456]]></transaction_id>
            <total_fee>3500</total_fee>
            <result_code><![CDATA[SUCCESS]]></result_code>
        </xml>"""
        result = verify_notify(xml)
        assert result["verified"] is True
        assert result["out_trade_no"] == "SCL123"
        assert result["transaction_id"] == "wx123456"

    def test_verify_notify_return_code_not_success(self):
        xml = """<xml><return_code><![CDATA[FAIL]]></return_code></xml>"""
        result = verify_notify(xml)
        assert result["verified"] is False


# ═══════════════════════════════════════
# API 端点集成测试
# ═══════════════════════════════════════

class TestPaymentCreateEndpoint:
    async def test_create_unauthorized(self, client: AsyncClient):
        resp = await client.post("/api/v1/subscriptions/wechat-pay/create")
        # CSRF 中间件对无 Bearer 请求返回 403
        assert resp.status_code in (401, 403)

    async def test_create_success(self, client: AsyncClient, auth_headers: dict):
        resp = await client.post("/api/v1/subscriptions/wechat-pay/create",
                                 params={"plan": "pro"}, headers=auth_headers)
        assert resp.status_code == 200, resp.text
        data = resp.json()
        assert "order_id" in data
        assert "code_url" in data
        assert "out_trade_no" in data
        assert data["plan"] == "pro"
        assert data["amount_cny"] == 35  # 3500 分 = 35 元
        assert "qrserver.com" in data["code_url"]
        # 验证 order_id 是有效 UUID
        uuid.UUID(data["order_id"])

    async def test_create_with_team_plan(self, client: AsyncClient, auth_headers: dict):
        resp = await client.post("/api/v1/subscriptions/wechat-pay/create",
                                 params={"plan": "team"}, headers=auth_headers)
        assert resp.status_code == 200, resp.text
        data = resp.json()
        assert data["amount_cny"] == 70
        uuid.UUID(data["order_id"])

    async def test_create_invalid_plan(self, client: AsyncClient, auth_headers: dict):
        resp = await client.post("/api/v1/subscriptions/wechat-pay/create",
                                 params={"plan": "invalid"}, headers=auth_headers)
        assert resp.status_code == 400


class TestPaymentStatusEndpoint:
    async def test_status_unauthorized(self, client: AsyncClient):
        resp = await client.get("/api/v1/subscriptions/payment/some-id/status")
        assert resp.status_code == 401

    async def test_status_not_found(self, client: AsyncClient, auth_headers: dict):
        fake_id = uuid.uuid4().hex
        resp = await client.get(f"/api/v1/subscriptions/payment/{fake_id}/status",
                                headers=auth_headers)
        assert resp.status_code == 404

    async def test_status_pending(self, client: AsyncClient, auth_headers: dict):
        # 先创建订单
        create_resp = await client.post("/api/v1/subscriptions/wechat-pay/create",
                                        params={"plan": "pro"}, headers=auth_headers)
        assert create_resp.status_code == 200
        order_id = create_resp.json()["order_id"]

        resp = await client.get(f"/api/v1/subscriptions/payment/{order_id}/status",
                                headers=auth_headers)
        assert resp.status_code == 200
        data = resp.json()
        assert data["status"] == "pending"
        assert data["plan"] == "pro"
        assert data["amount_cny"] == 35
        assert data["pay_time"] is None


class TestMockPaidEndpoint:
    async def test_mock_paid(self, client: AsyncClient, auth_headers: dict):
        # 创建订单
        create_resp = await client.post("/api/v1/subscriptions/wechat-pay/create",
                                        params={"plan": "pro"}, headers=auth_headers)
        order_id = create_resp.json()["order_id"]

        # 模拟支付
        resp = await client.post(f"/api/v1/subscriptions/payment/{order_id}/mock-paid",
                                 headers=auth_headers)
        assert resp.status_code == 200, resp.text
        data = resp.json()
        assert data["status"] == "paid"
        assert data["plan"] == "pro"

        # 验证状态
        status_resp = await client.get(f"/api/v1/subscriptions/payment/{order_id}/status",
                                       headers=auth_headers)
        assert status_resp.json()["status"] == "paid"
        assert status_resp.json()["pay_time"] is not None
        assert status_resp.json()["transaction_id"] is not None

    async def test_mock_paid_twice_rejected(self, client: AsyncClient, auth_headers: dict):
        create_resp = await client.post("/api/v1/subscriptions/wechat-pay/create",
                                        params={"plan": "team"}, headers=auth_headers)
        order_id = create_resp.json()["order_id"]

        # 第一次 mock 成功
        resp1 = await client.post(f"/api/v1/subscriptions/payment/{order_id}/mock-paid",
                                  headers=auth_headers)
        assert resp1.status_code == 200

        # 第二次应该拒绝
        resp2 = await client.post(f"/api/v1/subscriptions/payment/{order_id}/mock-paid",
                                  headers=auth_headers)
        assert resp2.status_code == 400

    async def test_mock_paid_wrong_user(self, client: AsyncClient, auth_headers: dict):
        """另一个用户不能 mock 不属于他的订单"""
        create_resp = await client.post("/api/v1/subscriptions/wechat-pay/create",
                                        params={"plan": "pro"}, headers=auth_headers)
        order_id = create_resp.json()["order_id"]

        # 用新用户注册再尝试 mock
        import uuid as _uuid
        email = f"other{_uuid.uuid4().hex[:6]}@test.cn"
        reg = await client.post("/api/v1/auth/register", json={
            "email": email, "password": "Test1234", "display_name": "Other"
        })
        other_headers = {"Authorization": f"Bearer {reg.json()['token']['access_token']}"}

        # 轮询 status 会返回 403（无权查看）
        status_resp = await client.get(f"/api/v1/subscriptions/payment/{order_id}/status",
                                       headers=other_headers)
        # 另一个用户可能看到 403 或 404，取决于实现
        assert status_resp.status_code in (403, 404)


class TestUpgradeEndpoint:
    async def test_upgrade_returns_payment_required(self, client: AsyncClient, auth_headers: dict):
        """升级端点现返回 payment_required"""
        resp = await client.post("/api/v1/subscriptions/upgrade",
                                 json={"target_plan": "pro"}, headers=auth_headers)
        assert resp.status_code == 200
        data = resp.json()
        assert data["status"] == "payment_required"
        assert "微信支付" in data["message"]

class TestPreviewEndpoint:
    async def test_preview_upgrade(self, client: AsyncClient, auth_headers: dict):
        resp = await client.post("/api/v1/subscriptions/upgrade/preview",
                                 params={"target_plan": "pro"}, headers=auth_headers)
        assert resp.status_code == 200
        data = resp.json()
        assert "current_plan" in data
        assert "target_plan" in data
        assert "changes" in data
        assert "prices" in data


class TestPaymentOrderDb:
    """验证 PaymentOrder 模型在数据库中的行为"""

    async def test_create_and_query(self, db):
        tid = uuid.uuid4()
        uid_ = uuid.uuid4()
        order = PaymentOrder(
            id=uuid.uuid4(), tenant_id=tid, user_id=uid_,
            plan="pro", amount=3500, status="pending",
            out_trade_no=f"SCL{datetime.now(UTC).timestamp()}abc123",
        )
        db.add(order)
        await db.commit()

        # 查询
        from sqlalchemy import select
        result = await db.execute(select(PaymentOrder).where(PaymentOrder.tenant_id == tid))
        found = result.scalar()
        assert found is not None
        assert found.plan == "pro"
        assert found.amount == 3500
        assert found.status == "pending"
        assert found.pay_time is None
        assert found.transaction_id is None
