from flask import Blueprint, request, jsonify, session
import sqlite3
import json
import time
import math
import logging
import threading
from jwt_utils import verify_token
from chat import get_db, close_game_connection
from state_manager import player_state_manager
from db_storage import load_state_from_tables, save_state_to_tables
from notification import send_public, send_private, notify_player_gold, notify_player_inventory, notify_auction_update

_expired_check_lock = threading.Lock()
logger = logging.getLogger(__name__)

auction_bp = Blueprint('auction', __name__, url_prefix='/api/auction')

# 保证金配置
DEPOSIT_RATE = 0.01
DEPOSIT_MIN = 1000
DEPOSIT_MAX = 1_000_000

def calc_deposit(starting_price):
    """计算保证金：起拍价的1%，但不低于1000，不高于100万"""
    return max(DEPOSIT_MIN, min(DEPOSIT_MAX, math.floor(starting_price * DEPOSIT_RATE)))


# ========== 认证辅助函数（保持不变） ==========
def _get_user_from_request(req):
    """从请求中获取 user_id，支持 JWT、proxy_session 和 session"""
    auth_header = req.headers.get('Authorization')
    if auth_header and auth_header.startswith('Bearer '):
        token = auth_header.split(' ')[1]
        payload = verify_token(token)
        if payload:
            return payload.get('user_id')

    token = req.args.get('proxy_session')
    if not token and req.is_json:
        data = req.get_json(silent=True)
        if data:
            token = data.get('proxy_session')
    if not token:
        token = req.headers.get('X-Proxy-Session')

    if token:
        try:
            from main import proxy_session_manager
            token_data = proxy_session_manager.get_token_data(token)
            if token_data:
                return token_data.get('user_id')
        except Exception:
            pass

    return session.get('user_id')


def _verify_character_owner(character_name, user_id):
    """检查角色是否属于当前用户（基于分表）"""
    if not character_name or not user_id:
        return False
    conn = get_db()
    try:
        c = conn.cursor()
        c.execute('SELECT user_id FROM players WHERE name = ?', (character_name,))
        row = c.fetchone()
        return row is not None and row[0] == user_id
    finally:
        close_game_connection(conn)


# ========== 数据库初始化 ==========
def init_auction_table():
    """初始化拍卖相关表（保持不变）"""
    conn = get_db()
    try:
        c = conn.cursor()
        c.execute('''
            CREATE TABLE IF NOT EXISTS auction_items (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                seller_character_name TEXT NOT NULL,
                seller_user_id INTEGER NOT NULL,
                item_json TEXT NOT NULL,
                starting_price INTEGER NOT NULL,
                current_price INTEGER NOT NULL,
                current_bidder TEXT,
                min_increment INTEGER NOT NULL DEFAULT 1,
                buyout_price INTEGER,
                start_time INTEGER NOT NULL,
                end_time INTEGER NOT NULL,
                status TEXT NOT NULL DEFAULT 'active',
                created_at INTEGER NOT NULL
            )
        ''')
        c.execute('''
            CREATE TABLE IF NOT EXISTS auction_bids (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                auction_id INTEGER NOT NULL,
                bidder_character_name TEXT NOT NULL,
                bidder_user_id INTEGER NOT NULL,
                bid_amount INTEGER NOT NULL,
                bid_time INTEGER NOT NULL,
                FOREIGN KEY(auction_id) REFERENCES auction_items(id)
            )
        ''')
        c.execute('CREATE INDEX IF NOT EXISTS idx_auction_status ON auction_items(status)')
        c.execute('CREATE INDEX IF NOT EXISTS idx_auction_end ON auction_items(end_time)')
        c.execute('CREATE INDEX IF NOT EXISTS idx_bids_auction ON auction_bids(auction_id)')
        conn.commit()
    finally:
        close_game_connection(conn)


# ========== 定时结标任务（核心修复） ==========
def check_expired_auctions():
    if not _expired_check_lock.acquire(blocking=False):
        return
    try:
        conn = get_db()
        conn.row_factory = sqlite3.Row
        try:
            c = conn.cursor()
            now = int(time.time())
            BATCH_SIZE = 50
            while True:
                c.execute('''
                    SELECT id, seller_character_name, seller_user_id, item_json,
                           starting_price, current_price, current_bidder
                    FROM auction_items
                    WHERE status = 'active' AND end_time <= ?
                    LIMIT ?
                ''', (now, BATCH_SIZE))
                rows = c.fetchall()
                if not rows:
                    break

                # 收集所有涉及的玩家名（卖家和出价人）
                player_names = set()
                for row in rows:
                    player_names.add(row['seller_character_name'])
                    if row['current_bidder']:
                        player_names.add(row['current_bidder'])
                
                # 批量加载所有玩家状态
                from state_manager import player_state_manager
                from db_storage import load_state_from_tables
                states = {}
                for name in player_names:
                    state = player_state_manager.get(name)
                    if state is None:
                        state = load_state_from_tables(name)
                        if state:
                            player_state_manager.set(name, state)
                    if state:
                        states[name] = state

                # 逐条处理，但重用已加载的状态
                for row in rows:
                    _process_single_auction_with_states(c, row, now, states)
                conn.commit()
        finally:
            close_game_connection(conn)
    finally:
        _expired_check_lock.release()

