"""
Client ApolloX V2 On-Chain
Utilise Web3 pour interagir avec les smart contracts de futures on-chain
"""

import logging
from typing import Dict, List, Optional, Tuple
from decimal import Decimal
from web3 import Web3
from eth_account import Account
from eth_account.signers.local import LocalAccount
import json

# Position tracking
from position_manager import get_position_manager

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Constantes
BSC_RPC_URL = "https://bsc-dataseed.binance.org/"
BSC_CHAIN_ID = 56

# Contrats ApolloX V2 sur BSC
APOLLOX_V2_CONTRACT = "0x1b6F2d3844C6ae7D56ceb3C3643b9060ba28FEb0"
APOLLOX_ORACLE_CONTRACT = "0x7B2d74D4C65D5720Fb765adA374cc44a66928e08"

# Tokens
USDT_BSC = "0x55d398326f99059fF775485246999027B3197955"
BUSD_BSC = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"

# ABI partiel pour les fonctions essentielles
APOLLOX_V2_ABI = json.loads('''[
    {
        "inputs": [
            {
                "components": [
                    {"internalType": "address", "name": "pairBase", "type": "address"},
                    {"internalType": "bool", "name": "isLong", "type": "bool"},
                    {"internalType": "address", "name": "tokenIn", "type": "address"},
                    {"internalType": "uint96", "name": "amountIn", "type": "uint96"},
                    {"internalType": "uint80", "name": "qty", "type": "uint80"},
                    {"internalType": "uint64", "name": "price", "type": "uint64"},
                    {"internalType": "uint64", "name": "stopLoss", "type": "uint64"},
                    {"internalType": "uint64", "name": "takeProfit", "type": "uint64"},
                    {"internalType": "uint24", "name": "broker", "type": "uint24"}
                ],
                "internalType": "struct IBook.OpenDataInput",
                "name": "data",
                "type": "tuple"
            }
        ],
        "name": "openMarketTrade",
        "outputs": [{"internalType": "bytes32", "name": "tradeHash", "type": "bytes32"}],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [{"internalType": "bytes32", "name": "tradeHash", "type": "bytes32"}],
        "name": "closeTrade",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [
            {"internalType": "bytes32", "name": "tradeHash", "type": "bytes32"},
            {"internalType": "uint96", "name": "amount", "type": "uint96"}
        ],
        "name": "addMargin",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [
            {"internalType": "bytes32", "name": "tradeHash", "type": "bytes32"},
            {"internalType": "uint64", "name": "takeProfit", "type": "uint64"},
            {"internalType": "uint64", "name": "stopLoss", "type": "uint64"}
        ],
        "name": "updateTradeTpAndSl",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [{"internalType": "address", "name": "user", "type": "address"}],
        "name": "getPositionsV2",
        "outputs": [
            {
                "components": [
                    {"internalType": "address", "name": "user", "type": "address"},
                    {"internalType": "uint32", "name": "userOpenTradeIndex", "type": "uint32"},
                    {"internalType": "uint64", "name": "entryPrice", "type": "uint64"},
                    {"internalType": "address", "name": "pairBase", "type": "address"},
                    {"internalType": "address", "name": "tokenIn", "type": "address"},
                    {"internalType": "uint96", "name": "margin", "type": "uint96"},
                    {"internalType": "uint64", "name": "stopLoss", "type": "uint64"},
                    {"internalType": "uint64", "name": "takeProfit", "type": "uint64"},
                    {"internalType": "uint24", "name": "broker", "type": "uint24"},
                    {"internalType": "bool", "name": "isLong", "type": "bool"},
                    {"internalType": "uint96", "name": "openFee", "type": "uint96"},
                    {"internalType": "int256", "name": "longAccFundingFeePerShare", "type": "int256"},
                    {"internalType": "uint96", "name": "executionFee", "type": "uint96"},
                    {"internalType": "uint40", "name": "timestamp", "type": "uint40"},
                    {"internalType": "uint80", "name": "qty", "type": "uint80"},
                    {"internalType": "uint40", "name": "holdingFeeRate", "type": "uint40"},
                    {"internalType": "uint256", "name": "openBlock", "type": "uint256"}
                ],
                "internalType": "struct ITrading.OpenTrade[]",
                "name": "",
                "type": "tuple[]"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [{"internalType": "bytes32", "name": "tradeHash", "type": "bytes32"}],
        "name": "getPositionByHashV2",
        "outputs": [
            {
                "components": [
                    {"internalType": "address", "name": "user", "type": "address"},
                    {"internalType": "uint32", "name": "userOpenTradeIndex", "type": "uint32"},
                    {"internalType": "uint64", "name": "entryPrice", "type": "uint64"},
                    {"internalType": "address", "name": "pairBase", "type": "address"},
                    {"internalType": "address", "name": "tokenIn", "type": "address"},
                    {"internalType": "uint96", "name": "margin", "type": "uint96"},
                    {"internalType": "uint64", "name": "stopLoss", "type": "uint64"},
                    {"internalType": "uint64", "name": "takeProfit", "type": "uint64"},
                    {"internalType": "uint24", "name": "broker", "type": "uint24"},
                    {"internalType": "bool", "name": "isLong", "type": "bool"},
                    {"internalType": "uint96", "name": "openFee", "type": "uint96"},
                    {"internalType": "int256", "name": "longAccFundingFeePerShare", "type": "int256"},
                    {"internalType": "uint96", "name": "executionFee", "type": "uint96"},
                    {"internalType": "uint40", "name": "timestamp", "type": "uint40"},
                    {"internalType": "uint80", "name": "qty", "type": "uint80"},
                    {"internalType": "uint40", "name": "holdingFeeRate", "type": "uint40"},
                    {"internalType": "uint256", "name": "openBlock", "type": "uint256"}
                ],
                "internalType": "struct ITrading.OpenTrade",
                "name": "",
                "type": "tuple"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [{"internalType": "bytes32", "name": "tradeHash", "type": "bytes32"}],
        "name": "getPendingTrade",
        "outputs": [
            {
                "components": [
                    {"internalType": "address", "name": "user", "type": "address"},
                    {"internalType": "uint64", "name": "price", "type": "uint64"},
                    {"internalType": "address", "name": "pairBase", "type": "address"},
                    {"internalType": "uint96", "name": "margin", "type": "uint96"},
                    {"internalType": "uint64", "name": "stopLoss", "type": "uint64"},
                    {"internalType": "uint64", "name": "takeProfit", "type": "uint64"},
                    {"internalType": "uint24", "name": "broker", "type": "uint24"},
                    {"internalType": "bool", "name": "isLong", "type": "bool"},
                    {"internalType": "uint80", "name": "qty", "type": "uint80"},
                    {"internalType": "uint256", "name": "blockNumber", "type": "uint256"}
                ],
                "internalType": "struct ITrading.PendingTrade",
                "name": "",
                "type": "tuple"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [{"internalType": "bytes32", "name": "tradeHash", "type": "bytes32"}],
        "name": "cancelPendingTrade",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    }
]''')

