#!/usr/bin/env python
"""
==============================================================================
Sophia 智能合约工具库 (contract_toolkit.py)
==============================================================================

高级合约测试与部署工具库，专为合约开发者设计。

与 contract.py 的区别:
  - contract.py:        SDK 核心底层合约调用类 (低级 API)
  - contract_toolkit.py: 高级封装工具库 (业务级 API)

核心功能模块:
  1. CompilerAdapter    - 编译器适配层 (支持 Sophia Compiler 8.x)
  2. NetworkManager     - 网络管理器 (主网/测试网切换)
  3. AccountLoader      - 账户加载器 (Keystore/助记词/私钥)
  4. ContractCompiler   - 合约编译器 (编译/ACI 生成)
  5. ContractDeployer   - 合约部署器 (部署/初始化)
  6. ContractCaller     - 合约调用器 (链上/静态调用)
  7. DeploymentTracker  - 部署追踪器 (记录部署历史)

设计原则:
  - 单一职责: 每个类只负责一个功能
  - 低耦合: 模块间通过接口交互
  - 高内聚: 相关功能聚合在同一模块
  - 易测试: 每个模块可独立测试

使用示例:
  from aeknow.contract_toolkit import (
      NetworkManager, AccountLoader, ContractCompiler, 
      ContractDeployer, ContractCaller
  )
  
  # 1. 网络管理
  network = NetworkManager.testnet()
  client = network.connect()
  
  # 2. 加载账户
  account = AccountLoader.from_mnemonic("word1 word2 ...")
  
  # 3. 编译合约
  compiler = ContractCompiler(network.compiler_url)
  result = compiler.compile_file("counter.aes")
  
  # 4. 部署合约
  deployer = ContractDeployer(client, account)
  contract_id = deployer.deploy(result.bytecode, result.source, init_args=[100])
  
  # 5. 调用合约
  caller = ContractCaller(client, contract_id, result.source)
  value = caller.call_static("get_count")
  caller.call("increment", account=account)

==============================================================================
"""

import json
import time
from typing import List, Dict, Any, Optional, Tuple
from dataclasses import dataclass, asdict
from datetime import datetime
from pathlib import Path

import requests
from aeknow import node, signing, hashing, defaults
from aeknow.contract import Contract


# ==============================================================================
#  数据类定义
# ==============================================================================

@dataclass
class NetworkConfig:
    """
    网络配置
    
    属性:
        name: 网络名称 (mainnet/testnet)
        node_url: 节点 URL
        network_id: 网络 ID
        compiler_url: 编译器 URL
    """
    name: str
    node_url: str
    network_id: str
    compiler_url: str


@dataclass
class CompileResult:
    """
    编译结果
    
    属性:
        bytecode: 编译后的字节码
        aci: ACI (应用合约接口)
        source: 源代码
        warnings: 编译警告列表
        contract_name: 合约名称
        functions: 函数列表
    """
    bytecode: str
    aci: List[Dict]
    source: str
    warnings: List[str] = None
    contract_name: str = ""
    functions: List[Dict] = None
    
    def __post_init__(self):
        """初始化后处理"""
        if self.warnings is None:
            self.warnings = []
        if self.functions is None:
            self.functions = []
        
        # 提取合约名称和函数列表
        if self.aci:
            contract_info = self.aci[-1].get('contract', {})
            self.contract_name = contract_info.get('name', 'Unknown')
            self.functions = contract_info.get('functions', [])


@dataclass
class DeploymentInfo:
    """
    部署信息
    
    属性:
        contract_id: 合约地址
        deployer: 部署者地址
        tx_hash: 部署交易哈希
        network: 网络名称
        timestamp: 部署时间戳
        block_height: 确认区块高度
        source_file: 源文件路径 (可选)
        init_args: 初始化参数 (可选)
        aci: ACI 信息 (可选)
    """
    contract_id: str
    deployer: str
    tx_hash: str
    network: str
    timestamp: str
    block_height: int = 0
    source_file: str = ""
    init_args: List[Any] = None
    aci: List[Dict] = None
    
    def to_dict(self) -> dict:
        """转换为字典"""
        return asdict(self)
    
    @classmethod
    def from_dict(cls, data: dict) -> 'DeploymentInfo':
        """从字典创建"""
        return cls(**data)