def _process_single_auction_with_states(c, row, now, preloaded_states):
    """使用预加载的状态处理单个过期拍卖"""
    auction_id = row['id']
    seller_name = row['seller_character_name']
    seller_user_id = row['seller_user_id']
    item_json = row['item_json']
    starting_price = row['starting_price']
    current_price = row['current_price']
    current_bidder = row['current_bidder']
    
    try:
        item = json.loads(item_json)
    except Exception:
        c.execute('UPDATE auction_items SET status="error" WHERE id=?', (auction_id,))
        return
    
    seller_state = preloaded_states.get(seller_name)
    if not seller_state:
        c.execute('UPDATE auction_items SET status="error" WHERE id=?', (auction_id,))
        return

    deposit = calc_deposit(starting_price)
    
    if current_bidder:
        buyer_state = preloaded_states.get(current_bidder)
        if not buyer_state:
            # 买家状态不存在，流拍处理
            _handle_auction_fail(seller_state, item, deposit, seller_name, auction_id, c)
            return
        
        if buyer_state['player']['gold'] < current_price:
            _handle_auction_insufficient_funds(buyer_state, seller_state, item, deposit,
                                               current_bidder, seller_name, auction_id, c)
        elif len(buyer_state.get('inventory', [])) >= buyer_state.get('maxInventorySlots', 500):
            _handle_auction_full_inventory(seller_state, item, deposit, seller_name, auction_id, c)
        else:
            _handle_auction_success(buyer_state, seller_state, item, current_price, deposit,
                                   current_bidder, seller_name, auction_id, c)
    else:
        _handle_auction_no_bids(seller_state, item, deposit, seller_name, auction_id, c)
        
def validate_item(item):
    """检查物品能否上架拍卖"""
    forbidden_types = ['quest_item', 'scroll', 'special', 'potion']
    if item.get('type') in forbidden_types:
        return False, '该类型物品不能上架拍卖'
    if not item.get('name') or not item.get('id'):
        return False, '物品数据不完整'
    if item.get('bindOnEquip') or item.get('bindOnPickup'):
        return False, '绑定物品不能上架'
    return True, ''

def _handle_auction_success(buyer_state, seller_state, item, current_price, deposit,
                            buyer_name, seller_name, auction_id, c):
    """拍卖成功，买家获得物品，卖家获得金币并退还保证金"""
    # 买家扣款并添加物品
    buyer_state['player']['gold'] -= current_price
    if not _add_item_to_inventory(buyer_state, item):
        # 理论上背包空间已检查，这里若失败则回滚（由外层负责）
        raise Exception("添加物品到买家背包失败")
    # 卖家获得金币（成交价 - 5% 手续费 + 保证金）
    fee = math.floor(current_price * 0.05)
    seller_gain = current_price - fee + deposit
    seller_state['player']['gold'] += seller_gain

    # 更新拍卖状态
    c.execute('UPDATE auction_items SET status = "sold" WHERE id = ?', (auction_id,))

    # 标记玩家状态为脏，以便后台保存
    from state_manager import player_state_manager
    player_state_manager.set(buyer_name, buyer_state)
    player_state_manager.set(seller_name, seller_state)

    # 发送通知（可选）
    send_private(buyer_name, f'🎉 您竞拍的 [{item["name"]}] 已成交，花费 {current_price} 金币')
    send_private(seller_name, f'💰 您的拍卖品 [{item["name"]}] 以 {current_price} 金币成交，扣除手续费后获得 {seller_gain} 金币（保证金已退还）')


def _handle_auction_insufficient_funds(buyer_state, seller_state, item, deposit,
                                       buyer_name, seller_name, auction_id, c):
    """买家金币不足，标记为流拍"""
    c.execute('UPDATE auction_items SET status = "expired" WHERE id = ?', (auction_id,))
    # 不修改玩家状态，等待卖家手动领取


def _handle_auction_full_inventory(seller_state, item, deposit, seller_name, auction_id, c):
    """买家背包已满，标记为流拍"""
    c.execute('UPDATE auction_items SET status = "expired" WHERE id = ?', (auction_id,))


def _handle_auction_fail(seller_state, item, deposit, seller_name, auction_id, c):
    """买家状态不存在，标记为流拍"""
    c.execute('UPDATE auction_items SET status = "expired" WHERE id = ?', (auction_id,))


def _handle_auction_no_bids(seller_state, item, deposit, seller_name, auction_id, c):
    """无人出价，标记为流拍"""
    c.execute('UPDATE auction_items SET status = "expired" WHERE id = ?', (auction_id,))
    
def _add_item_to_inventory(state, item):
    """将不可堆叠物品添加到玩家背包（拍卖物品一般为单个装备）"""
    inv = state.get('inventory', [])
    max_slots = state.get('maxInventorySlots', 500)
    if len(inv) >= max_slots:
        return False
    inv.append(item)
    return True
    
