"""
Solon SDK (Python) v0.1.0 — Solon Chain 官方 Python SDK（测试网）
依赖：pip install web3
用法：
    from solon_sdk import SolonSDK
    sdk = SolonSDK()                      # 只读
    sdk.connect("0x你的私钥")             # 写操作需要
    print(sdk.price("NVDA"))              # 145.0
    sdk.buy("NVDA", "10")                 # 10 SLN 买入 NVDA 通证
只读场景（行情/名册/对账）也可以用公开机构 API，无需任何依赖：
    GET https://solon.digital/api/v1/markets
"""
import json
import os

try:
    from web3 import Web3
except ImportError as e:
    raise SystemExit("SolonSDK 需要 web3.py：pip install web3") from e

VERSION = "0.1.0"
DEFAULT_RPC = "https://solon.digital/solonrpc/"
CHAIN_ID = 51051

# 合约地址簿（与 assets/solon-defi.js 同步）
FEED = "0x864e468C8086283Ac43E2830C88cE0d87f29C381"
KYC = "0x9ce874DB71b89Ce54a54b203ce04931C5AaA93B0"
AMM = "0x21917d97e699a125696e93b036022F70783a0Ce2"
IR_STOCK = "0x7C66D9e02e75f4C1bb99a302E23F81A4F58fDf56"
DIVIDEND = "0x8aC596e41db553BAE8B82d151A80dc10d9C98351"
ORDERBOOK = "0x86a60247Bdad0fE1b4fdFdeA231af996C8d5189c"
BRIDGE = "0x36507aB96929181867CE406B7DcAE0114467474f"
OFFERING = "0xf61Ed0E0964Ac3B2f7d872B03315972BfE97e816"

STOCKS = [
    {"symbol": "NVDA", "name": "Tokenized NVIDIA", "addr": "0x679cFa7329f8924B9455cb0D961E8CDE62f867D3"},
    {"symbol": "AAPL", "name": "Tokenized Apple", "addr": "0x7A349aE2D6Ea3C8Cc2c653D76d0AB3a8e1089035"},
    {"symbol": "TSLA", "name": "Tokenized Tesla", "addr": "0xE08961ED138e849f5aB830326E8492528b303D55"},
    {"symbol": "MSFT", "name": "Tokenized Microsoft", "addr": "0xaE5c16e714901d55a60C29EFc9A4Dcb5949Ed5E1"},
    {"symbol": "GLD", "name": "Solon Gold Share", "addr": "0x0Fe35F9621969B4bBEed7C153660B91d00cC4094"},
]

_ABI_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "abi")


def _load_abi(name):
    """从 ./abi/<name>.json 读取 ABI；找不到时给出清晰提示。"""
    path = os.path.join(_ABI_DIR, name + ".json")
    if not os.path.exists(path):
        raise FileNotFoundError(
            f"缺少 ABI 文件 {path}。请从 https://solon.digital/assets/abi/{name}.json 下载，"
            "与 solon_sdk.py 放在同一目录的 abi/ 文件夹下。")
    with open(path, encoding="utf-8") as f:
        return json.load(f)