@dataclass
class CallResult:
    """
    调用结果
    
    属性:
        success: 是否成功
        tx_hash: 交易哈希 (链上调用)
        gas_used: Gas 消耗
        return_type: 返回类型 (ok/revert/error)
        return_value: 返回值 (编码)
        decoded_value: 解码后的返回值
        error_message: 错误信息 (如果失败)
    """
    success: bool
    gas_used: int
    return_type: str
    return_value: str
    decoded_value: Any = None
    tx_hash: str = ""
    error_message: str = ""


# ==============================================================================
#  1. 编译器适配层
# ==============================================================================

class CompilerAdapter:
    """
    Sophia 编译器适配器
    
    支持 Sophia Compiler 8.x API，提供统一的编译接口。
    
    核心方法:
        - compile: 编译合约源码
        - generate_aci: 生成 ACI
        - encode_calldata: 编码调用数据
        - decode_result: 解码返回值
    
    用法:
        compiler = CompilerAdapter("https://v8.compiler.aepps.com")
        result = compiler.compile(source_code)
        calldata = compiler.encode_calldata(source, "init", arg1, arg2)
    """
    
    def __init__(self, compiler_url: str):
        """
        初始化编译器适配器
        
        :param compiler_url: 编译器 URL (如 https://v8.compiler.aepps.com)
        """
        self.url = compiler_url.rstrip('/')
        self.options = {"backend": "fate"}  # FATE VM 后端
        self.session = requests.Session()  # 复用 HTTP 连接
    
    def compile(self, source: str) -> Dict[str, Any]:
        """
        编译合约源码
        
        :param source: Sophia 源代码
        :return: 编译结果字典 {bytecode, aci, warnings}
        :raises Exception: 编译失败时抛出异常
        """
        try:
            response = self.session.post(
                f'{self.url}/compile',
                json={'code': source, 'options': self.options},
                timeout=3000
            )
            response.raise_for_status()
            return response.json()
        except requests.RequestException as e:
            raise Exception(f"编译请求失败: {e}")
        except Exception as e:
            raise Exception(f"编译失败: {e}")
    
    def generate_aci(self, source: str) -> List[Dict]:
        """
        生成 ACI (Application Contract Interface)
        
        ACI 包含合约的函数签名、类型定义等元数据。
        
        :param source: Sophia 源代码
        :return: ACI 列表
        :raises Exception: 生成失败时抛出异常
        """
        try:
            response = self.session.post(
                f'{self.url}/aci',
                json={'code': source, 'options': self.options},
                timeout=3000
            )
            response.raise_for_status()
            return response.json()
        except requests.RequestException as e:
            raise Exception(f"ACI 生成请求失败: {e}")
        except Exception as e:
            raise Exception(f"ACI 生成失败: {e}")
    
    def _format_sophia_arg(self, arg) -> str:
        """
        格式化 Sophia 函数参数
        
        Sophia 编译器要求：
        - 字符串参数必须用双引号包裹: "MyToken"
        - 数字参数不需要引号: 18
        - 布尔参数: true / false
        - option 类型保持原样: Some(1000) 或 None
        - 地址类型保持原样: ak_xxx
        - 记录类型保持原样: { field = value, ... }
        - 列表类型: [elem1, elem2, ...]
        
        :param arg: 原始参数
        :return: 格式化后的参数字符串
        """
        # 布尔类型：必须在字符串转换之前处理
        if isinstance(arg, bool):
            return 'true' if arg else 'false'
        
        # 数字类型：直接转字符串
        if isinstance(arg, (int, float)):
            return str(arg)
        
        # 列表类型：递归格式化每个元素
        if isinstance(arg, (list, tuple)):
            formatted_items = [self._format_sophia_arg(item) for item in arg]
            return '[' + ', '.join(formatted_items) + ']'
        
        # 字符串类型：需要判断是否添加引号
        s = str(arg)
        
        # 已经有双引号包裹，保持原样
        if s.startswith('"') and s.endswith('"'):
            return s
        
        # Sophia option 类型: Some(...) 或 None
        if s.upper() == 'NONE' or s.upper().startswith('SOME('):
            return s
        
        # Sophia 记录类型: { field = value, ... }
        if s.strip().startswith('{') and s.strip().endswith('}'):
            return s
        
        # Sophia 布尔类型: true / false (字符串格式)
        if s.lower() in ('true', 'false'):
            return s.lower()
        
        # aeknow 地址/标识符: ak_, ct_, ok_, oq_, th_, sg_, ba_, cb_, nm_, pp_, ch_
        if any(s.startswith(prefix) for prefix in ('ak_', 'ct_', 'ok_', 'oq_', 'th_', 'sg_', 'ba_', 'cb_', 'nm_', 'pp_', 'ch_')):
            # 保持原样 - 不再将 ct_ 转换为 ak_
            # 因为 contract 类型参数需要 ct_ 前缀
            return s
        
        # 纯数字字符串，不加引号
        try:
            int(s)
            return s
        except ValueError:
            pass
        
        # 其他情况: 添加双引号 (作为 Sophia 字符串)
        return f'"{s}"'
    
    def encode_calldata(self, source: str, function: str, *args) -> str:
        """
        编码函数调用数据
        
        将函数名和参数编码为字节码格式的 calldata，用于合约调用。
        
        :param source: Sophia 源代码
        :param function: 函数名 (如 "init", "get_count")
        :param args: 函数参数列表
        :return: 编码后的 calldata 字符串
        :raises Exception: 编码失败时抛出异常
        """
        try:
            # 重要：正确格式化参数 (Sophia 字符串需要双引号)
            str_args = [self._format_sophia_arg(v) for v in args]
            
            response = self.session.post(
                f'{self.url}/encode-calldata',
                json={
                    'source': source,
                    'function': function,
                    'arguments': str_args,
                    'options': self.options
                },
                timeout=3000
            )
            
            # 检查响应状态，显示详细错误
            if response.status_code != 200:
                error_msg = f"HTTP {response.status_code}"
                try:
                    error_msg += f": {response.text[:500]}"
                except:
                    pass
                raise Exception(f"Calldata 编码失败: {error_msg}")
            
            response.raise_for_status()
            data = response.json()
            # 兼容不同版本的返回格式
            return data.get('calldata', data) if isinstance(data, dict) else data
        except requests.RequestException as e:
            # 显示详细错误信息
            error_detail = ''
            if hasattr(e, 'response') and e.response is not None:
                try:
                    error_detail = f"\n响应内容: {e.response.text[:500]}"
                except:
                    pass
            raise Exception(f"Calldata 编码请求失败: {e}{error_detail}")
        except Exception as e:
            raise Exception(f"Calldata 编码失败: {e}")
    
    def decode_result(self, source: str, function: str, 
                     call_value: str, call_result: str = "ok") -> Any:
        """
        解码函数返回值
        
        将编码的返回值解码为 Python 对象。
        
        :param source: Sophia 源代码
        :param function: 函数名
        :param call_value: 编码的返回值
        :param call_result: 调用结果类型 (ok/revert/error)
        :return: 解码后的 Python 对象
        :raises Exception: 解码失败时抛出异常
        """
        try:
            response = self.session.post(
                f'{self.url}/decode-call-result',
                json={
                    'source': source,
                    'function': function,
                    'call-result': call_result,
                    'call-value': call_value,
                    'options': self.options
                },
                timeout=3000
            )
            response.raise_for_status()
            return response.json()
        except requests.RequestException as e:
            raise Exception(f"结果解码请求失败: {e}")
        except Exception as e:
            raise Exception(f"结果解码失败: {e}")