@auction_bp.route('/add', methods=['POST'])
def add_auction():
    user_id = _get_user_from_request(request)
    if not user_id:
        return jsonify({'error': '请先登录'}), 401

    data = request.json
    character_name = data.get('character_name')
    item_uuid = data.get('item_uuid')          # 改为 uuid
    starting_price = data.get('starting_price')
    buyout_price = data.get('buyout_price')
    duration_hours = data.get('duration_hours', 24)
    min_increment = data.get('min_increment')

    if not character_name or not item_uuid or starting_price is None:
        return jsonify({'error': '参数不足'}), 400

    try:
        starting_price = int(starting_price)
        if starting_price <= 0 or starting_price > 1000000000:
            raise ValueError
    except:
        return jsonify({'error': '起拍价必须为1-10亿之间的整数'}), 400

    if buyout_price:
        try:
            buyout_price = int(buyout_price)
            if buyout_price <= starting_price or buyout_price > 1000000000:
                raise ValueError
        except:
            return jsonify({'error': '一口价必须大于起拍价且不超过10亿'}), 400

    if duration_hours not in [1, 6, 24, 48]:
        return jsonify({'error': '时长可选1,6,24,48小时'}), 400

    if min_increment:
        try:
            min_increment = int(min_increment)
            if min_increment < 1:
                raise ValueError
        except:
            return jsonify({'error': '最小加价幅度必须为正整数'}), 400
    else:
        min_increment = max(1, math.floor(starting_price * 0.05))

    # 获取角色状态（分表）
    state = player_state_manager.get(character_name)
    if state is None:
        state = load_state_from_tables(character_name)
        if state is None or state.get('userId') != user_id:
            return jsonify({'error': '角色不存在或无权限'}), 404
        player_state_manager.set(character_name, state)

    inventory = state.get('inventory', [])
    # 根据 UUID 查找物品
    item_index = None
    item = None
    for i, inv_item in enumerate(inventory):
        if inv_item.get('uuid') == item_uuid:
            item_index = i
            item = inv_item
            break
    if item_index is None:
        return jsonify({'error': '物品不存在或已变化'}), 400

    valid, msg = validate_item(item)
    if not valid:
        return jsonify({'error': msg}), 400

    conn = None
    try:
        conn = get_db()
        c = conn.cursor()
        c.execute('SELECT COUNT(*) FROM auction_items WHERE seller_character_name = ? AND status IN ("active", "expired")', (character_name,))
        if c.fetchone()[0] >= 5:
            return jsonify({'error': '同时最多进行5个拍卖'}), 400

        deposit = calc_deposit(starting_price)
        if state['player']['gold'] < deposit:
            return jsonify({'error': f'保证金不足，需要{deposit}金币'}), 400

        state['player']['gold'] -= deposit
        removed_item = inventory.pop(item_index)   # 从背包移除

        now = int(time.time())
        end_time = now + duration_hours * 3600
        c.execute('''
            INSERT INTO auction_items
            (seller_character_name, seller_user_id, item_json, starting_price, current_price,
             min_increment, buyout_price, start_time, end_time, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ''', (character_name, user_id, json.dumps(removed_item),
              starting_price, starting_price, min_increment,
              buyout_price, now, end_time, now))

        conn.commit()
        player_state_manager.set(character_name, state)   # 脏保存

        item_name = removed_item.get('name', '未知物品')
        end_time_str = time.strftime('%Y-%m-%d %H:%M', time.localtime(end_time))
        buyout_info = f"，一口价 {buyout_price}" if buyout_price else "，无一口价"
        send_private(character_name, f'您成功上架拍卖品 [{item_name}]，起拍价 {starting_price} 金币{buyout_info}，扣除保证金 {deposit} 金币。拍卖将于 {end_time_str} 结束。')
        send_public(f'📢 玩家 [{character_name}] 上架 [{item_name}] 到拍卖行，起拍价 {starting_price} 金币！')

        return jsonify({'status': 'ok', 'message': '上架拍卖成功', 'deposit': deposit})
    except Exception as e:
        if conn:
            conn.rollback()
        logger.exception("上架拍卖异常")
        return jsonify({'error': str(e)}), 500
    finally:
        if conn:
            close_game_connection(conn)

@auction_bp.route('/items', methods=['GET'])
def list_auctions():
    page = request.args.get('page', 1, type=int)
    limit = request.args.get('limit', 20, type=int)
    status = request.args.get('status', 'active')
    offset = (page - 1) * limit

    conn = None
    try:
        conn = get_db()
        conn.row_factory = sqlite3.Row
        c = conn.cursor()

        c.execute('SELECT MAX(created_at) as max_created FROM auction_items')
        row = c.fetchone()
        max_time = row['max_created'] or 0
        etag = f'"{max_time}"' if max_time else '""'

        if request.headers.get('If-None-Match') == etag:
            return '', 304

        if status != 'all':
            total = c.execute('SELECT COUNT(*) FROM auction_items WHERE status = ?', (status,)).fetchone()[0]
            c.execute('''
                SELECT id, seller_character_name, item_json, starting_price, current_price,
                       current_bidder, min_increment, buyout_price, start_time, end_time, status
                FROM auction_items WHERE status = ?
                ORDER BY start_time DESC LIMIT ? OFFSET ?
            ''', (status, limit, offset))
        else:
            total = c.execute('SELECT COUNT(*) FROM auction_items WHERE status != "cancelled"').fetchone()[0]
            c.execute('''
                SELECT id, seller_character_name, item_json, starting_price, current_price,
                       current_bidder, min_increment, buyout_price, start_time, end_time, status
                FROM auction_items WHERE status != "cancelled"
                ORDER BY start_time DESC LIMIT ? OFFSET ?
            ''', (limit, offset))

        rows = c.fetchall()
        items = []
        for row in rows:
            try:
                item_data = json.loads(row['item_json'])
            except:
                continue
            items.append({
                'id': row['id'],
                'seller': row['seller_character_name'],
                'item': item_data,
                'starting_price': row['starting_price'],
                'current_price': row['current_price'],
                'current_bidder': row['current_bidder'],
                'min_increment': row['min_increment'],
                'buyout_price': row['buyout_price'],
                'start_time': row['start_time'],
                'end_time': row['end_time'],
                'status': row['status']
            })

        response = jsonify({'items': items, 'total': total})
        response.headers['ETag'] = etag
        return response
    except Exception as e:
        logger.exception("获取拍卖列表异常")
        return jsonify({'error': str(e)}), 500
    finally:
        if conn:
            close_game_connection(conn)