class SolonSDK:
    """方法签名与 JS 版（assets/solon-sdk.js）一一对齐。"""

    def __init__(self, rpc=DEFAULT_RPC, chain_id=CHAIN_ID):
        self.w3 = Web3(Web3.HTTPProvider(rpc))
        self.chain_id = chain_id
        self.account = None
        if not self.w3.is_connected():
            raise ConnectionError(f"无法连接 Solon RPC：{rpc}")

    # ---------- 连接 ----------
    def connect(self, private_key):
        self.account = self.w3.eth.account.from_key(private_key)
        return self.account.address

    @property
    def address(self):
        return self.account.address if self.account else None

    def _c(self, addr, abi_name):
        return self.w3.eth.contract(address=Web3.to_checksum_address(addr), abi=_load_abi(abi_name))

    def _send(self, fn, value_wei=0):
        if not self.account:
            raise PermissionError("未连接私钥（SolonSDK.connect(pk)）")
        tx = fn.build_transaction({
            "from": self.account.address,
            "value": value_wei,
            "nonce": self.w3.eth.get_transaction_count(self.account.address),
            "chainId": self.chain_id,
        })
        signed = self.account.sign_transaction(tx)
        h = self.w3.eth.send_raw_transaction(signed.raw_transaction)
        return self.w3.eth.wait_for_transaction_receipt(h)

    @staticmethod
    def _stock(sym):
        for s in STOCKS:
            if s["symbol"] == sym.upper():
                return s
        raise ValueError(f"未知通证符号：{sym}")

    # ---------- 行情 ----------
    def price(self, sym):
        s = self._stock(sym)
        p = self._c(FEED, "feed").functions.price(Web3.to_checksum_address(s["addr"])).call()
        return float(Web3.from_wei(p, "ether"))

    def prices(self):
        return {s["symbol"]: self.price(s["symbol"]) for s in STOCKS if s["symbol"] != "GLD"}

    def quote_buy(self, sym, sln_amount):
        s = self._stock(sym)
        q = self._c(AMM, "amm").functions.quoteToken(
            Web3.to_checksum_address(s["addr"]), Web3.to_wei(sln_amount, "ether")).call()
        return float(Web3.from_wei(q, "ether"))

    def quote_sell(self, sym, shares):
        s = self._stock(sym)
        q = self._c(AMM, "amm").functions.quoteSLN(
            Web3.to_checksum_address(s["addr"]), Web3.to_wei(shares, "ether")).call()
        return float(Web3.from_wei(q, "ether"))

    # ---------- 交易 ----------
    def buy(self, sym, sln_amount):
        s = self._stock(sym)
        fn = self._c(AMM, "amm").functions.swapSLNForToken(Web3.to_checksum_address(s["addr"]), 0)
        return self._send(fn, Web3.to_wei(sln_amount, "ether"))

    def sell(self, sym, shares):
        s = self._stock(sym)
        amt = Web3.to_wei(shares, "ether")
        ct = self._c(s["addr"], "ct")
        alw = ct.functions.allowance(self.address, AMM).call()
        if alw < amt:
            self._send(ct.functions.approve(AMM, amt))
        fn = self._c(AMM, "amm").functions.swapTokenForSLN(Web3.to_checksum_address(s["addr"]), amt, 0)
        return self._send(fn)

    # ---------- 限价单 ----------
    def place_buy(self, sym, price_sln, shares):
        s = self._stock(sym)
        cost = Web3.to_wei(price_sln, "ether") * Web3.to_wei(shares, "ether") // Web3.to_wei(1, "ether")
        fn = self._c(ORDERBOOK, "orderbook").functions.placeBuy(
            Web3.to_checksum_address(s["addr"]), Web3.to_wei(price_sln, "ether"), Web3.to_wei(shares, "ether"))
        return self._send(fn, cost)

    def place_sell(self, sym, price_sln, shares):
        s = self._stock(sym)
        amt = Web3.to_wei(shares, "ether")
        ct = self._c(s["addr"], "ct")
        alw = ct.functions.allowance(self.address, ORDERBOOK).call()
        if alw < amt:
            self._send(ct.functions.approve(ORDERBOOK, amt))
        fn = self._c(ORDERBOOK, "orderbook").functions.placeSell(
            Web3.to_checksum_address(s["addr"]), Web3.to_wei(price_sln, "ether"), amt)
        return self._send(fn)

    def cancel_order(self, order_id):
        return self._send(self._c(ORDERBOOK, "orderbook").functions.cancel(order_id))

    # ---------- 股息 ----------
    def dividends(self):
        dv = self._c(DIVIDEND, "dividend")
        return [dv.functions.getDiv(i).call() for i in range(1, dv.functions.nextId().call())]

    def claimable(self, div_id, addr=None):
        addr = addr or self.address
        c = self._c(DIVIDEND, "dividend").functions.claimable(div_id, Web3.to_checksum_address(addr)).call()
        return float(Web3.from_wei(c, "ether"))

    def claim_dividend(self, div_id):
        return self._send(self._c(DIVIDEND, "dividend").functions.claim(div_id))

    # ---------- 一级认购 ----------
    def offers(self):
        of = self._c(OFFERING, "offering")
        return [of.functions.offers(i).call() for i in range(1, of.functions.nextId().call())]

    def subscribe(self, offer_id, amount):
        of = self._c(OFFERING, "offering")
        cost = of.functions.quote(offer_id, Web3.to_wei(amount, "ether")).call()
        return self._send(of.functions.subscribe(offer_id, Web3.to_wei(amount, "ether")), cost)

    # ---------- 跨链桥 ----------
    def bridge_deposit(self, sln_amount, target_chain, target):
        fn = self._c(BRIDGE, "bridge").functions.deposit(target_chain, Web3.to_checksum_address(target))
        return self._send(fn, Web3.to_wei(sln_amount, "ether"))

    def bridge_claim(self, xfer_id):
        return self._send(self._c(BRIDGE, "bridge").functions.release(xfer_id))

    # ---------- 账户 ----------
    def balance_sln(self, addr=None):
        addr = addr or self.address
        return float(Web3.from_wei(self.w3.eth.get_balance(Web3.to_checksum_address(addr)), "ether"))

    def balance_of(self, sym, addr=None):
        addr = addr or self.address
        s = self._stock(sym)
        b = self._c(s["addr"], "ct").functions.balanceOf(Web3.to_checksum_address(addr)).call()
        return float(Web3.from_wei(b, "ether"))

    def is_verified(self, addr=None):
        addr = addr or self.address
        return self._c(IR_STOCK, "ir").functions.isVerified(Web3.to_checksum_address(addr)).call()

    def register_self(self):
        return self._send(self._c(KYC, "kyc").functions.registerSelf())

    @staticmethod
    def version():
        return VERSION


if __name__ == "__main__":
    sdk = SolonSDK()
    print("SolonSDK v", VERSION, "· block:", sdk.w3.eth.block_number)
    print("prices:", sdk.prices())