# ==============================================================================
#  2. 网络管理器
# ==============================================================================

class NetworkManager:
    """
    网络管理器
    
    管理不同区块链网络的配置和连接。
    
    预定义网络:
        - mainnet: 主网
        - testnet: 测试网
    
    用法:
        # 方式 1: 使用预定义网络
        network = NetworkManager.mainnet()
        
        # 方式 2: 自定义网络
        custom_config = NetworkConfig(
            name="custom",
            node_url="http://localhost:3013",
            network_id="ae_custom",
            compiler_url="http://localhost:3080"
        )
        network = NetworkManager(custom_config)
        
        # 连接到节点
        client = network.connect()
    """
    
    # 预定义网络配置
    MAINNET = NetworkConfig(
        name='mainnet',
        node_url='https://sdk-mainnet.aepps.com',
        network_id='ae_mainnet',
        compiler_url='https://v8.compiler.aepps.com'
    )
    
    TESTNET = NetworkConfig(
        name='testnet',
        node_url='https://testnet.aeknow.io',
        network_id='ae_uat',
        compiler_url='https://v8.compiler.aepps.com'
    )
    
    def __init__(self, config: NetworkConfig):
        """
        初始化网络管理器
        
        :param config: 网络配置对象
        """
        self.config = config
        self._client = None
    
    @classmethod
    def mainnet(cls) -> 'NetworkManager':
        """创建主网管理器"""
        return cls(cls.MAINNET)
    
    @classmethod
    def testnet(cls) -> 'NetworkManager':
        """创建测试网管理器"""
        return cls(cls.TESTNET)
    
    @classmethod
    def custom(cls, node_url: str, network_id: str, 
               compiler_url: str = None, name: str = "custom") -> 'NetworkManager':
        """
        创建自定义网络管理器
        
        :param node_url: 节点 URL
        :param network_id: 网络 ID
        :param compiler_url: 编译器 URL (可选)
        :param name: 网络名称
        :return: NetworkManager 实例
        """
        config = NetworkConfig(
            name=name,
            node_url=node_url,
            network_id=network_id,
            compiler_url=compiler_url or cls.TESTNET.compiler_url
        )
        return cls(config)
    
    def connect(self) -> node.NodeClient:
        """
        连接到区块链节点
        
        :return: NodeClient 实例
        :raises Exception: 连接失败时抛出异常
        """
        if self._client is None:
            try:
                config = node.Config(
                    external_url=self.config.node_url,
                    internal_url=self.config.node_url,  # dry-run 需要
                    network_id=self.config.network_id
                )
                self._client = node.NodeClient(config)
            except Exception as e:
                raise Exception(f"连接节点失败: {e}")
        
        return self._client
    
    @property
    def compiler_url(self) -> str:
        """获取编译器 URL"""
        return self.config.compiler_url
    
    @property
    def network_name(self) -> str:
        """获取网络名称"""
        return self.config.name
    
    def get_status(self) -> Dict[str, Any]:
        """
        获取节点状态
        
        :return: 节点状态信息
        """
        client = self.connect()
        try:
            status = client.get_status()
            return {
                'network': self.network_name,
                'node_version': status.node_version,
                'syncing': status.syncing,
                'sync_progress': status.sync_progress,
                'top_block_height': status.top_block_height
            }
        except Exception as e:
            return {
                'network': self.network_name,
                'error': str(e)
            }