@auction_bp.route('/mine', methods=['GET'])
def my_auctions():
    user_id = _get_user_from_request(request)
    if not user_id:
        return jsonify({'error': '未登录'}), 401

    role = request.args.get('role', 'seller')
    character_name = request.args.get('character_name')
    if not character_name:
        return jsonify({'error': '缺少角色名'}), 400

    if not _verify_character_owner(character_name, user_id):
        return jsonify({'error': '无权访问此角色'}), 403

    conn = None
    try:
        conn = get_db()
        conn.row_factory = sqlite3.Row
        c = conn.cursor()

        if role == 'seller':
            c.execute('''
                SELECT id, seller_character_name, item_json, starting_price, current_price,
                       current_bidder, min_increment, buyout_price, start_time, end_time, status
                FROM auction_items
                WHERE seller_character_name = ?
                ORDER BY end_time DESC
            ''', (character_name,))
        else:
            c.execute('''
                SELECT DISTINCT a.id, a.seller_character_name, a.item_json, a.starting_price,
                       a.current_price, a.current_bidder, a.min_increment, a.buyout_price,
                       a.start_time, a.end_time, a.status
                FROM auction_items a
                JOIN auction_bids b ON a.id = b.auction_id
                WHERE b.bidder_character_name = ?
                ORDER BY a.end_time DESC
            ''', (character_name,))

        rows = c.fetchall()
        items = []
        for row in rows:
            try:
                item_data = json.loads(row['item_json'])
            except:
                continue
            items.append({
                'id': row['id'],
                'seller': row['seller_character_name'],
                'item': item_data,
                'starting_price': row['starting_price'],
                'current_price': row['current_price'],
                'current_bidder': row['current_bidder'],
                'min_increment': row['min_increment'],
                'buyout_price': row['buyout_price'],
                'start_time': row['start_time'],
                'end_time': row['end_time'],
                'status': row['status']
            })
        return jsonify(items)
    except Exception as e:
        logger.exception("获取我的拍卖异常")
        return jsonify({'error': str(e)}), 500
    finally:
        if conn:
            close_game_connection(conn)

@auction_bp.route('/bid', methods=['POST'])
def place_bid():
    user_id = _get_user_from_request(request)
    if not user_id:
        return jsonify({'error': '未登录'}), 401

    data = request.json
    auction_id = data.get('auction_id')
    character_name = data.get('character_name')
    bid_amount = data.get('bid_amount')

    if not all([auction_id, character_name, bid_amount]):
        return jsonify({'error': '参数不足'}), 400

    try:
        auction_id = int(auction_id)
        bid_amount = int(bid_amount)
        if bid_amount <= 0:
            raise ValueError
    except:
        return jsonify({'error': '出价必须为正整数'}), 400

    conn = None
    try:
        conn = get_db()
        conn.row_factory = sqlite3.Row
        conn.execute('BEGIN IMMEDIATE')
        c = conn.cursor()

        c.execute('SELECT * FROM auction_items WHERE id = ? AND status = "active"', (auction_id,))
        auction = c.fetchone()
        if not auction:
            conn.rollback()
            return jsonify({'error': '拍卖不存在或已结束'}), 404

        if character_name == auction['seller_character_name']:
            conn.rollback()
            return jsonify({'error': '不能对自己的拍卖出价'}), 400

        now = int(time.time())
        if now >= auction['end_time']:
            conn.rollback()
            return jsonify({'error': '拍卖已结束'}), 400

        required_min = auction['current_price'] + auction['min_increment']
        if bid_amount < required_min:
            conn.rollback()
            return jsonify({'error': f'出价至少为 {required_min} 金币'}), 400

        # 获取角色状态并验证金币（不扣款）
        state = player_state_manager.get(character_name)
        if state is None:
            state = load_state_from_tables(character_name)
            if state is None or state.get('userId') != user_id:
                conn.rollback()
                return jsonify({'error': '角色不存在或无权限'}), 404

        if state['player']['gold'] < bid_amount:
            conn.rollback()
            return jsonify({'error': '金币不足'}), 400

        # 记录出价，更新拍卖当前价格和出价人
        c.execute('''
            INSERT INTO auction_bids (auction_id, bidder_character_name, bidder_user_id, bid_amount, bid_time)
            VALUES (?, ?, ?, ?, ?)
        ''', (auction_id, character_name, user_id, bid_amount, now))

        c.execute('''
            UPDATE auction_items SET current_price = ?, current_bidder = ? WHERE id = ?
        ''', (bid_amount, character_name, auction_id))

        conn.commit()

        item = json.loads(auction['item_json'])
        send_private(character_name, f'您已成功出价 {bid_amount} 金币竞拍 [{item["name"]}]')

        if auction['current_bidder'] and auction['current_bidder'] != character_name:
            send_private(auction['current_bidder'], f'您在拍卖 [{item["name"]}] 中的出价已被 [{character_name}] 超过')

        send_private(auction['seller_character_name'], f'您的拍卖品 [{item["name"]}] 有新出价 {bid_amount} 金币')

        if bid_amount >= 1000000:
            send_public(f'💰 玩家 [{character_name}] 对 [{item["name"]}] 出价 {bid_amount} 金币！')

        return jsonify({'status': 'ok', 'message': '出价成功'})
    except Exception as e:
        if conn:
            conn.rollback()
        logger.exception("出价异常")
        return jsonify({'error': str(e)}), 500
    finally:
        if conn:
            close_game_connection(conn)

