import json

from web3 import Web3
from solcx import install_solc, compile_standard

from config import RPC_URL, PRIVATE_KEY, CONTRACT_SOURCE, CONTRACT_INFO_JSON


def _get_raw_tx(signed):
    if hasattr(signed, "raw_transaction"):
        return signed.raw_transaction
    return signed.rawTransaction


def get_w3():
    w3 = Web3(Web3.HTTPProvider(RPC_URL))
    if not w3.is_connected():
        raise RuntimeError(f"无法连接链节点: {RPC_URL}")
    return w3


def get_account(w3):
    return w3.eth.account.from_key(PRIVATE_KEY)


def compile_contract():
    install_solc("0.8.19")
    source = CONTRACT_SOURCE.read_text(encoding="utf-8")

    compiled = compile_standard(
        {
            "language": "Solidity",
            "sources": {
                "ProvenanceRegistryV3.sol": {
                    "content": source
                }
            },
            "settings": {
                "outputSelection": {
                    "*": {
                        "*": ["abi", "evm.bytecode"]
                    }
                }
            },
        },
        solc_version="0.8.19",
    )

    contract_data = compiled["contracts"]["ProvenanceRegistryV3.sol"]["ProvenanceRegistryV3"]
    abi = contract_data["abi"]
    bytecode = contract_data["evm"]["bytecode"]["object"]
    return abi, bytecode


def save_contract_info(address, abi):
    CONTRACT_INFO_JSON.parent.mkdir(parents=True, exist_ok=True)
    with CONTRACT_INFO_JSON.open("w", encoding="utf-8") as f:
        json.dump({"address": address, "abi": abi}, f, ensure_ascii=False, indent=2)


def load_contract_info():
    with CONTRACT_INFO_JSON.open("r", encoding="utf-8") as f:
        return json.load(f)


def get_contract():
    w3 = get_w3()
    info = load_contract_info()
    contract = w3.eth.contract(
        address=Web3.to_checksum_address(info["address"]),
        abi=info["abi"]
    )
    return w3, contract


def deploy_contract():
    w3 = get_w3()
    account = get_account(w3)
    abi, bytecode = compile_contract()

    contract = w3.eth.contract(abi=abi, bytecode=bytecode)
    nonce = w3.eth.get_transaction_count(account.address)

    tx = contract.constructor().build_transaction({
        "from": account.address,
        "nonce": nonce,
        "gas": 3000000,
        "gasPrice": w3.to_wei("2", "gwei"),
        "chainId": w3.eth.chain_id,
    })

    signed = account.sign_transaction(tx)
    tx_hash = w3.eth.send_raw_transaction(_get_raw_tx(signed))
    receipt = w3.eth.wait_for_transaction_receipt(tx_hash)

    save_contract_info(receipt.contractAddress, abi)

    return {
        "contract_address": receipt.contractAddress,
        "tx_hash": receipt.transactionHash.hex(),
        "gas_used": receipt.gasUsed,
        "block_number": receipt.blockNumber,
        "from": account.address
    }


def register_record(contract, w3, account, content_id, file_name, platform_name, watermark_id, phash_value):
    nonce = w3.eth.get_transaction_count(account.address)

    tx = contract.functions.registerRecord(
        content_id,
        file_name,
        platform_name,
        watermark_id,
        phash_value
    ).build_transaction({
        "from": account.address,
        "nonce": nonce,
        "gas": 500000,
        "gasPrice": w3.to_wei("2", "gwei"),
        "chainId": w3.eth.chain_id,
    })

    signed = account.sign_transaction(tx)
    tx_hash = w3.eth.send_raw_transaction(_get_raw_tx(signed))
    receipt = w3.eth.wait_for_transaction_receipt(tx_hash)

    return {
        "tx_hash": receipt.transactionHash.hex(),
        "gas_used": receipt.gasUsed,
        "block_number": receipt.blockNumber
    }


def estimate_query_gas(contract, account_address, content_id):
    return contract.functions.getRecordByContentId(content_id).estimate_gas({
        "from": account_address
    })


def query_record(contract, content_id):
    r = contract.functions.getRecordByContentId(content_id).call()
    return {
        "content_id": r[0],
        "file_name": r[1],
        "platform_name": r[2],
        "watermark_id": r[3],
        "phash_value": r[4],
        "timestamp": r[5],
        "exists": r[6]
    }


def get_all_records_from_chain(contract):
    ids = contract.functions.getAllContentIds().call()
    records = []
    for cid in ids:
        r = contract.functions.getRecordByContentId(cid).call()
        records.append({
            "content_id": r[0],
            "file_name": r[1],
            "platform_name": r[2],
            "watermark_id": r[3],
            "phash_value": r[4],
            "timestamp": r[5],
            "exists": r[6]
        })
    return records