# ==============================================================================
#  3. 账户加载器
# ==============================================================================

class AccountLoader:
    """
    账户加载器
    
    提供多种方式加载区块链账户。
    
    支持的加载方式:
        1. Keystore 文件 (加密的 JSON 文件)
        2. 助记词 (BIP39 12/24 个单词)
        3. 私钥 (十六进制字符串)
        4. 生成新账户 (随机)
    
    用法:
        # 从 Keystore 加载
        account = AccountLoader.from_keystore("wallet.json", "password")
        
        # 从助记词加载
        account = AccountLoader.from_mnemonic("word1 word2 ...")
        
        # 生成新账户
        account = AccountLoader.generate()
        
        # 获取账户信息
        print(f"地址: {account.get_address()}")
    """
    
    @staticmethod
    def from_keystore(path: str, password: str) -> signing.Account:
        """
        从 Keystore 文件加载账户
        
        Keystore 是加密的 JSON 文件，需要密码解密。
        
        :param path: Keystore 文件路径
        :param password: 解密密码
        :return: Account 对象
        :raises ValueError: 加载失败时抛出异常
        """
        try:
            return signing.Account.from_keystore(path, password)
        except Exception as e:
            raise ValueError(f"从 Keystore 加载账户失败: {e}")
    
    @staticmethod
    def from_mnemonic(mnemonic: str, account_index: int = 0, 
                     address_index: int = 0) -> signing.Account:
        """
        从 BIP39 助记词加载账户
        
        使用标准 BIP44 派生路径: m/44'/457'/account'/0'/address'
        
        :param mnemonic: BIP39 助记词 (12 或 24 个单词，空格分隔)
        :param account_index: 账户索引 (默认 0)
        :param address_index: 地址索引 (默认 0)
        :return: Account 对象
        :raises ValueError: 加载失败时抛出异常
        """
        try:
            from aeknow.hdwallet import HDWallet
            wallet = HDWallet(mnemonic)
            
            if account_index == 0 and address_index == 0:
                # 使用默认主账户
                return wallet.get_master_account()
            else:
                # 派生指定路径的账户
                _, account = wallet.derive_child(account_index, address_index)
                return account
        except Exception as e:
            raise ValueError(f"从助记词加载账户失败: {e}")
    
    @staticmethod
    def from_private_key(private_key: str) -> signing.Account:
        """
        从私钥加载账户
        
        :param private_key: 私钥 (十六进制字符串或 sk_xxx 格式)
        :return: Account 对象
        :raises ValueError: 加载失败时抛出异常
        """
        try:
            return signing.Account.from_secret_key_string(private_key)
        except Exception as e:
            raise ValueError(f"从私钥加载账户失败: {e}")
    
    @staticmethod
    def generate() -> signing.Account:
        """
        生成新的随机账户
        
        :return: Account 对象
        """
        return signing.Account.generate()
    
    @staticmethod
    def get_balance(client: node.NodeClient, account: signing.Account) -> int:
        """
        获取账户余额
        
        :param client: 节点客户端
        :param account: 账户对象
        :return: 余额 (单位: aettos, 1 AE = 10^18 aettos)
        """
        try:
            acc_info = client.get_account_by_pubkey(pubkey=account.get_address())
            return acc_info.balance
        except:
            return 0


