//! STAGING-ONLY: complete a market sale on behalf of a synthetic Buyer B.
//!
//! Production has no trigger that decides "your listing sold" — that needs the
//! seller-facing sold wire contract, which is exactly what the staging experiment
//! is trying to establish. This binary is the synthetic counterparty: it runs the
//! REAL settlement path (`CoreEconomy::settle_sale` → Core's atomic
//! `POST /economy/settle-sale`) and then flips the host's listing to `sold`, so the
//! seller's client sees an authentic completed sale rather than a hand-written row.
//!
//! It is a separate binary precisely so no production HTTP surface grows an
//! experiment hook. It is never deployed and never runs in production.
//!
//! Ordering is deliberate: **settle first, mark sold second.** If settlement fails
//! the listing stays live and nothing has moved. If marking fails after a
//! successful settlement, the coins and ownership are already correct and the
//! listing is merely still shown as active — recoverable, and it never pays twice
//! because `mark_sold` is a once-only transition and clearing is presentation-only.
//!
//! ```text
//! staging-sell --market-db PATH --core-url URL --trade-id ID \
//!              --item CORE_ITEM_ID --seller CLUB --buyer CLUB [--gross N]
//! ```
//! `--gross` defaults to 150, the canonical staging sale.

use openfut_utas_host::market_store::MarketStore;
use openfut_utas_host::{CoreEconomy, EconomySale, HttpCoreClient};

/// FIFA 17's transfer fee, taken from the adapter so this harness can never
/// disagree with the shipped policy about what the seller is owed.
fn fee_for(gross: i64) -> i64 {
    openfut_adapter_fifa17::fut::economy_policy::transfer_market_fee(gross)
}

struct Args {
    market_db: String,
    core_url: String,
    trade_id: String,
    item: String,
    seller: String,
    buyer: String,
    gross: i64,
}

fn parse_args() -> Result<Args, String> {
    let mut market_db = None;
    let mut core_url = None;
    let mut trade_id = None;
    let mut item = None;
    let mut seller = None;
    let mut buyer = None;
    let mut gross = 150i64;
    let argv: Vec<String> = std::env::args().skip(1).collect();
    let mut i = 0;
    while i < argv.len() {
        let need = |i: usize| -> Result<String, String> {
            argv.get(i + 1)
                .cloned()
                .ok_or_else(|| format!("{} needs a value", argv[i]))
        };
        match argv[i].as_str() {
            "--market-db" => market_db = Some(need(i)?),
            "--core-url" => core_url = Some(need(i)?),
            "--trade-id" => trade_id = Some(need(i)?),
            "--item" => item = Some(need(i)?),
            "--seller" => seller = Some(need(i)?),
            "--buyer" => buyer = Some(need(i)?),
            "--gross" => gross = need(i)?.parse().map_err(|e| format!("--gross: {e}"))?,
            other => return Err(format!("unknown argument {other}")),
        }
        i += 2;
    }
    Ok(Args {
        market_db: market_db.ok_or("--market-db is required")?,
        core_url: core_url.ok_or("--core-url is required")?,
        trade_id: trade_id.ok_or("--trade-id is required")?,
        item: item.ok_or("--item is required")?,
        seller: seller.ok_or("--seller is required")?,
        buyer: buyer.ok_or("--buyer is required")?,
        gross,
    })
}

fn main() {
    let args = match parse_args() {
        Ok(a) => a,
        Err(e) => {
            eprintln!("staging-sell: {e}");
            eprintln!(
                "usage: staging-sell --market-db PATH --core-url URL --trade-id ID \
                 --item CORE_ITEM_ID --seller CLUB --buyer CLUB [--gross N]"
            );
            std::process::exit(2);
        }
    };

    // Refuse to run against anything that looks like production state. This binary
    // exists to keep an experiment isolated, so the guard belongs here rather than
    // only in the caller.
    for (label, value) in [
        ("--market-db", &args.market_db),
        ("--core-url", &args.core_url),
    ] {
        if value.contains("openfut-promotion")
            || value.contains(":18080")
            || value.contains(":8099")
        {
            eprintln!("staging-sell: REFUSING to touch production via {label}={value}");
            std::process::exit(3);
        }
    }

    let fee = fee_for(args.gross);
    let proceeds = args.gross - fee;
    println!(
        "staging-sell: gross={} fee={} proceeds={} (floor 5%, fee+proceeds==gross)",
        args.gross, fee, proceeds
    );

    let core = HttpCoreClient::new(args.core_url.clone(), "fifa17");
    let sale = EconomySale {
        item_id: &args.item,
        seller_club_id: Some(&args.seller),
        buyer_club_id: Some(&args.buyer),
        gross: args.gross,
        fee,
    };

    // 1. Settle atomically in Core: buyer debited, item transferred, seller paid net.
    let receipt = match core.settle_sale(&sale) {
        Ok(r) => r,
        Err(e) => {
            eprintln!("staging-sell: settlement FAILED, listing left live: {e:?}");
            std::process::exit(1);
        }
    };
    println!(
        "staging-sell: SETTLED item={} card={} seller={} -> buyer={:?} \
         seller_balance={} buyer_balance={:?} squad_slots_freed={}",
        receipt.item_id,
        receipt.card_id,
        receipt.seller_club_id,
        receipt.buyer_club_id,
        receipt.seller_balance,
        receipt.buyer_balance,
        receipt.squad_slots_freed
    );

    // 2. Only now does the seller's listing become `sold`, so the client can be
    //    shown a completed sale.
    let rt = match tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
    {
        Ok(rt) => rt,
        Err(e) => {
            eprintln!("staging-sell: runtime: {e} (settlement already committed)");
            std::process::exit(1);
        }
    };
    rt.block_on(async {
        let store = match MarketStore::open(&args.market_db).await {
            Ok(s) => s,
            Err(e) => {
                eprintln!(
                    "staging-sell: market store {} failed to open: {e} \
                     (settlement already committed — coins/ownership are correct)",
                    args.market_db
                );
                std::process::exit(1);
            }
        };
        match store.mark_sold(&args.trade_id).await {
            Ok(true) => println!("staging-sell: listing {} -> sold", args.trade_id),
            Ok(false) => println!(
                "staging-sell: listing {} was NOT live (already sold/cancelled) — \
                 no second sale, nothing changed",
                args.trade_id
            ),
            Err(e) => {
                eprintln!("staging-sell: mark_sold failed: {e}");
                std::process::exit(1);
            }
        }
        match store.uncleared_sold().await {
            Ok(rows) => println!("staging-sell: uncleared sold rows now {}", rows.len()),
            Err(e) => eprintln!("staging-sell: uncleared_sold read failed: {e}"),
        }
    });
}
