#!/usr/bin/env python3

# This script produces a summary of channel stats, sorted by routing performance:
#
# - Displays PeerID, SCID, and Alias for each channel
# - Sorts first by net fees (income - expenses) (descending)
# - Then sorts by "Success Per Day" (SPD) (descending)
# - Then sorts by Age in days (ascending)
# - Uses `clboss-recent-earnings` to limit the history considered
#
# The channels at the top of the list are good, the ones at the bottom are bad.

import os
import subprocess
import argparse
import json
from tabulate import tabulate
from wcwidth import wcswidth
from clboss.alias_cache import lookup_alias

def run_lightning_cli_command(lightning_dir, network_option, command, *args):
    try:
        command = ['lightning-cli', network_option, command, *args]
        if lightning_dir:
            command = command[:2] + [f'--lightning-dir={lightning_dir}'] + command[2:]
        result = subprocess.run(command, capture_output=True, text=True, check=True)
        return json.loads(result.stdout)
    except subprocess.CalledProcessError as e:
        print(f"Command '{command}' failed with error: {e}")
    except json.JSONDecodeError as e:
        print(f"Failed to parse JSON from command '{command}': {e}")
    return None

def calculate_peer_score(peer):
    in_earnings = peer.get("in_earnings", 0)
    out_earnings = peer.get("out_earnings", 0)
    in_expenditures = peer.get("in_expenditures", 0)
    out_expenditures = peer.get("out_expenditures", 0)
    success_per_day = peer.get("success_per_day", 0)
    income_minus_expenditures = in_earnings + out_earnings - in_expenditures - out_expenditures
    return income_minus_expenditures, success_per_day

def pad_string(s, width):
    pad = width - wcswidth(s)
    return s + ' ' * pad

import argparse

def main():
    parser = argparse.ArgumentParser(description="Run lightning-cli with specified network")

    parser.add_argument('--mainnet', action='store_true', help='Run on mainnet')
    parser.add_argument('--testnet', action='store_true', help='Run on testnet')
    parser.add_argument('--signet', action='store_true', help='Run on signet')
    parser.add_argument('--regtest', action='store_true', help='Run on regtest')
    parser.add_argument('--network', help='Set the network explicitly')
    parser.add_argument('--lightning-dir', help='lightning data location')
    parser.add_argument('--days', type=int,
                        help='Number of days of history to use with clboss-recent-earnings')

    args = parser.parse_args()

    # Reconcile network option
    if args.network:
        network_option = f'--network={args.network}'
    elif args.testnet:
        network_option = '--network=testnet'
    elif args.signet:
        network_option = '--network=signet'
    elif args.regtest:
        network_option = '--network=regtest'
    else:
        network_option = '--network=bitcoin'	# lightning-cli wants "bitcoin" for mainnet

    if args.lightning_dir:
        lightning_dir = args.lightning_dir
        assert os.path.isdir(lightning_dir), f'"{lightning_dir}" is not a valid directory'
    else:
        lightning_dir = None

    # Run listpeerchannels command
    listpeerchannels_data = run_lightning_cli_command(lightning_dir, network_option, 'listpeerchannels')
    if not listpeerchannels_data:
        return

    channels_data = listpeerchannels_data.get("channels", [])

    channels = {}
    peers = {}
    for channel in channels_data:
        # Skip channels that are not open
        if channel.get("state") != "CHANNELD_NORMAL":
            continue

        short_channel_id = channel.get("short_channel_id")
        peer_id = channel.get("peer_id")
        to_us_msat = channel.get("to_us_msat")
        if short_channel_id and peer_id:
            channels[short_channel_id] = {
                "peerid": peer_id,
                "opener": channel.get("opener"),
                "to_us_msat": to_us_msat,
                "total_msat": channel.get("total_msat")
            }
            if peer_id not in peers:
                peers[peer_id] = {
                    "alias": None,  # Placeholder for alias
                    "in_earnings": 0,
                    "in_expenditures": 0,
                    "out_earnings": 0,
                    "out_expenditures": 0,
                    "age": 0,
                    "success_per_day": 0
                }

    for peer_id in peers.keys():
        alias = lookup_alias(run_lightning_cli_command, lightning_dir, network_option, peer_id)
        peers[peer_id]["alias"] = alias

    # Get recent earnings for each peer
    if args.days:
        recent_data = run_lightning_cli_command(lightning_dir, network_option,
                                                'clboss-recent-earnings', str(args.days))
    else:
        recent_data = run_lightning_cli_command(lightning_dir, network_option,
                                                'clboss-recent-earnings')

    # Run clboss-status command and capture the output
    clboss_status_data = run_lightning_cli_command(lightning_dir, network_option, 'clboss-status')
    if clboss_status_data:
        peer_metrics = clboss_status_data.get("peer_metrics", {})

        recent = recent_data.get("recent", {}) if recent_data else {}

        for id in peers.keys():
            if id in recent:
                peers[id]["in_earnings"] = recent[id].get("in_earnings", 0)
                peers[id]["in_expenditures"] = recent[id].get("in_expenditures", 0)
                peers[id]["out_earnings"] = recent[id].get("out_earnings", 0)
                peers[id]["out_expenditures"] = recent[id].get("out_expenditures", 0)

            if id in peer_metrics:
                peers[id]["age"] = peer_metrics[id].get("age", 0)
                peers[id]["success_per_day"] = peer_metrics[id].get("success_per_day", 0)

    # Sort channels based on the calculated score
    sorted_channels = sorted(channels.keys(), key=lambda cid: (
        -calculate_peer_score(peers[channels[cid]["peerid"]])[0],  # Descending income_minus_expenditures
        -calculate_peer_score(peers[channels[cid]["peerid"]])[1],  # Descending success_per_day
        peers[channels[cid]["peerid"]]["age"]  # Ascending age
    ))

    # Prepare table data
    table_data = []
    max_alias_length = max([wcswidth(peer["alias"]) for peer in peers.values() if peer["alias"]] + [5])  # 5 is the length of "Alias"
    for short_channel_id in sorted_channels:
        peer_id = channels[short_channel_id]["peerid"]
        peer = peers[peer_id]
        income_minus_expenditures, success_per_day = calculate_peer_score(peer)
        alias = pad_string(peer["alias"], max_alias_length)
        opener = "L" if channels[short_channel_id]["opener"] == "local" else "R"
        to_us_msat = f"{channels[short_channel_id]['to_us_msat']:,}"  # With commas
        income_minus_expenditures_formatted = f"{income_minus_expenditures:,}"  # With commas
        spd_formatted = f"x{success_per_day:4.1f}"  # Prefix w/ 'x', strip later, preserves padding
        table_data.append([
            alias,
            short_channel_id,
            opener,
            to_us_msat,
            peer["age"] // 86400,  # Convert age from seconds to days
            income_minus_expenditures_formatted,
            spd_formatted,
            peer_id
        ])

    # Print the table without grid
    table_str = tabulate(table_data,
                         headers=[
                             "Alias",
                             "SCID",
                             "O",
                             "to us",
                             "Age",
                             "Net msat",
                             "SPD",
                             "PeerID"
                         ],
                         tablefmt="plain", stralign="left", numalign="right",
                         colalign=(
                             "left",
                             "left",
                             "left",
                             "right",
                             "right",
                             "right",
                             "left",
                             "left"))

    # Remove the "x" prefix from SPD values in the final output
    table_str = table_str.replace(" x", " ")
    print(table_str)

if __name__ == "__main__":
    main()