# ==============================================================================
#  4. 合约编译器
# ==============================================================================

class ContractCompiler:
    """
    合约编译器
    
    封装编译器适配器，提供高级编译功能。
    
    核心功能:
        - 编译文件
        - 编译源码
        - 提取合约信息
        - 保存编译结果
    
    用法:
        compiler = ContractCompiler("https://v8.compiler.aepps.com")
        
        # 编译文件
        result = compiler.compile_file("counter.aes")
        
        # 查看合约信息
        print(f"合约名称: {result.contract_name}")
        for func in result.functions:
            print(f"  - {func['name']}")
    """
    
    def __init__(self, compiler_url: str):
        """
        初始化合约编译器
        
        :param compiler_url: 编译器 URL
        """
        self.adapter = CompilerAdapter(compiler_url)
    
    def compile_file(self, filepath: str) -> CompileResult:
        """
        编译合约文件
        
        :param filepath: 合约文件路径 (.aes)
        :return: CompileResult 对象
        :raises FileNotFoundError: 文件不存在
        :raises Exception: 编译失败
        """
        if not Path(filepath).exists():
            raise FileNotFoundError(f"合约文件不存在: {filepath}")
        
        with open(filepath, 'r', encoding='utf-8') as f:
            source = f.read()
        
        return self.compile_source(source)
    
    def compile_source(self, source: str) -> CompileResult:
        """
        编译合约源码
        
        :param source: Sophia 源代码
        :return: CompileResult 对象
        :raises Exception: 编译失败
        """
        result_data = self.adapter.compile(source)
        
        return CompileResult(
            bytecode=result_data.get('bytecode', ''),
            aci=result_data.get('aci', []),
            source=source,
            warnings=result_data.get('warnings', [])
        )
    
    def save_result(self, result: CompileResult, output_path: str):
        """
        保存编译结果到文件
        
        :param result: 编译结果
        :param output_path: 输出文件路径 (.json)
        """
        data = {
            'bytecode': result.bytecode,
            'aci': result.aci,
            'source': result.source,
            'warnings': result.warnings,
            'contract_name': result.contract_name
        }
        
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
    
    @staticmethod
    def load_result(input_path: str) -> CompileResult:
        """
        从文件加载编译结果
        
        :param input_path: 编译结果文件路径 (.json)
        :return: CompileResult 对象
        """
        with open(input_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        
        return CompileResult(
            bytecode=data.get('bytecode', ''),
            aci=data.get('aci', []),
            source=data.get('source', ''),
            warnings=data.get('warnings', [])
        )


# ==============================================================================
#  5. 合约部署器
# ==============================================================================

class ContractDeployer:
    """
    合约部署器
    
    负责将编译后的合约部署到区块链。
    
    核心功能:
        - 部署合约
        - 等待交易确认
        - 记录部署信息
    
    用法:
        deployer = ContractDeployer(client, account)
        
        # 部署合约
        contract_id = deployer.deploy(
            bytecode=compile_result.bytecode,
            source=compile_result.source,
            init_args=[100],  # init 参数
            gas=100000
        )
        
        print(f"合约地址: {contract_id}")
    """
    
    DEFAULT_GAS = 100000
    DEFAULT_GAS_PRICE = 1000000000  # 1 Gwei (默认最低价)
    
    def __init__(self, client: node.NodeClient, account: signing.Account,
                 compiler_adapter: CompilerAdapter = None):
        """
        初始化合约部署器
        
        :param client: 节点客户端
        :param account: 部署账户
        :param compiler_adapter: 编译器适配器 (可选)
        """
        self.client = client
        self.account = account
        self.compiler = compiler_adapter
    
    def deploy(self, bytecode: str, source: str, 
               init_args: List[Any] = None,
               gas: int = None,
               gas_price: int = None,
               amount: int = 0,
               deposit: int = 0,
               wait_confirmations: bool = True,
               max_wait_seconds: int = 300) -> str:
        """
        部署合约到区块链
        
        :param bytecode: 编译后的字节码
        :param source: 合约源码 (用于编码 calldata)
        :param init_args: init 函数参数列表
        :param gas: Gas 限制 (默认 100000)
        :param gas_price: Gas 价格 (默认 1 Gwei)
        :param amount: 转账金额 (aettos)
        :param deposit: 押金 (aettos)
        :param wait_confirmations: 是否等待确认
        :param max_wait_seconds: 最大等待时间 (秒)
        :return: 合约地址 (ct_xxx)
        :raises ValueError: 参数错误
        :raises Exception: 部署失败
        """
        if not bytecode:
            raise ValueError("bytecode 不能为空")
        
        if self.compiler is None:
            # 使用默认编译器 (尝试从 client 获取，否则使用默认值)
            compiler_url = 'https://v8.compiler.aepps.com'
            if hasattr(self.client, 'config'):
                # 尝试从客户端配置获取编译器 URL
                compiler_url = getattr(self.client.config, 'compiler_url', compiler_url)
            self.compiler = CompilerAdapter(compiler_url)
        
        # 设置默认值
        gas = gas or self.DEFAULT_GAS
        gas_price = gas_price or self.DEFAULT_GAS_PRICE
        init_args = init_args or []
        
        # 编码 init calldata
        calldata = self.compiler.encode_calldata(source, "init", *init_args)
        
        # 获取 VM/ABI 版本
        vm_version, abi_version = self.client.get_vm_abi_versions()
        
        # 构建交易
        txb = self.client.tx_builder
        nonce, ttl = self.client._get_nonce_ttl(self.account.get_address(), 500)
        
        tx = txb.tx_contract_create(
            self.account.get_address(),
            bytecode,
            calldata,
            amount,
            deposit,
            gas,
            gas_price,
            vm_version,
            abi_version,
            fee=0,  # 自动计算
            ttl=ttl,
            nonce=nonce
        )
        
        # 计算合约地址
        contract_id = hashing.contract_id(self.account.get_address(), nonce)
        
        # 签名并广播
        tx_signed = self.client.sign_transaction(
            self.account, tx, 
            metadata={"contract_id": contract_id}
        )
        self.client.broadcast_transaction(tx_signed)
        
        # 等待确认
        if wait_confirmations:
            self._wait_for_confirmation(tx_signed.hash, max_wait_seconds)
        
        return contract_id
    
    def _wait_for_confirmation(self, tx_hash: str, max_wait_seconds: int = 300):
        """
        等待交易确认
        
        :param tx_hash: 交易哈希
        :param max_wait_seconds: 最大等待时间 (秒)
        """
        interval = 10  # 每 10 秒检查一次
        elapsed = 0
        
        while elapsed < max_wait_seconds:
            try:
                tx_info = self.client.get_transaction_by_hash(hash=tx_hash)
                if hasattr(tx_info, 'block_height') and tx_info.block_height > 0:
                    return  # 已确认
            except:
                pass
            
            time.sleep(interval)
            elapsed += interval


# ==============================================================================
#  6. 合约调用器
# ==============================================================================

class ContractCaller:
    """
    合约调用器
    
    封装合约的链上调用和静态调用。
    
    核心功能:
        - 静态调用 (dry-run, 不消耗 gas)
        - 链上调用 (实际执行，消耗 gas)
        - 自动解码返回值
    
    用法:
        caller = ContractCaller(client, contract_id, source_code)
        
        # 静态调用 (只读)
        result = caller.call_static("get_count")
        print(f"当前计数: {result.decoded_value}")
        
        # 链上调用 (写入)
        result = caller.call("increment", account=account)
        print(f"交易哈希: {result.tx_hash}")
    """
    
    DEFAULT_GAS = 100000
    DEFAULT_GAS_PRICE = 1000000000  # 1 Gwei (默认最低价)
    
    def __init__(self, client: node.NodeClient, contract_id: str, 
                 source: str, compiler_adapter: CompilerAdapter = None):
        """
        初始化合约调用器
        
        :param client: 节点客户端
        :param contract_id: 合约地址 (ct_xxx)
        :param source: 合约源码
        :param compiler_adapter: 编译器适配器 (可选)
        """
        self.client = client
        self.contract_id = contract_id
        self.source = source
        self.contract = Contract(client)
        
        if compiler_adapter is None:
            # 使用默认编译器
            compiler_url = 'https://v8.compiler.aepps.com'
            self.compiler = CompilerAdapter(compiler_url)
        else:
            self.compiler = compiler_adapter
    
    def call_static(self, function: str, *args, gas: int = None) -> CallResult:
        """
        静态调用合约方法 (dry-run)
        
        静态调用不会消耗 gas，不会修改链上状态，用于查询操作。
        
        :param function: 函数名
        :param args: 函数参数
        :param gas: Gas 限制
        :return: CallResult 对象
        """
        gas = gas or self.DEFAULT_GAS
        
        try:
            # 编码 calldata
            calldata = self.compiler.encode_calldata(self.source, function, *args)
            
            # 执行静态调用
            result = self.contract.call_static(
                contract_id=self.contract_id,
                function=function,
                calldata=calldata,
                gas=gas
            )
            
            # 提取调用对象
            call_obj = result.call_obj if hasattr(result, 'call_obj') else result
            
            # 解码返回值
            decoded = None
            if call_obj.return_type == 'ok':
                try:
                    decoded = self.compiler.decode_result(
                        self.source, function, 
                        call_obj.return_value, call_obj.return_type
                    )
                except:
                    pass  # 解码失败不影响结果
            
            return CallResult(
                success=(call_obj.return_type == 'ok'),
                gas_used=call_obj.gas_used,
                return_type=call_obj.return_type,
                return_value=call_obj.return_value,
                decoded_value=decoded
            )
        
        except Exception as e:
            return CallResult(
                success=False,
                gas_used=0,
                return_type='error',
                return_value='',
                error_message=str(e)
            )
    
    def call(self, function: str, *args, 
             account: signing.Account,
             gas: int = None,
             gas_price: int = None,
             amount: int = 0) -> CallResult:
        """
        链上调用合约方法
        
        链上调用会消耗 gas，可能会修改链上状态。
        
        :param function: 函数名
        :param args: 函数参数
        :param account: 调用账户
        :param gas: Gas 限制
        :param gas_price: Gas 价格
        :param amount: 转账金额 (payable 函数)
        :return: CallResult 对象
        """
        gas = gas or self.DEFAULT_GAS
        gas_price = gas_price or self.DEFAULT_GAS_PRICE
        
        try:
            # 编码 calldata
            calldata = self.compiler.encode_calldata(self.source, function, *args)
            
            # 执行链上调用
            tx = self.contract.call(
                contract_id=self.contract_id,
                account=account,
                function=function,
                calldata=calldata,
                gas=gas,
                gas_price=gas_price,
                amount=amount
            )
            
            # 获取调用结果
            call_info = self.contract.get_call_object(tx.hash)
            
            # 解码返回值
            decoded = None
            if call_info.return_type == 'ok':
                try:
                    decoded = self.compiler.decode_result(
                        self.source, function,
                        call_info.return_value, call_info.return_type
                    )
                except:
                    pass
            
            return CallResult(
                success=(call_info.return_type == 'ok'),
                tx_hash=tx.hash,
                gas_used=call_info.gas_used,
                return_type=call_info.return_type,
                return_value=call_info.return_value,
                decoded_value=decoded
            )
        
        except Exception as e:
            return CallResult(
                success=False,
                gas_used=0,
                return_type='error',
                return_value='',
                error_message=str(e)
            )


# ==============================================================================
#  7. 部署追踪器
# ==============================================================================

class DeploymentTracker:
    """
    部署追踪器
    
    记录和管理合约部署历史。
    
    核心功能:
        - 保存部署信息
        - 加载部署历史
        - 查询部署记录
    
    用法:
        tracker = DeploymentTracker("deployments.json")
        
        # 保存部署信息
        info = DeploymentInfo(
            contract_id="ct_xxx",
            deployer="ak_yyy",
            tx_hash="th_zzz",
            network="testnet",
            timestamp=datetime.now().isoformat()
        )
        tracker.save(info)
        
        # 查询最新部署
        latest = tracker.get_latest("testnet")
    """
    
    def __init__(self, storage_path: str = "deployments.json"):
        """
        初始化部署追踪器
        
        :param storage_path: 存储文件路径
        """
        self.storage_path = Path(storage_path)
        self.deployments = self._load_all()
    
    def _load_all(self) -> List[DeploymentInfo]:
        """加载所有部署记录"""
        if not self.storage_path.exists():
            return []
        
        try:
            with open(self.storage_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
                return [DeploymentInfo.from_dict(d) for d in data]
        except:
            return []
    
    def save(self, info: DeploymentInfo):
        """
        保存部署信息
        
        :param info: 部署信息对象
        """
        self.deployments.append(info)
        self._persist()
    
    def _persist(self):
        """持久化到文件"""
        data = [d.to_dict() for d in self.deployments]
        with open(self.storage_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
    
    def get_latest(self, network: str = None) -> Optional[DeploymentInfo]:
        """
        获取最新部署
        
        :param network: 网络名称 (可选)
        :return: DeploymentInfo 或 None
        """
        filtered = self.deployments
        if network:
            filtered = [d for d in filtered if d.network == network]
        
        return filtered[-1] if filtered else None
    
    def get_by_contract_id(self, contract_id: str) -> Optional[DeploymentInfo]:
        """
        根据合约地址查询
        
        :param contract_id: 合约地址
        :return: DeploymentInfo 或 None
        """
        for d in reversed(self.deployments):
            if d.contract_id == contract_id:
                return d
        return None
    
    def list_all(self, network: str = None) -> List[DeploymentInfo]:
        """
        列出所有部署
        
        :param network: 网络名称 (可选)
        :return: DeploymentInfo 列表
        """
        if network:
            return [d for d in self.deployments if d.network == network]
        return self.deployments.copy()


# ==============================================================================
#  导出接口
# ==============================================================================

__all__ = [
    # 数据类
    'NetworkConfig',
    'CompileResult',
    'DeploymentInfo',
    'CallResult',
    
    # 核心类
    'CompilerAdapter',
    'NetworkManager',
    'AccountLoader',
    'ContractCompiler',
    'ContractDeployer',
    'ContractCaller',
    'DeploymentTracker',
]