@auction_bp.route('/buyout', methods=['POST'])
def buyout_auction():
    user_id = _get_user_from_request(request)
    if not user_id:
        return jsonify({'error': '请先登录'}), 401

    data = request.json
    auction_id = data.get('auction_id')
    buyer_name = data.get('character_name')

    if not auction_id or not buyer_name:
        return jsonify({'error': '参数不足'}), 400

    if not _verify_character_owner(buyer_name, user_id):
        return jsonify({'error': '无权操作此角色'}), 403

    try:
        auction_id = int(auction_id)
    except ValueError:
        return jsonify({'error': '无效的拍卖ID'}), 400

    conn = None
    try:
        conn = get_db()
        conn.row_factory = sqlite3.Row
        conn.execute('BEGIN IMMEDIATE')
        c = conn.cursor()

        c.execute('SELECT * FROM auction_items WHERE id = ? AND status = "active"', (auction_id,))
        auction = c.fetchone()
        if not auction:
            conn.rollback()
            return jsonify({'error': '拍卖品不存在或已结束'}), 404

        if not auction['buyout_price']:
            conn.rollback()
            return jsonify({'error': '该拍卖未设置一口价'}), 400

        if buyer_name == auction['seller_character_name']:
            conn.rollback()
            return jsonify({'error': '不能购买自己的拍卖品'}), 400

        # 获取买家状态
        buyer_state = player_state_manager.get(buyer_name)
        if buyer_state is None:
            buyer_state = load_state_from_tables(buyer_name)
            if buyer_state is None or buyer_state.get('userId') != user_id:
                conn.rollback()
                return jsonify({'error': '角色不存在或无权限'}), 404

        if buyer_state['player']['gold'] < auction['buyout_price']:
            conn.rollback()
            return jsonify({'error': '金币不足'}), 400

        if len(buyer_state.get('inventory', [])) >= buyer_state.get('maxInventorySlots', 500):
            conn.rollback()
            return jsonify({'error': '背包已满'}), 400

        # 获取卖家状态
        seller_state = player_state_manager.get(auction['seller_character_name'])
        if seller_state is None:
            seller_state = load_state_from_tables(auction['seller_character_name'])
            if seller_state is None:
                conn.rollback()
                return jsonify({'error': '卖家数据异常'}), 500

        item = json.loads(auction['item_json'])

        # 交易处理
        buyer_state['player']['gold'] -= auction['buyout_price']
        buyer_state.setdefault('inventory', []).append(item)

        fee = math.floor(auction['buyout_price'] * 0.05)
        seller_gain = auction['buyout_price'] - fee
        deposit = calc_deposit(auction['starting_price'])
        seller_state['player']['gold'] += seller_gain + deposit

        # 保存状态
        player_state_manager.set(buyer_name, buyer_state)
        player_state_manager.set(auction['seller_character_name'], seller_state)

        now = int(time.time())
        c.execute('''
            INSERT INTO auction_bids (auction_id, bidder_character_name, bidder_user_id, bid_amount, bid_time)
            VALUES (?, ?, ?, ?, ?)
        ''', (auction_id, buyer_name, user_id, auction['buyout_price'], now))

        c.execute('''
            UPDATE auction_items SET status = "sold", current_price = ?, current_bidder = ? WHERE id = ?
        ''', (auction['buyout_price'], buyer_name, auction_id))

        conn.commit()

        send_private(buyer_name, f'您以一口价 {auction["buyout_price"]} 金币购得 [{item["name"]}]')
        send_private(auction['seller_character_name'], f'您的拍卖品 [{item["name"]}] 被一口价 {auction["buyout_price"]} 金币买走，获得 {seller_gain} 金币（已扣5%手续费），保证金已退还')
        if auction['current_bidder'] and auction['current_bidder'] != buyer_name:
            send_private(auction['current_bidder'], f'您竞拍的 [{item["name"]}] 已被 [{buyer_name}] 一口价买走')
        send_public(f'💰 玩家 [{buyer_name}] 以一口价 {auction["buyout_price"]} 金币购得 [{item["name"]}]！')

        notify_player_gold(auction['seller_character_name'])
        notify_player_gold(buyer_name)
        notify_player_inventory(auction['seller_character_name'])
        notify_player_inventory(buyer_name)

        return jsonify({'status': 'ok', 'message': '购买成功', 'state': buyer_state})
    except Exception as e:
        if conn:
            conn.rollback()
        logger.exception("一口价购买异常")
        return jsonify({'error': '服务器内部错误'}), 500
    finally:
        if conn:
            close_game_connection(conn)

@auction_bp.route('/cancel', methods=['POST'])
def cancel_auction():
    user_id = _get_user_from_request(request)
    if not user_id:
        return jsonify({'error': '未登录'}), 401

    data = request.json
    auction_id = data.get('auction_id')
    character_name = data.get('character_name')

    if not auction_id or not character_name:
        return jsonify({'error': '参数不足'}), 400

    try:
        auction_id = int(auction_id)
    except ValueError:
        return jsonify({'error': '无效拍卖ID'}), 400

    conn = None
    try:
        conn = get_db()
        conn.execute('BEGIN IMMEDIATE')
        c = conn.cursor()

        c.execute('SELECT * FROM auction_items WHERE id = ?', (auction_id,))
        auction = c.fetchone()
        if not auction:
            conn.rollback()
            return jsonify({'error': '拍卖不存在'}), 404

        if auction[1] != character_name or auction[2] != user_id:
            conn.rollback()
            return jsonify({'error': '无权操作'}), 403

        if auction[11] != 'active':
            conn.rollback()
            return jsonify({'error': '该拍卖已结束，无法取消'}), 400

        c.execute('SELECT COUNT(*) FROM auction_bids WHERE auction_id = ?', (auction_id,))
        if c.fetchone()[0] > 0:
            conn.rollback()
            return jsonify({'error': '已有出价，无法取消'}), 400

        # 获取卖家状态
        state = player_state_manager.get(character_name)
        if state is None:
            state, _ = load_state_from_tables(character_name)
            if state is None or state.get('userId') != user_id:
                conn.rollback()
                return jsonify({'error': '角色数据异常'}), 404

        item = json.loads(auction[3])
        if len(state.get('inventory', [])) >= state.get('maxInventorySlots', 500):
            conn.rollback()
            return jsonify({'error': '背包已满，无法取回物品'}), 400

        state['inventory'].append(item)
        deposit = calc_deposit(auction[4])
        state['player']['gold'] += deposit

        player_state_manager.set(character_name, state)
        c.execute('DELETE FROM auction_items WHERE id=?', (auction_id,))
        c.execute('DELETE FROM auction_bids WHERE auction_id=?', (auction_id,))

        conn.commit()

        send_private(character_name, f'您已取消拍卖 [{item["name"]}]，物品已返还背包，保证金 {deposit} 金币已退还')
        notify_player_gold(character_name)
        notify_player_inventory(character_name)

        return jsonify({'status': 'ok', 'message': '拍卖已取消，物品已返还背包'})
    except Exception as e:
        if conn:
            conn.rollback()
        logger.exception("取消拍卖异常")
        return jsonify({'error': str(e)}), 500
    finally:
        if conn:
            close_game_connection(conn)

