#!/usr/bin/env python3
# Copyright (c) 2019-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test RPC misc output."""
import xml.etree.ElementTree as ET

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
    assert_raises_rpc_error,
    assert_equal,
    assert_greater_than,
    assert_greater_than_or_equal,
    JSONRPCException,
)

import http
import subprocess


class RpcMiscTest(BitcoinTestFramework):
    def set_test_params(self):
        self.num_nodes = 1

    def run_test(self):
        node = self.nodes[0]

        self.log.info("test CHECK_NONFATAL")
        msg_internal_bug = 'request.params[9].get_str() != "trigger_internal_bug"'
        self.restart_node(0)  # Required to flush the chainstate
        try:
            node.echo(arg9="trigger_internal_bug")
            assert False  # Must hit one of the exceptions below
        except (
                subprocess.CalledProcessError,
                http.client.CannotSendRequest,
                http.client.RemoteDisconnected,
        ):
            self.log.info("Restart node after crash")
            assert_equal(-6, node.process.wait(timeout=10))
            self.start_node(0)
        except JSONRPCException as e:
            assert_equal(e.error["code"], -1)
            assert f"Internal bug detected: {msg_internal_bug}" in e.error["message"]

        self.log.info("test max arg size")
        ARG_SZ_COMMON = 131071  # Common limit, used previously in the test framework, serves as a regression test
        ARG_SZ_LARGE = 8 * 1024 * 1024  # A large size, which should be rare to hit in practice
        for arg_sz in [0, 1, 100, ARG_SZ_COMMON, ARG_SZ_LARGE]:
            arg_string = 'a' * arg_sz
            assert_equal([arg_string, arg_string], node.echo(arg_string, arg_string))

        self.log.info("test getmemoryinfo")
        memory = node.getmemoryinfo()['locked']
        assert_greater_than(memory['used'], 0)
        assert_greater_than(memory['free'], 0)
        assert_greater_than(memory['total'], 0)
        # assert_greater_than_or_equal() for locked in case locking pages failed at some point
        assert_greater_than_or_equal(memory['locked'], 0)
        assert_greater_than(memory['chunks_used'], 0)
        assert_greater_than(memory['chunks_free'], 0)
        assert_equal(memory['used'] + memory['free'], memory['total'])

        self.log.info("test mallocinfo")
        try:
            mallocinfo = node.getmemoryinfo(mode="mallocinfo")
            self.log.info('getmemoryinfo(mode="mallocinfo") call succeeded')
            tree = ET.fromstring(mallocinfo)
            assert_equal(tree.tag, 'malloc')
        except JSONRPCException:
            self.log.info('getmemoryinfo(mode="mallocinfo") not available')
            assert_raises_rpc_error(-8, 'mallocinfo mode not available', node.getmemoryinfo, mode="mallocinfo")

        assert_raises_rpc_error(-8, "unknown mode foobar", node.getmemoryinfo, mode="foobar")

        self.log.info("test logging rpc and help")

        # Test toggling a logging category on/off/on with the logging RPC.
        assert_equal(node.logging()['qt'], True)
        node.logging(exclude=['qt'])
        assert_equal(node.logging()['qt'], False)
        node.logging(include=['qt'])
        assert_equal(node.logging()['qt'], True)

        # Test logging RPC returns the logging categories in alphabetical order.
        sorted_logging_categories = sorted(node.logging())
        assert_equal(list(node.logging()), sorted_logging_categories)

        # Test logging help returns the logging categories string in alphabetical order.
        categories = ', '.join(sorted_logging_categories)
        logging_help = self.nodes[0].help('logging')
        assert f"valid logging categories are: {categories}" in logging_help

        self.log.info("test echoipc (testing spawned process in multiprocess build)")
        assert_equal(node.echoipc("hello"), "hello")

        self.log.info("test getindexinfo")
        # Without any indices running the RPC returns an empty object
        assert_equal(node.getindexinfo(), {})

        # Restart the node with indices and wait for them to sync
        self.restart_node(0, ["-txindex", "-blockfilterindex", "-coinstatsindex", "-txospenderindex"])
        self.wait_until(lambda: all(i["synced"] for i in node.getindexinfo().values()))

        # Returns a list of all running indices by default
        values = {"synced": True, "best_block_height": 200}
        assert_equal(
            node.getindexinfo(),
            {
                "txindex": values,
                "basic block filter index": values,
                "coinstatsindex": values,
                "txospenderindex": values,
            }
        )
        # Specifying an index by name returns only the status of that index
        for i in {"txindex", "basic block filter index", "coinstatsindex", "txospenderindex"}:
            assert_equal(node.getindexinfo(i), {i: values})

        # Specifying an unknown index name returns an empty result
        assert_equal(node.getindexinfo("foo"), {})


if __name__ == '__main__':
    RpcMiscTest(__file__).main()
