import os
import sys
from decimal import Decimal
from bitcoinrpc.authproxy import AuthServiceProxy

from lbryum.wallet import Wallet, WalletStorage
from lbryum.commands import known_commands, Commands
from lbryum.simple_config import SimpleConfig
from lbryum.blockchain import get_blockchain
from lbryum.network import Network


def get_lbrycrdd_connection_string(wallet_conf):
    settings = {"username": "rpcuser",
                "password": "rpcpassword",
                "rpc_port": 9245}
    if wallet_conf and os.path.exists(wallet_conf):
        with open(wallet_conf, "r") as conf:
            conf_lines = conf.readlines()
        for l in conf_lines:
            if l.startswith("rpcuser="):
                settings["username"] = l[8:].rstrip('\n')
            if l.startswith("rpcpassword="):
                settings["password"] = l[12:].rstrip('\n')
            if l.startswith("rpcport="):
                settings["rpc_port"] = int(l[8:].rstrip('\n'))

    rpc_user = settings["username"]
    rpc_pass = settings["password"]
    rpc_port = settings["rpc_port"]
    rpc_url = "127.0.0.1"
    return "http://%s:%s@%s:%i" % (rpc_user, rpc_pass, rpc_url, rpc_port)


class LBRYumWallet(object):
    def __init__(self, lbryum_path):
        self.config = SimpleConfig()
        self.config.set_key('chain', 'lbrycrd_main')
        self.storage = WalletStorage(lbryum_path)
        self.wallet = Wallet(self.storage)
        self.cmd_runner = Commands(self.config, self.wallet, None)
        if not self.wallet.has_seed():
            seed = self.wallet.make_seed()
            self.wallet.add_seed(seed, "derp")
            self.wallet.create_master_keys("derp")
            self.wallet.create_main_account()
            self.wallet.update_password("derp", "")
        self.network = Network(self.config)
        self.blockchain = get_blockchain(self.config, self.network)
        print self.config.get('chain'), self.blockchain
        self.wallet.storage.write()

    def command(self, command_name, *args, **kwargs):
        cmd_runner = Commands(self.config, self.wallet, None)
        cmd = known_commands[command_name]
        func = getattr(cmd_runner, cmd.name)
        return func(*args, **kwargs)

    def generate_address(self):
        address = self.wallet.create_new_address()
        self.wallet.storage.write()
        return address


class LBRYcrd(object):
    def __init__(self, lbrycrdd_path):
        self.lbrycrdd_conn_str = get_lbrycrdd_connection_string(lbrycrdd_path)

    def __call__(self, method, *args, **kwargs):
        return self.rpc(method)(*args, **kwargs)

    def rpc(self, method):
        return AuthServiceProxy(self.lbrycrdd_conn_str, service_name=method)


def get_wallet_path():
    cwd = os.getcwd()
    wallet_path = os.path.join(cwd, "wallet.json")
    if not os.path.exists(wallet_path):
        return wallet_path
    i = 1
    while True:
        wallet_path = os.path.join(cwd, "wallet_%i.json" % i)
        if not os.path.exists(wallet_path):
            return wallet_path
        i += 1


def coin_chooser(lbrycrdd, amount, fee=0.001):
    def iter_txis():
        unspent = lbrycrdd("listunspent")
        unspent = sorted(unspent, key=lambda x: x['amount'], reverse=True)
        spendable = Decimal(0.0)
        for txi in unspent:
            if spendable >= amount:
                break
            else:
                spendable += txi['amount']
                yield txi
        if spendable < amount:
            print spendable, amount
            raise Exception("Not enough funds")

    coins = list(iter(iter_txis()))
    total = sum(c['amount'] for c in coins)
    change = Decimal(total) - Decimal(amount) - Decimal(fee)

    if change < 0:
        raise Exception("Not enough funds")
    if change:
        change_address = lbrycrdd("getnewaddress")
    else:
        change_address = None

    print "Total: %f, amount: %f, change: %f" % (total, amount, change)

    return coins, change, change_address


def get_raw_tx(lbrycrdd, addresses, coins, amount, change, change_address):
    txi = [{'txid': c['txid'], 'vout': c['vout']} for c in coins]
    txo = {address: float(amount) for address in addresses}
    if change_address:
        txo[change_address] = float(change)
    return lbrycrdd("createrawtransaction", txi, txo)


def main(count, value=None, lbryum_path=None, lbrycrdd_path=None):
    count = int(count)
    lbryum_path = lbryum_path or get_wallet_path()
    if sys.platform == "darwin":
        default_lbrycrdd = os.path.join(os.path.expanduser("~"),
                                 "Library/Application Support/lbrycrd/lbrycrd.conf")
    else:
        default_lbrycrdd = os.path.join(os.path.expanduser("~"), ".lbrycrd/lbrycrd.conf")
    lbrycrdd_path = lbrycrdd_path or default_lbrycrdd
    l = LBRYcrd(lbrycrdd_path=lbrycrdd_path)
    s = LBRYumWallet(lbryum_path)
    value = value or 1.0
    value = Decimal(value)

    coins, change, change_address = coin_chooser(l, count * value)
    addresses = [s.generate_address() for i in range(count)]
    raw_tx = get_raw_tx(l, addresses, coins, value, change, change_address)
    signed = l("signrawtransaction", raw_tx)['hex']
    txid = l("sendrawtransaction", signed)
    print txid


if __name__ == "__main__":
    args = sys.argv[1:]
    main(*args)