@auction_bp.route('/claim', methods=['POST'])
def claim_expired():
    user_id = _get_user_from_request(request)
    if not user_id:
        return jsonify({'error': '未登录'}), 401

    data = request.json
    auction_id = data.get('auction_id')
    character_name = data.get('character_name')

    if not auction_id or not character_name:
        return jsonify({'error': '参数不足'}), 400

    try:
        auction_id = int(auction_id)
    except ValueError:
        return jsonify({'error': '无效拍卖ID'}), 400

    conn = None
    try:
        conn = get_db()
        conn.execute('BEGIN IMMEDIATE')
        c = conn.cursor()

        c.execute('SELECT * FROM auction_items WHERE id = ?', (auction_id,))
        auction = c.fetchone()
        if not auction:
            conn.rollback()
            return jsonify({'error': '拍卖不存在'}), 404

        if auction[1] != character_name or auction[2] != user_id:
            conn.rollback()
            return jsonify({'error': '无权操作'}), 403

        if auction[11] != 'expired':
            conn.rollback()
            return jsonify({'error': '该拍卖状态不是流拍，无法领取'}), 400

        state = player_state_manager.get(character_name)
        if state is None:
            state, _ = load_state_from_tables(character_name)
            if state is None or state.get('userId') != user_id:
                conn.rollback()
                return jsonify({'error': '角色数据异常'}), 404

        item = json.loads(auction[3])
        if len(state.get('inventory', [])) >= state.get('maxInventorySlots', 500):
            conn.rollback()
            return jsonify({'error': '背包已满，无法取回物品'}), 400

        state['inventory'].append(item)
        player_state_manager.set(character_name, state)
        c.execute('DELETE FROM auction_items WHERE id=?', (auction_id,))
        c.execute('DELETE FROM auction_bids WHERE auction_id=?', (auction_id,))

        conn.commit()

        send_private(character_name, f'您已成功领取流拍物品 [{item["name"]}]')
        notify_player_inventory(character_name)

        return jsonify({'status': 'ok', 'message': '流拍物品已取回'})
    except Exception as e:
        if conn:
            conn.rollback()
        logger.exception("领取流拍物品异常")
        return jsonify({'error': str(e)}), 500
    finally:
        if conn:
            close_game_connection(conn)

@auction_bp.route('/bids/<int:auction_id>', methods=['GET'])
def get_bids(auction_id):
    conn = None
    try:
        conn = get_db()
        conn.row_factory = sqlite3.Row
        c = conn.cursor()
        c.execute('''
            SELECT bidder_character_name, bid_amount, bid_time
            FROM auction_bids
            WHERE auction_id = ?
            ORDER BY bid_time DESC
        ''', (auction_id,))
        rows = c.fetchall()
        return jsonify([dict(row) for row in rows])
    except Exception as e:
        logger.exception("获取出价记录异常")
        return jsonify({'error': str(e)}), 500
    finally:
        if conn:
            close_game_connection(conn)

@auction_bp.route('/status/<int:auction_id>', methods=['GET'])
def get_auction_status(auction_id):
    conn = None
    try:
        conn = get_db()
        conn.row_factory = sqlite3.Row
        c = conn.cursor()
        c.execute('SELECT status, current_bidder, current_price, end_time FROM auction_items WHERE id = ?', (auction_id,))
        row = c.fetchone()
        if not row:
            return jsonify({'error': '拍卖不存在'}), 404
        return jsonify({
            'status': row['status'],
            'current_bidder': row['current_bidder'],
            'current_price': row['current_price'],
            'end_time': row['end_time']
        })
    except Exception as e:
        logger.exception("获取拍卖状态异常")
        return jsonify({'error': str(e)}), 500
    finally:
        if conn:
            close_game_connection(conn)

