#!/usr/bin/env python3
import sys
from datetime import datetime
from decimal import Decimal
from json import loads
from optparse import OptionParser

from jmbase import EXIT_ARGERROR, jmprint, get_log, utxostr_to_utxo, EXIT_FAILURE
from jmbitcoin import amount_to_sat, amount_to_str
from jmclient import add_base_options, load_program_config, jm_single, get_bond_values

DESCRIPTION = """Given either a Bitcoin UTXO in the form TXID:n
(e.g., 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098:0)
or an amount in either satoshi or bitcoin (e.g., 150000, 0.1, 10.123, 10btc),
calculate fidelity bond values for all possible locktimes in a one-year period
(12 months, you can change that with the `-m --months` option).
By default it uses the values from your joinmarket.cfg,
you can override these with the `-i --interest` and `-e --exponent` options.
Additionally, you can export the orderbook from ob-watcher.py and use the data here
with the `-o --orderbook` option, this will compare the results from this script
with the fidelity bonds in the orderbook.
"""

log = get_log()


def main() -> None:
    parser = OptionParser(
        usage="usage: %prog [options] UTXO or amount",
        description=DESCRIPTION,
    )
    add_base_options(parser)
    parser.add_option(
        "-i",
        "--interest",
        action="store",
        type="float",
        dest="interest",
        help="Interest rate to use for fidelity bond calculation (instead of interest_rate config)",
    )
    parser.add_option(
        "-e",
        "--exponent",
        action="store",
        type="float",
        dest="exponent",
        help="Exponent to use for fidelity bond calculation (instead of bond_value_exponent config)",
    )
    parser.add_option(
        "-m",
        "--months",
        action="store",
        type="int",
        dest="months",
        help="For how many months to calculate the fidelity bond values, each month has its own stats (default 12)",
        default=12,
    )
    parser.add_option(
        "-o",
        "--orderbook",
        action="store",
        type="str",
        dest="path_to_json",
        help="Path to the exported orderbook in JSON format",
    )

    options, args = parser.parse_args()
    load_program_config(config_path=options.datadir)
    if len(args) != 1:
        log.error("Invalid arguments, see --help")
        sys.exit(EXIT_ARGERROR)
    if options.path_to_json:
        try:
            with open(options.path_to_json, "r", encoding="UTF-8") as orderbook:
                orderbook = loads(orderbook.read())
        except FileNotFoundError as exc:
            log.error(exc)
            sys.exit(EXIT_ARGERROR)
    else:
        orderbook = None
    try:
        amount = amount_to_sat(args[0])
        confirm_time = None
    except ValueError:
        # If it's not a valid amount then it has to be a UTXO
        if jm_single().bc_interface is None:
            log.error("For calculation based on UTXO access to Bitcoin Core is required")
            sys.exit(EXIT_FAILURE)
        success, utxo = utxostr_to_utxo(args[0])
        if not success:
            # utxo contains the error message
            log.error(utxo)
            sys.exit(EXIT_ARGERROR)
        utxo_data = jm_single().bc_interface.query_utxo_set(utxo, includeconfs=True)[0]
        amount = utxo_data["value"]
        if utxo_data["confirms"] == 0:
            log.warning("Given UTXO is unconfirmed, current time will be used as confirmation time")
            confirm_time = None
        elif utxo_data["confirms"] < 0:
            log.error("Given UTXO is invalid, reason: conflicted")
            sys.exit(EXIT_ARGERROR)
        else:
            current_height = jm_single().bc_interface.get_current_block_height()
            block_hash = jm_single().bc_interface.get_block_hash(current_height - utxo_data["confirms"] + 1)
            confirm_time = jm_single().bc_interface.get_block_time(block_hash)

    parameters, results = get_bond_values(amount,
                                          options.months,
                                          confirm_time,
                                          options.interest,
                                          options.exponent,
                                          orderbook)
    jmprint(f"Amount locked: {amount_to_str(amount)}")
    jmprint(f"Confirmation time: {datetime.fromtimestamp(parameters['confirm_time'])}")
    jmprint(f"Interest rate: {parameters['interest']} ({parameters['interest'] * 100}%)")
    jmprint(f"Exponent: {parameters['exponent']}")
    jmprint(f"\nFIDELITY BOND VALUES (BTC^{parameters['exponent']})")
    jmprint("\nSee /docs/fidelity-bonds.md for complete formula and more")

    for result in results:
        locktime = datetime.fromtimestamp(result["locktime"])
        # Mimic the locktime value the user would have to insert to create such fidelity bond
        jmprint(f"\nLocktime: {locktime.year}-{locktime.month}")
        # Mimic orderbook value
        jmprint(f"Bond value: {float(Decimal(result['value']) / Decimal(1e16)):.16f}")
        if options.path_to_json:
            jmprint(f"Weight: {result['weight']:.5f} ({result['weight'] * 100:.2f}% of all bonds)")
            jmprint(f"Top {result['percentile']}% of the orderbook by value")


if __name__ == "__main__":
    main()
