"""邀请奖励金提现账户隔离测试:提现扣 invite_cash 账户、退款退回 invite_cash、与金币现金不串、
/invite/me 返回奖励金战绩、提现单 source 过滤。复用 test_withdraw 的 wxpay monkeypatch 模式。
"""
from __future__ import annotations

from sqlalchemy import select

from app.db.session import SessionLocal
from app.models.user import User
from app.models.wallet import CoinAccount, InviteCashTransaction
from app.repositories import wallet as crud_wallet


def _login(client, phone: str) -> str:
    client.post("/api/v1/auth/sms/send", json={"phone": phone})
    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 _auth(token: str) -> dict[str, str]:
    return {"Authorization": f"Bearer {token}"}


def _patch_userinfo(monkeypatch, openid: str) -> None:
    monkeypatch.setattr(
        "app.integrations.wxpay.code_to_userinfo",
        lambda code: {"openid": openid, "nickname": None, "avatar_url": None, "raw": {}},
    )


def _seed_balances(client, token: str, phone: str, *, cash: int = 0, invite_cash: int = 0) -> None:
    """访问 /account 触发建账户,再 DB 直接灌两个账户余额(没有"加钱"接口,正常靠兑换/发奖)。"""
    client.get("/api/v1/wallet/account", headers=_auth(token))
    db = SessionLocal()
    try:
        user = db.execute(select(User).where(User.phone == phone)).scalar_one()
        acc = db.get(CoinAccount, user.id)
        acc.cash_balance_cents = cash
        acc.invite_cash_balance_cents = invite_cash
        db.commit()
    finally:
        db.close()


def _balances(client, token: str) -> tuple[int, int]:
    j = client.get("/api/v1/wallet/account", headers=_auth(token)).json()
    return j["cash_balance_cents"], j["invite_cash_balance_cents"]


def _reject(bill: str, reason: str = "测试拒绝") -> None:
    db = SessionLocal()
    try:
        crud_wallet.reject_withdraw(db, bill, reason)
    finally:
        db.close()


def test_invite_cash_withdraw_deducts_invite_account(client, monkeypatch) -> None:
    """source=invite_cash 提现 → 扣 invite_cash_balance_cents,不动 cash;流水落 invite_cash_transaction。"""
    _patch_userinfo(monkeypatch, "openid_ic_1")
    token = _login(client, "13800004001")
    _seed_balances(client, token, "13800004001", cash=300, invite_cash=500)
    client.post("/api/v1/wallet/bind-wechat", json={"code": "c"}, headers=_auth(token))

    r = client.post(
        "/api/v1/wallet/withdraw",
        json={"amount_cents": 200, "source": "invite_cash"},
        headers=_auth(token),
    )
    assert r.status_code == 200, r.text
    assert r.json()["status"] == "reviewing"

    cash, invite_cash = _balances(client, token)
    assert invite_cash == 300       # 扣了邀请奖励金
    assert cash == 300              # 金币现金没动

    db = SessionLocal()
    try:
        user = db.execute(select(User).where(User.phone == "13800004001")).scalar_one()
        txns = db.execute(
            select(InviteCashTransaction).where(
                InviteCashTransaction.user_id == user.id,
                InviteCashTransaction.biz_type == "invite_withdraw",
            )
        ).scalars().all()
        assert len(txns) == 1 and txns[0].amount_cents == -200
    finally:
        db.close()
    orders = client.get("/api/v1/wallet/withdraw-orders", headers=_auth(token)).json()["items"]
    assert orders[0]["source"] == "invite_cash"


def test_invite_cash_reject_refunds_invite_account(client, monkeypatch) -> None:
    """拒绝 invite_cash 提现 → 退回 invite_cash,不串金币现金;退款流水落 invite_cash_transaction。"""
    _patch_userinfo(monkeypatch, "openid_ic_2")
    token = _login(client, "13800004002")
    _seed_balances(client, token, "13800004002", cash=0, invite_cash=500)
    client.post("/api/v1/wallet/bind-wechat", json={"code": "c"}, headers=_auth(token))

    r = client.post(
        "/api/v1/wallet/withdraw",
        json={"amount_cents": 200, "source": "invite_cash"},
        headers=_auth(token),
    )
    bill = r.json()["out_bill_no"]
    cash, invite_cash = _balances(client, token)
    assert invite_cash == 300 and cash == 0    # 扣后

    _reject(bill)

    cash, invite_cash = _balances(client, token)
    assert invite_cash == 500    # 退回邀请奖励金
    assert cash == 0             # 没串进现金

    db = SessionLocal()
    try:
        user = db.execute(select(User).where(User.phone == "13800004002")).scalar_one()
        refunds = db.execute(
            select(InviteCashTransaction).where(
                InviteCashTransaction.user_id == user.id,
                InviteCashTransaction.biz_type == "invite_withdraw_refund",
            )
        ).scalars().all()
        assert len(refunds) == 1 and refunds[0].amount_cents == 200
    finally:
        db.close()