def add_auction_logic(state, params, user_id):
    character_name = params.get('character_name')
    item_uuid = params.get('item_uuid')          # 改为 uuid
    starting_price = params.get('starting_price')
    buyout_price = params.get('buyout_price')
    duration_hours = params.get('duration_hours', 24)
    min_increment = params.get('min_increment')

    if not character_name or not item_uuid or starting_price is None:
        return None, "参数不足"
    try:
        starting_price = int(starting_price)
        if starting_price <= 0 or starting_price > 1000000000:
            raise ValueError
    except:
        return None, "起拍价必须为1-10亿之间的整数"
    if buyout_price:
        try:
            buyout_price = int(buyout_price)
            if buyout_price <= starting_price or buyout_price > 1000000000:
                raise ValueError
        except:
            return None, "一口价必须大于起拍价且不超过10亿"
    if duration_hours not in [1,6,24,48]:
        return None, "时长可选1,6,24,48小时"
    if min_increment:
        try:
            min_increment = int(min_increment)
            if min_increment < 1:
                raise ValueError
        except:
            return None, "最小加价幅度必须为正整数"
    else:
        min_increment = max(1, math.floor(starting_price * 0.05))

    inventory = state.get('inventory', [])
    # 根据 UUID 查找物品
    item_index = None
    item = None
    for i, inv_item in enumerate(inventory):
        if inv_item.get('uuid') == item_uuid:
            item_index = i
            item = inv_item
            break
    if item_index is None:
        return None, "物品不存在或已变化"
    valid, msg = validate_item(item)
    if not valid:
        return None, msg

    conn = get_db()
    try:
        c = conn.cursor()
        c.execute('SELECT COUNT(*) FROM auction_items WHERE seller_character_name = ? AND status IN ("active", "expired")', (character_name,))
        if c.fetchone()[0] >= 5:
            return None, "同时最多进行5个拍卖"

        deposit = calc_deposit(starting_price)
        if state['player']['gold'] < deposit:
            return None, f"保证金不足，需要{deposit}金币"

        state['player']['gold'] -= deposit
        removed_item = inventory.pop(item_index)

        now = int(time.time())
        end_time = now + duration_hours * 3600
        c.execute('''
            INSERT INTO auction_items
            (seller_character_name, seller_user_id, item_json, starting_price, current_price,
             min_increment, buyout_price, start_time, end_time, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ''', (character_name, user_id, json.dumps(removed_item),
              starting_price, starting_price, min_increment,
              buyout_price, now, end_time, now))
        conn.commit()

        send_private(character_name, f'您成功上架拍卖品 [{removed_item["name"]}]，起拍价 {starting_price} 金币，扣除保证金 {deposit} 金币。')
        send_public(f'📢 玩家 [{character_name}] 上架 [{removed_item["name"]}] 到拍卖行，起拍价 {starting_price} 金币！')
        return state, "上架成功"
    except Exception as e:
        logger.exception("上架拍卖异常")
        return None, str(e)
    finally:
        close_game_connection(conn)

def place_bid_logic(state, params, user_id):
    auction_id = params.get('auction_id')
    character_name = params.get('character_name')
    bid_amount = params.get('bid_amount')

    if not all([auction_id, character_name, bid_amount]):
        return None, "参数不足"
    try:
        auction_id = int(auction_id)
        bid_amount = int(bid_amount)
        if bid_amount <= 0:
            raise ValueError
    except:
        return None, "出价必须为正整数"

    conn = get_db()
    try:
        conn.row_factory = sqlite3.Row
        conn.execute('BEGIN IMMEDIATE')
        c = conn.cursor()

        c.execute('SELECT * FROM auction_items WHERE id = ? AND status = "active"', (auction_id,))
        auction = c.fetchone()
        if not auction:
            conn.rollback()
            return None, "拍卖不存在或已结束"
        if character_name == auction['seller_character_name']:
            conn.rollback()
            return None, "不能对自己的拍卖出价"
        now = int(time.time())
        if now >= auction['end_time']:
            conn.rollback()
            return None, "拍卖已结束"

        required_min = auction['current_price'] + auction['min_increment']
        if bid_amount < required_min:
            conn.rollback()
            return None, f"出价至少为 {required_min} 金币"

        # 只验证金币，不扣款
        if state['player']['gold'] < bid_amount:
            conn.rollback()
            return None, "金币不足"

        c.execute('''INSERT INTO auction_bids (auction_id, bidder_character_name, bidder_user_id, bid_amount, bid_time)
                     VALUES (?, ?, ?, ?, ?)''', (auction_id, character_name, user_id, bid_amount, now))
        c.execute('UPDATE auction_items SET current_price = ?, current_bidder = ? WHERE id = ?',
                  (bid_amount, character_name, auction_id))
        conn.commit()

        item = json.loads(auction['item_json'])
        send_private(character_name, f'您已成功出价 {bid_amount} 金币竞拍 [{item["name"]}]')
        if auction['current_bidder'] and auction['current_bidder'] != character_name:
            send_private(auction['current_bidder'], f'您在拍卖 [{item["name"]}] 中的出价已被 [{character_name}] 超过')
        send_private(auction['seller_character_name'], f'您的拍卖品 [{item["name"]}] 有新出价 {bid_amount} 金币')
        if bid_amount >= 1000000:
            send_public(f'💰 玩家 [{character_name}] 对 [{item["name"]}] 出价 {bid_amount} 金币！')

        return state, "出价成功"
    except Exception as e:
        if conn:
            conn.rollback()
        logger.exception("出价异常")
        return None, str(e)
    finally:
        if conn:
            close_game_connection(conn)

