#!/usr/bin/env python3
"""
Simple command line client into RPC commands
Can be used as an alternate to Telegram

Should not import anything from freqtrade,
so it can be used as a standalone script.
"""

import argparse
import json
import logging
import inspect
from urllib.parse import urlencode, urlparse, urlunparse
from pathlib import Path

import requests
from requests.exceptions import ConnectionError

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
)
logger = logging.getLogger("ft_rest_client")


class FtRestClient():

    def __init__(self, serverurl, username=None, password=None):

        self._serverurl = serverurl
        self._session = requests.Session()
        self._session.auth = (username, password)

    def _call(self, method, apipath, params: dict = None, data=None, files=None):

        if str(method).upper() not in ('GET', 'POST', 'PUT', 'DELETE'):
            raise ValueError('invalid method <{0}>'.format(method))
        basepath = f"{self._serverurl}/api/v1/{apipath}"

        hd = {"Accept": "application/json",
              "Content-Type": "application/json"
              }

        # Split url
        schema, netloc, path, par, query, fragment = urlparse(basepath)
        # URLEncode query string
        query = urlencode(params) if params else ""
        # recombine url
        url = urlunparse((schema, netloc, path, par, query, fragment))

        try:
            resp = self._session.request(method, url, headers=hd, data=json.dumps(data))
            # return resp.text
            return resp.json()
        except ConnectionError:
            logger.warning("Connection error")

    def _get(self, apipath, params: dict = None):
        return self._call("GET", apipath, params=params)

    def _post(self, apipath, params: dict = None, data: dict = None):
        return self._call("POST", apipath, params=params, data=data)

    def start(self):
        """
        Start the bot if it's in stopped state.
        :return: json object
        """
        return self._post("start")

    def stop(self):
        """
        Stop the bot. Use start to restart
        :return: json object
        """
        return self._post("stop")

    def stopbuy(self):
        """
        Stop buying (but handle sells gracefully).
        use reload_conf to reset
        :return: json object
        """
        return self._post("stopbuy")

    def reload_conf(self):
        """
        Reload configuration
        :return: json object
        """
        return self._post("reload_conf")

    def balance(self):
        """
        Get the account balance
        :return: json object
        """
        return self._get("balance")

    def count(self):
        """
        Returns the amount of open trades
        :return: json object
        """
        return self._get("count")

    def daily(self, days=None):
        """
        Returns the amount of open trades
        :return: json object
        """
        return self._get("daily", params={"timescale": days} if days else None)

    def edge(self):
        """
        Returns information about edge
        :return: json object
        """
        return self._get("edge")

    def profit(self):
        """
        Returns the profit summary
        :return: json object
        """
        return self._get("profit")

    def performance(self):
        """
        Returns the performance of the different coins
        :return: json object
        """
        return self._get("performance")

    def status(self):
        """
        Get the status of open trades
        :return: json object
        """
        return self._get("status")

    def version(self):
        """
        Returns the version of the bot
        :return: json object containing the version
        """
        return self._get("version")

    def whitelist(self):
        """
        Show the current whitelist
        :return: json object
        """
        return self._get("whitelist")

    def blacklist(self, *args):
        """
        Show the current blacklist
        :param add: List of coins to add (example: "BNB/BTC")
        :return: json object
        """
        if not args:
            return self._get("blacklist")
        else:
            return self._post("blacklist", data={"blacklist": args})

    def forcebuy(self, pair, price=None):
        """
        Buy an asset
        :param pair: Pair to buy (ETH/BTC)
        :param price: Optional - price to buy
        :return: json object of the trade
        """
        data = {"pair": pair,
                "price": price
                }
        return self._post("forcebuy", data=data)

    def forcesell(self, tradeid):
        """
        Force-sell a trade
        :param tradeid: Id of the trade (can be received via status command)
        :return: json object
        """

        return self._post("forcesell", data={"tradeid": tradeid})


def add_arguments():
    parser = argparse.ArgumentParser()
    parser.add_argument("command",
                        help="Positional argument defining the command to execute.")

    parser.add_argument('--show',
                        help='Show possible methods with this client',
                        dest='show',
                        action='store_true',
                        default=False
                        )

    parser.add_argument('-c', '--config',
                        help='Specify configuration file (default: %(default)s). ',
                        dest='config',
                        type=str,
                        metavar='PATH',
                        default='config.json'
                        )

    parser.add_argument("command_arguments",
                        help="Positional arguments for the parameters for [command]",
                        nargs="*",
                        default=[]
                        )

    args = parser.parse_args()
    return vars(args)


def load_config(configfile):
    file = Path(configfile)
    if file.is_file():
        with file.open("r") as f:
            config = json.load(f)
        return config
    return {}


def print_commands():
    # Print dynamic help for the different commands using the commands doc-strings
    client = FtRestClient(None)
    print("Possible commands:")
    for x, y in inspect.getmembers(client):
        if not x.startswith('_'):
            print(f"{x} {getattr(client, x).__doc__}")


def main(args):

    if args.get("help"):
        print_commands()

    config = load_config(args["config"])
    url = config.get("api_server", {}).get("server_url", "127.0.0.1")
    port = config.get("api_server", {}).get("listen_port", "8080")
    username = config.get("api_server", {}).get("username")
    password = config.get("api_server", {}).get("password")

    server_url = f"http://{url}:{port}"
    client = FtRestClient(server_url, username, password)

    m = [x for x, y in inspect.getmembers(client) if not x.startswith('_')]
    command = args["command"]
    if command not in m:
        logger.error(f"Command {command} not defined")
        print_commands()
        return

    print(getattr(client, command)(*args["command_arguments"]))


if __name__ == "__main__":
    args = add_arguments()
    main(args)
