use std::str::FromStr;

use anyhow::{Context, Result, bail};
use clap::Parser;
use fedimint_core::Amount;
use lightning_invoice::Bolt11Invoice;
use lnurl::lightning_address::LightningAddress;
use lnurl::lnurl::LnUrl;

/// A tiny command line tool to fetch BOLT11 invoices from LNURLs
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    /// The LNURL to fetch the invoice from (can be LNURL or Lightning Address)
    lnurl: String,

    /// The amount in millisatoshis to request
    #[arg(long, short)]
    amount: Amount,

    /// Optional comment to include with the payment
    #[arg(long, short)]
    comment: Option<String>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let args = Args::parse();
    let invoice = get_invoice(&args.lnurl, args.amount, args.comment).await?;

    println!("{invoice}");
    Ok(())
}

/// Get LN invoice with given settings
async fn get_invoice(
    lnurl: &str,
    amount: Amount,
    lnurl_comment: Option<String>,
) -> Result<Bolt11Invoice> {
    let info = lnurl.trim();

    // If not a BOLT11 invoice, try to parse as LNURL or Lightning Address
    let lnurl = if info.to_lowercase().starts_with("lnurl") {
        LnUrl::from_str(info).context("Invalid LNURL format")?
    } else if info.contains('@') {
        LightningAddress::from_str(info)
            .context("Invalid Lightning Address format")?
            .lnurl()
    } else {
        bail!("Invalid invoice, LNURL, or Lightning Address");
    };

    let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
    let response = async_client.make_request(&lnurl.url).await?;

    match response {
        lnurl::LnUrlResponse::LnUrlPayResponse(response) => {
            let invoice = async_client
                .get_invoice(&response, amount.msats, None, lnurl_comment.as_deref())
                .await?;

            let invoice = Bolt11Invoice::from_str(invoice.invoice())
                .context("LNURL server returned an invalid invoice")?;
            let invoice_amount = invoice.amount_milli_satoshis();

            if invoice_amount != Some(amount.msats) {
                bail!(
                    "The amount generated by the LNURL ({invoice_amount:?}) is different from the requested amount ({amount}), try again using a different amount"
                );
            }

            Ok(invoice)
        }
        other => {
            bail!("Unexpected response from LNURL: {other:?}");
        }
    }
}