def buyout_auction_logic(state, params, user_id):
    auction_id = params.get('auction_id')
    buyer_name = params.get('character_name')
    if not auction_id or not buyer_name:
        return None, "参数不足"
    try:
        auction_id = int(auction_id)
    except ValueError:
        return None, "无效的拍卖ID"

    conn = get_db()
    try:
        conn.row_factory = sqlite3.Row
        conn.execute('BEGIN IMMEDIATE')
        c = conn.cursor()
        c.execute('SELECT * FROM auction_items WHERE id = ? AND status = "active"', (auction_id,))
        auction = c.fetchone()
        if not auction:
            conn.rollback()
            return None, "拍卖品不存在或已结束"
        if not auction['buyout_price']:
            conn.rollback()
            return None, "该拍卖未设置一口价"
        if buyer_name == auction['seller_character_name']:
            conn.rollback()
            return None, "不能购买自己的拍卖品"

        if state['player']['gold'] < auction['buyout_price']:
            conn.rollback()
            return None, "金币不足"
        if len(state.get('inventory', [])) >= state.get('maxInventorySlots', 500):
            conn.rollback()
            return None, "背包已满"

        seller_name = auction['seller_character_name']
        seller_state = player_state_manager.get(seller_name)
        if seller_state is None:
            seller_state, _ = load_state_from_tables(seller_name)
            if seller_state is None:
                conn.rollback()
                return None, "卖家数据异常"
            player_state_manager.set(seller_name, seller_state)

        item = json.loads(auction['item_json'])

        # 买家扣款、获得物品
        state['player']['gold'] -= auction['buyout_price']
        state.setdefault('inventory', []).append(item)

        # 卖家获得金币（扣除手续费）并退还保证金
        fee = math.floor(auction['buyout_price'] * 0.05)
        seller_gain = auction['buyout_price'] - fee
        deposit = calc_deposit(auction['starting_price'])
        seller_state['player']['gold'] += seller_gain + deposit

        now = int(time.time())
        c.execute('''INSERT INTO auction_bids (auction_id, bidder_character_name, bidder_user_id, bid_amount, bid_time)
                     VALUES (?, ?, ?, ?, ?)''', (auction_id, buyer_name, user_id, auction['buyout_price'], now))
        c.execute('UPDATE auction_items SET status = "sold", current_price = ?, current_bidder = ? WHERE id = ?',
                  (auction['buyout_price'], buyer_name, auction_id))
        conn.commit()

        player_state_manager.set(buyer_name, state)
        player_state_manager.set(seller_name, seller_state)

        send_private(buyer_name, f'您以一口价 {auction["buyout_price"]} 金币购得 [{item["name"]}]')
        send_private(seller_name, f'您的拍卖品 [{item["name"]}] 被一口价 {auction["buyout_price"]} 金币买走，获得 {seller_gain} 金币（已扣5%手续费），保证金已退还')
        if auction['current_bidder'] and auction['current_bidder'] != buyer_name:
            send_private(auction['current_bidder'], f'您竞拍的 [{item["name"]}] 已被 [{buyer_name}] 一口价买走')
        send_public(f'💰 玩家 [{buyer_name}] 以一口价 {auction["buyout_price"]} 金币购得 [{item["name"]}]！')

        return state, "购买成功"
    except Exception as e:
        if conn:
            conn.rollback()
        logger.exception("一口价购买异常")
        return None, str(e)
    finally:
        if conn:
            close_game_connection(conn)

def cancel_auction_logic(state, params, user_id):
    auction_id = params.get('auction_id')
    character_name = params.get('character_name')
    if not auction_id or not character_name:
        return None, "参数不足"
    try:
        auction_id = int(auction_id)
    except ValueError:
        return None, "无效拍卖ID"

    conn = get_db()
    try:
        conn.execute('BEGIN IMMEDIATE')
        c = conn.cursor()
        c.execute('SELECT * FROM auction_items WHERE id = ?', (auction_id,))
        auction = c.fetchone()
        if not auction:
            conn.rollback()
            return None, "拍卖不存在"
        if character_name != state.get('player', {}).get('name') or auction[2] != user_id:
            conn.rollback()
            return None, "无权操作"
        if auction[11] != 'active':
            conn.rollback()
            return None, "该拍卖已结束，无法取消"
        c.execute('SELECT COUNT(*) FROM auction_bids WHERE auction_id = ?', (auction_id,))
        if c.fetchone()[0] > 0:
            conn.rollback()
            return None, "已有出价，无法取消"

        item = json.loads(auction[3])
        if len(state.get('inventory', [])) >= state.get('maxInventorySlots', 500):
            conn.rollback()
            return None, "背包已满，无法取回物品"

        state['inventory'].append(item)
        deposit = calc_deposit(auction[4])
        state['player']['gold'] += deposit

        c.execute('DELETE FROM auction_items WHERE id=?', (auction_id,))
        c.execute('DELETE FROM auction_bids WHERE auction_id=?', (auction_id,))
        conn.commit()

        send_private(character_name, f'您已取消拍卖 [{item["name"]}]，物品已返还背包，保证金 {deposit} 金币已退还')
        return state, "拍卖已取消"
    except Exception as e:
        if conn:
            conn.rollback()
        logger.exception("取消拍卖异常")
        return None, str(e)
    finally:
        if conn:
            close_game_connection(conn)

def claim_expired_logic(state, params, user_id):
    auction_id = params.get('auction_id')
    character_name = params.get('character_name')
    if not auction_id or not character_name:
        return None, "参数不足"
    try:
        auction_id = int(auction_id)
    except ValueError:
        return None, "无效拍卖ID"

    conn = get_db()
    try:
        conn.execute('BEGIN IMMEDIATE')
        c = conn.cursor()
        c.execute('SELECT * FROM auction_items WHERE id = ?', (auction_id,))
        auction = c.fetchone()
        if not auction:
            conn.rollback()
            return None, "拍卖不存在"
        if character_name != state.get('player', {}).get('name') or auction[2] != user_id:
            conn.rollback()
            return None, "无权操作"
        if auction[11] != 'expired':
            conn.rollback()
            return None, "该拍卖状态不是流拍，无法领取"

        item = json.loads(auction[3])
        if len(state.get('inventory', [])) >= state.get('maxInventorySlots', 500):
            conn.rollback()
            return None, "背包已满，无法取回物品"

        state['inventory'].append(item)
        c.execute('DELETE FROM auction_items WHERE id=?', (auction_id,))
        c.execute('DELETE FROM auction_bids WHERE auction_id=?', (auction_id,))
        conn.commit()

        send_private(character_name, f'您已成功领取流拍物品 [{item["name"]}]')
        return state, "流拍物品已取回"
    except Exception as e:
        if conn:
            conn.rollback()
        logger.exception("领取流拍物品异常")
        return None, str(e)
    finally:
        if conn:
            close_game_connection(conn)