# Pairs disponibles (adresses pairBase = adresses des tokens)
PAIRS = {
    'BTC': '0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c',  # BTCB Standard (max 50x)
    'BTCUSD_DEGEN': '0xD89aE81e55b1Bd46591CB5555209fdF8fFb43b76',  # BTCUSD Degen Mode (max 1001x)
    'ETH': '0x2170ed0880ac9a755fd29b2688956bd959f933f8',  # ETH BEP-20
    'BNB': '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',  # WBNB
    'SOL': '0x570A5D26f7765Ecb712C0924E4De545B89fd43dF'   # SOL BEP-20
}


class ApolloXV2Client:
    """
    Client pour interagir avec ApolloX V2 on-chain perpetuals
    """

    def __init__(self, private_key: str = None, rpc_url: str = BSC_RPC_URL):
        """
        Initialise le client Web3

        Args:
            private_key: Clé privée du wallet (avec 0x)
            rpc_url: URL RPC BSC
        """
        self.w3 = Web3(Web3.HTTPProvider(rpc_url))

        if not self.w3.is_connected():
            raise ConnectionError("Impossible de se connecter au réseau BSC")

        logger.info(f"✅ Connecté à BSC (chain_id: {self.w3.eth.chain_id})")

        # Account
        self.account: Optional[LocalAccount] = None
        if private_key:
            self.account = Account.from_key(private_key)
            logger.info(f"✅ Wallet chargé: {self.account.address}")

            # Initialiser position manager
            try:
                self.position_manager = get_position_manager(
                    wallet_address=self.account.address,
                    storage_file='positions.json'
                )
            except Exception as e:
                logger.warning(f"⚠️  Position manager non disponible: {e}")
                self.position_manager = None
        else:
            logger.warning("⚠️  Aucune clé privée - mode lecture seule")
            self.position_manager = None

        # Contrats
        self.contract = self.w3.eth.contract(
            address=Web3.to_checksum_address(APOLLOX_V2_CONTRACT),
            abi=APOLLOX_V2_ABI
        )

        logger.info("✅ Client ApolloX V2 initialisé")

    # ==================== LECTURE ====================

    def get_balance(self) -> float:
        """Récupère la balance BNB du wallet"""
        if not self.account:
            return 0.0

        balance_wei = self.w3.eth.get_balance(self.account.address)
        return float(self.w3.from_wei(balance_wei, 'ether'))

    def get_usdt_balance(self) -> float:
        """Récupère la balance USDT"""
        if not self.account:
            return 0.0

        # ABI minimal pour balanceOf
        erc20_abi = json.loads('[{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"type":"function"}]')

        usdt_contract = self.w3.eth.contract(
            address=Web3.to_checksum_address(USDT_BSC),
            abi=erc20_abi
        )

        balance = usdt_contract.functions.balanceOf(self.account.address).call()
        return float(balance / 1e18)

    def get_positions(self) -> List[Dict]:
        """Récupère les positions ouvertes"""
        if not self.account:
            return []

        try:
            # Récupérer les trade hashes
            trade_hashes = self.contract.functions.getPositionsV2(self.account.address).call()

            positions = []
            for trade_hash in trade_hashes:
                try:
                    # Récupérer les détails de chaque position
                    position = self.contract.functions.getPositionByHashV2(trade_hash).call()

                    positions.append({
                        'tradeHash': position[0].hex(),
                        'user': position[1],
                        'pairBase': position[2],
                        'isLong': position[3],
                        'margin': position[4] / 1e18,
                        'qty': position[5] / 1e10,
                        'entryPrice': position[6] / 1e8,
                        'stopLoss': position[7] / 1e8,
                        'takeProfit': position[8] / 1e8
                    })
                except Exception as e:
                    logger.warning(f"Erreur récupération position {trade_hash.hex()}: {e}")
                    continue

            return positions

        except Exception as e:
            logger.error(f"Erreur get_positions: {e}")
            return []

    # ==================== TRADING ====================

    def open_position(
        self,
        pair: str,
        is_long: bool,
        margin_usd: float,
        leverage: int,
        price: float,
        stop_loss: float = 0,
        take_profit: float = 0
    ) -> Optional[str]:
        """
        Ouvre une position on-chain

        IMPORTANT: SL/TP sont gérés côté BOT, pas on-chain
        Le contrat reçoit des valeurs très larges (jamais atteintes)
        Le bot ferme la position quand le vrai SL/TP est atteint

        Args:
            pair: 'BTC', 'ETH', etc.
            is_long: True pour LONG, False pour SHORT
            margin_usd: Montant de margin en USD
            leverage: Leverage souhaité
            price: Prix d'entrée (pour vérification)
            stop_loss: Prix de stop-loss BOT (stocké pour gestion bot)
            take_profit: Prix de take-profit BOT (stocké pour gestion bot)

        Returns:
            Trade hash ou None si erreur
        """
        if not self.account:
            logger.error("Pas de wallet configuré")
            return None

        try:
            # Sauvegarder les vrais SL/TP pour gestion bot
            bot_stop_loss = stop_loss
            bot_take_profit = take_profit

            # Conversion des paramètres (selon doc Aster: prix en 1e8, qty en 1e10, margin en 1e18)
            pair_address = Web3.to_checksum_address(PAIRS.get(pair, '0x'))
            amount_in = int(margin_usd * 1e18)  # USDT 1e18
            qty = int((margin_usd * leverage) / price * 1e10)  # Qty 1e10

            # SLIPPAGE: Ajuster le prix acceptable pour éviter rejets
            SLIPPAGE_PCT = 0.001  # 0.1% tolérance (10 basis points)
            if is_long:
                # LONG: accepter prix jusqu'à +0.1% plus haut
                acceptable_price = price * (1 + SLIPPAGE_PCT)
            else:
                # SHORT: accepter prix jusqu'à -0.1% plus bas
                acceptable_price = price * (1 - SLIPPAGE_PCT)

            price_int = int(acceptable_price * 1e8)  # Prix en 1e8 selon doc Aster

            # ========== SL/TP ON-CHAIN: Valeurs très larges (jamais atteintes) ==========
            # Le bot gère les vrais SL/TP - on met des valeurs acceptées par le contrat
            if is_long:
                # LONG: SL très bas (-20%), TP très haut (+100%)
                onchain_sl = price * 0.80  # -20%
                onchain_tp = price * 2.00  # +100%
            else:
                # SHORT: SL très haut (+20%), TP très bas (-50%)
                onchain_sl = price * 1.20  # +20%
                onchain_tp = price * 0.50  # -50%

            sl_int = int(onchain_sl * 1e8)
            tp_int = int(onchain_tp * 1e8)

            logger.info(f"🔍 OPEN POSITION:")
            logger.info(f"   Price: ${price:.2f} → Acceptable: ${acceptable_price:.2f}")
            logger.info(f"   🤖 BOT SL: ${bot_stop_loss:.2f} | BOT TP: ${bot_take_profit:.2f}")
            logger.info(f"   📡 On-chain SL: ${onchain_sl:.2f} | On-chain TP: ${onchain_tp:.2f} (larges, jamais atteints)")
            logger.info(f"   → Le BOT gère les vrais SL/TP")

            # Struct OpenDataInput
            open_data = (
                pair_address,  # pairBase
                is_long,  # isLong
                Web3.to_checksum_address(USDT_BSC),  # tokenIn
                amount_in,  # amountIn (uint96)
                qty,  # qty (uint80)
                price_int,  # price (uint64)
                sl_int,  # stopLoss (uint64) - large, jamais atteint
                tp_int,  # takeProfit (uint64) - large, jamais atteint
                0  # broker (uint24)
            )

            # Utiliser le gas price du RÉSEAU directement (pas de minimum forcé)
            gas_price = self.w3.eth.gas_price

            logger.info(f"⛽ Gas price: {self.w3.from_wei(gas_price, 'gwei'):.2f} gwei")

            tx = self.contract.functions.openMarketTrade(open_data).build_transaction({
                'from': self.account.address,
                'gas': 500000,
                'gasPrice': gas_price,
                'nonce': self.w3.eth.get_transaction_count(self.account.address),
                'chainId': BSC_CHAIN_ID
            })

            # Signer et envoyer
            signed_tx = self.account.sign_transaction(tx)
            tx_hash = self.w3.eth.send_raw_transaction(signed_tx.raw_transaction)

            logger.info(f"✅ Transaction envoyée: {tx_hash.hex()}")

            # Attendre confirmation
            receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)

            if receipt['status'] == 1:
                logger.info(f"✅ Position ouverte! Gas used: {receipt['gasUsed']}")

                # Extraire trade hash des logs
                # Event signature: 0a6da83417411689fd88436e7fa57a7cf1cf635a35194c0658314d4a037382af
                # Topic 1 = tradeHash (bytes32)
                trade_hash = None
                target_sig = '0a6da83417411689fd88436e7fa57a7cf1cf635a35194c0658314d4a037382af'

                for log in receipt['logs']:
                    if log['address'].lower() == APOLLOX_V2_CONTRACT.lower():
                        # Premier event ApolloX avec au moins 2 topics
                        if len(log['topics']) >= 2:
                            event_sig = log['topics'][0].hex().lower()
                            logger.info(f"🔍 Checking event sig: {event_sig}")

                            if event_sig == target_sig.lower():
                                trade_hash = log['topics'][1].hex()
                                logger.info(f"📝 Trade Hash extrait: {trade_hash}")
                                break

                logger.info(f"🔍 DEBUG: Après extraction trade_hash, valeur: {trade_hash}")

                if not trade_hash:
                    logger.warning(f"⚠️  Trade hash non trouvé dans les logs, utilisation TX hash")
                    trade_hash = tx_hash.hex()

                logger.info(f"🔍 DEBUG: Avant sauvegarde position, position_manager existe: {self.position_manager is not None}")

                # Sauvegarder la position
                if self.position_manager:
                    logger.info(f"🔍 DEBUG: Entré dans le if position_manager")
                    try:
                        logger.info(f"🔍 DEBUG: Avant appel add_position")
                        self.position_manager.add_position(
                            trade_hash=trade_hash,
                            tx_hash=tx_hash.hex(),
                            symbol=pair,
                            pair_address=pair_address,
                            is_long=is_long,
                            margin=margin_usd,
                            leverage=leverage,
                            entry_price=price,
                            qty=margin_usd * leverage / price,
                            stop_loss=bot_stop_loss,  # SL géré par le BOT
                            take_profit=bot_take_profit,  # TP géré par le BOT
                            block_number=receipt['blockNumber']
                        )
                        logger.info(f"🔍 DEBUG: Après appel add_position, succès!")
                    except Exception as e:
                        logger.warning(f"⚠️  Erreur sauvegarde position: {e}")
                        logger.error(f"🔍 DEBUG: Exception dans add_position: {e}", exc_info=True)

                logger.info(f"🔍 DEBUG: Avant return trade_hash")
                return trade_hash
            else:
                # Transaction failed - essayer de récupérer la raison
                logger.error("❌ Transaction failed")
                logger.error(f"   TX Hash: {tx_hash.hex()}")
                logger.error(f"   Gas used: {receipt.get('gasUsed', 'N/A')}")

                # Tenter de récupérer le revert reason
                try:
                    self.w3.eth.call(tx, block_identifier=receipt['blockNumber'])
                except Exception as revert_error:
                    logger.error(f"   Revert reason: {str(revert_error)}")

                return None

        except Exception as e:
            logger.error(f"❌ Erreur open_position: {e}")
            import traceback
            logger.error(traceback.format_exc())
            return None

    def get_position_state(self, trade_hash: str) -> Optional[Dict]:
        """
        Récupère l'état d'une position on-chain

        Args:
            trade_hash: Hash de la position (bytes32 en hex)

        Returns:
            Dict avec les infos de la position ou None si n'existe pas
        """
        try:
            # Convertir trade_hash en bytes32
            trade_hash_bytes = bytes.fromhex(trade_hash.replace('0x', ''))

            # Appeler getPositionByHashV2 (view function - pas de gas)
            position = self.contract.functions.getPositionByHashV2(trade_hash_bytes).call()

            # Struct ITrading.OpenTrade (17 champs):
            # 0: address user
            # 1: uint32 userOpenTradeIndex
            # 2: uint64 entryPrice
            # 3: address pairBase
            # 4: address tokenIn
            # 5: uint96 margin
            # 6: uint64 stopLoss
            # 7: uint64 takeProfit
            # 8: uint24 broker
            # 9: bool isLong
            # 10: uint96 openFee
            # 11: int256 longAccFundingFeePerShare
            # 12: uint96 executionFee
            # 13: uint40 timestamp
            # 14: uint80 qty
            # 15: uint40 holdingFeeRate
            # 16: uint256 openBlock

            # Si la position n'existe pas, margin sera 0
            if position[5] == 0:  # margin est index 5
                logger.info(f"⚠️  Position {trade_hash[:10]}... n'existe pas on-chain (margin = 0)")
                return None

            # Décoder les données
            result = {
                'tradeHash': trade_hash,
                'user': position[0],
                'userOpenTradeIndex': position[1],
                'pairBase': position[3],
                'tokenIn': position[4],
                'isLong': position[9],
                'margin': position[5],
                'qty': position[14],
                'entryPrice': position[2] / 1e10,  # Prix en 1e10
                'stopLoss': position[6] / 1e10,
                'takeProfit': position[7] / 1e10,
                'openFee': position[10],
                'executionFee': position[12],
                'timestamp': position[13],
                'openBlock': position[16]
            }

            logger.info(f"📊 Position on-chain:")
            logger.info(f"   Trade Hash: {result['tradeHash'][:16]}...")
            logger.info(f"   User: {result['user']}")
            logger.info(f"   Side: {'LONG' if result['isLong'] else 'SHORT'}")
            logger.info(f"   Margin: {result['margin'] / 1e18:.4f} USDT")
            logger.info(f"   Entry: ${result['entryPrice']:.2f}")
            logger.info(f"   SL: ${result['stopLoss']:.2f}")
            logger.info(f"   TP: ${result['takeProfit']:.2f}")

            return result

        except Exception as e:
            logger.error(f"❌ Erreur get_position_state: {e}")
            return None

    def close_position(self, trade_hash: str) -> bool:
        """
        Ferme une position

        Args:
            trade_hash: Hash de la position (bytes32 en hex)

        Returns:
            True si succès
        """
        if not self.account:
            logger.error("Pas de wallet configuré")
            return False

        try:
            logger.info(f"🔍 Vérification état position avant fermeture...")

            # Vérifier l'état de la position on-chain (optionnel, ne pas bloquer si échec)
            position_state = self.get_position_state(trade_hash)

            if position_state:
                logger.info(f"✅ Position trouvée on-chain")

                # Vérifier que c'est bien notre position
                if position_state['user'].lower() != self.account.address.lower():
                    logger.error(f"❌ Cette position appartient à {position_state['user']}")
                    logger.error(f"   Notre adresse: {self.account.address}")
                    return False
            else:
                # Peut-être problème ABI, on essaie quand même de fermer
                logger.warning(f"⚠️  Impossible de vérifier l'état on-chain (possiblement problème ABI)")
                logger.info(f"   Tentative de fermeture quand même...")

            logger.info(f"📡 Envoi transaction closeTrade...")

            # Convertir trade_hash en bytes32
            trade_hash_bytes = bytes.fromhex(trade_hash.replace('0x', ''))

            # Utiliser le gas price du RÉSEAU directement
            gas_price = self.w3.eth.gas_price

            tx = self.contract.functions.closeTrade(trade_hash_bytes).build_transaction({
                'from': self.account.address,
                'gas': 300000,
                'gasPrice': gas_price,
                'nonce': self.w3.eth.get_transaction_count(self.account.address),
                'chainId': BSC_CHAIN_ID
            })

            # Signer et envoyer
            signed_tx = self.account.sign_transaction(tx)
            tx_hash = self.w3.eth.send_raw_transaction(signed_tx.raw_transaction)

            logger.info(f"✅ Close transaction envoyée: {tx_hash.hex()}")

            # Attendre confirmation
            receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)

            if receipt['status'] == 1:
                logger.info(f"✅ Position fermée! Gas used: {receipt['gasUsed']}")

                # Mettre à jour la position
                if self.position_manager:
                    try:
                        # On ne connaît pas le prix de sortie ici, on met 0 pour l'instant
                        # Le PnL sera calculé plus tard si on a le prix
                        self.position_manager.close_position(
                            trade_hash=trade_hash,
                            close_tx_hash=tx_hash.hex(),
                            exit_price=0,  # À récupérer depuis l'oracle si nécessaire
                            block_number=receipt['blockNumber']
                        )
                    except Exception as e:
                        logger.warning(f"⚠️  Erreur mise à jour position fermée: {e}")

                return True
            else:
                logger.error(f"❌ Close transaction failed (reverted)")
                logger.error(f"   TX Hash: {tx_hash.hex()}")
                logger.error(f"   Gas used: {receipt.get('gasUsed', 'N/A')}")
                logger.error(f"   Trade hash: {trade_hash}")

                # Essayer de récupérer la raison du revert
                try:
                    self.w3.eth.call(tx, block_identifier=receipt['blockNumber'])
                except Exception as revert_error:
                    logger.error(f"   Revert reason: {str(revert_error)}")

                logger.error(f"   Receipt: {receipt}")
                return False

        except Exception as e:
            logger.error(f"❌ Erreur close_position: {e}")
            import traceback
            logger.error(traceback.format_exc())
            return False

    def update_sl_tp(self, trade_hash: str, stop_loss: float, take_profit: float) -> bool:
        """Met à jour stop-loss et take-profit"""
        if not self.account:
            return False

        try:
            trade_hash_bytes = bytes.fromhex(trade_hash.replace('0x', ''))
            sl_int = int(stop_loss * 1e8)
            tp_int = int(take_profit * 1e8)

            tx = self.contract.functions.updateTradeTpAndSl(
                trade_hash_bytes, tp_int, sl_int  # IMPORTANT: TP avant SL!
            ).build_transaction({
                'from': self.account.address,
                'gas': 200000,
                'gasPrice': self.w3.eth.gas_price,
                'nonce': self.w3.eth.get_transaction_count(self.account.address),
                'chainId': BSC_CHAIN_ID
            })

            signed_tx = self.account.sign_transaction(tx)
            tx_hash = self.w3.eth.send_raw_transaction(signed_tx.raw_transaction)
            receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)

            return receipt['status'] == 1

        except Exception as e:
            logger.error(f"❌ Erreur update_sl_tp: {e}")
            return False


if __name__ == '__main__':
    logger.info("="*80)
    logger.info("🧪 TEST CLIENT APOLLOX V2 ON-CHAIN")
    logger.info("="*80)

    # Test en mode lecture seule (sans private key)
    client = ApolloXV2Client()

    logger.info(f"\n📊 Infos réseau:")
    logger.info(f"   Chain ID: {client.w3.eth.chain_id}")
    logger.info(f"   Latest block: {client.w3.eth.block_number}")
    logger.info(f"   Gas price: {client.w3.from_wei(client.w3.eth.gas_price, 'gwei')} gwei")

    logger.info(f"\n📝 Contrat ApolloX V2:")
    logger.info(f"   Address: {APOLLOX_V2_CONTRACT}")
    logger.info(f"   Fonctions disponibles: {len(APOLLOX_V2_ABI)}")

    logger.info("\n✅ Client prêt!")
    logger.info("\n⚠️  Pour trader, fournir une private key:")
    logger.info("   client = ApolloXV2Client(private_key='0x...')")