def test_invite_cash_reject_recreates_missing_account(client, monkeypatch) -> None:
    """A legacy or hand-written order without CoinAccount can still be refunded."""
    _patch_userinfo(monkeypatch, "openid_ic_missing_account")
    token = _login(client, "13800004012")
    _seed_balances(client, token, "13800004012", cash=0, invite_cash=200)
    client.post("/api/v1/wallet/bind-wechat", json={"code": "c"}, headers=_auth(token))

    response = client.post(
        "/api/v1/wallet/withdraw",
        json={"amount_cents": 200, "source": "invite_cash"},
        headers=_auth(token),
    )
    assert response.status_code == 200, response.text
    bill = response.json()["out_bill_no"]

    with SessionLocal() as db:
        user = db.execute(
            select(User).where(User.phone == "13800004012")
        ).scalar_one()
        db.delete(db.get(CoinAccount, user.id))
        db.commit()

    _reject(bill, "missing account fixture")

    with SessionLocal() as db:
        user = db.execute(
            select(User).where(User.phone == "13800004012")
        ).scalar_one()
        account = db.get(CoinAccount, user.id)
        order = crud_wallet._get_withdraw_or_raise(db, bill)
        refunds = db.execute(
            select(InviteCashTransaction).where(
                InviteCashTransaction.user_id == user.id,
                InviteCashTransaction.biz_type == "invite_withdraw_refund",
                InviteCashTransaction.ref_id == bill,
            )
        ).scalars().all()

        assert account is not None
        assert account.invite_cash_balance_cents == 200
        assert order.status == "rejected"
        assert order.fail_reason == "missing account fixture"
        assert len(refunds) == 1 and refunds[0].amount_cents == 200


def test_two_accounts_withdraw_independent(client, monkeypatch) -> None:
    """两账户各提各的不串:提 invite_cash(拒绝→验证退款回原账户),再提 cash,各扣各账户互不串。"""
    _patch_userinfo(monkeypatch, "openid_ic_3")
    token = _login(client, "13800004003")
    _seed_balances(client, token, "13800004003", cash=400, invite_cash=500)
    client.post("/api/v1/wallet/bind-wechat", json={"code": "c"}, headers=_auth(token))

    r1 = client.post(
        "/api/v1/wallet/withdraw",
        json={"amount_cents": 200, "source": "invite_cash"},
        headers=_auth(token),
    )
    _reject(r1.json()["out_bill_no"])    # 拒绝退回 invite_cash(验证退款回原账户)
    r2 = client.post(
        "/api/v1/wallet/withdraw",
        # 50 分 = 0.5 元档(7-9 起 coin_cash 只能提预设档位)
        json={"amount_cents": 50, "source": "coin_cash"},
        headers=_auth(token),
    )
    assert r2.json()["status"] == "reviewing"

    cash, invite_cash = _balances(client, token)
    assert invite_cash == 500    # 已退回
    assert cash == 350           # 扣了 cash 50


def test_invite_cash_multiple_in_flight_allowed(client, monkeypatch) -> None:
    """取消「同时仅一单」:invite_cash 无档位上限,已有在途时仍可继续提交,多笔并存各扣 invite_cash。"""
    _patch_userinfo(monkeypatch, "openid_ic_multi")
    token = _login(client, "13800004006")
    _seed_balances(client, token, "13800004006", cash=0, invite_cash=500)
    client.post("/api/v1/wallet/bind-wechat", json={"code": "c"}, headers=_auth(token))

    r1 = client.post(
        "/api/v1/wallet/withdraw",
        json={"amount_cents": 200, "source": "invite_cash", "out_bill_no": "billicflight0001"},
        headers=_auth(token),
    )
    assert r1.status_code == 200, r1.text
    assert r1.json()["status"] == "reviewing"

    r2 = client.post(
        "/api/v1/wallet/withdraw",
        json={"amount_cents": 200, "source": "invite_cash", "out_bill_no": "billicflight0002"},
        headers=_auth(token),
    )
    assert r2.status_code == 200, r2.text
    assert r2.json()["status"] == "reviewing"

    # 两笔 invite_cash 在途并存,共扣 400(500→100),金币现金不动
    cash, invite_cash = _balances(client, token)
    assert invite_cash == 100 and cash == 0
    orders = client.get(
        "/api/v1/wallet/withdraw-orders", params={"source": "invite_cash"}, headers=_auth(token)
    ).json()["items"]
    reviewing = [o for o in orders if o["status"] == "reviewing"]
    assert len(reviewing) == 2


def test_invite_me_returns_reward_stats(client) -> None:
    """/invite/me 返回 reward_balance_cents(可提现奖励金)。"""
    token = _login(client, "13800004004")
    _seed_balances(client, token, "13800004004", invite_cash=350)
    j = client.get("/api/v1/invite/me", headers=_auth(token)).json()
    assert j["reward_balance_cents"] == 350
    assert j["reward_withdrawn_cents"] == 0


def test_withdraw_orders_source_filter(client, monkeypatch) -> None:
    """/wallet/withdraw-orders?source=invite_cash 只返回邀请奖励金提现单。"""
    _patch_userinfo(monkeypatch, "openid_ic_5")
    token = _login(client, "13800004005")
    _seed_balances(client, token, "13800004005", cash=400, invite_cash=500)
    client.post("/api/v1/wallet/bind-wechat", json={"code": "c"}, headers=_auth(token))

    client.post(
        "/api/v1/wallet/withdraw",
        json={"amount_cents": 200, "source": "invite_cash"},
        headers=_auth(token),
    )
    client.post(
        "/api/v1/wallet/withdraw",
        # 50 分 = 0.5 元档(7-9 起 coin_cash 只能提预设档位)
        json={"amount_cents": 50, "source": "coin_cash"},
        headers=_auth(token),
    )

    all_orders = client.get("/api/v1/wallet/withdraw-orders", headers=_auth(token)).json()["items"]
    invite_orders = client.get(
        "/api/v1/wallet/withdraw-orders", params={"source": "invite_cash"}, headers=_auth(token)
    ).json()["items"]
    coin_orders = client.get(
        "/api/v1/wallet/withdraw-orders", params={"source": "coin_cash"}, headers=_auth(token)
    ).json()["items"]
    assert len(all_orders) == 2
    assert len(invite_orders) == 1 and invite_orders[0]["source"] == "invite_cash"
    assert len(coin_orders) == 1 and coin_orders[0]["source"] == "coin_cash"
