#![allow(dead_code)]
use alloy::hex;
use alloy::signers::local::PrivateKeySigner;
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use std::sync::Once;

const DEFAULT_BASE_URL: &str = "http://localhost:8080";

static INIT: Once = Once::new();

fn load_env() {
    INIT.call_once(|| {
        let _ = dotenvy::dotenv();
    });
}

pub fn base_url() -> String {
    load_env();
    std::env::var("API_BASE_URL").unwrap_or_else(|_| DEFAULT_BASE_URL.to_string())
}

pub fn client() -> monaco_sdk::Client {
    monaco_sdk::Client::new(&base_url())
}

pub fn authed_client(token: &str) -> monaco_sdk::Client {
    let mut headers = HeaderMap::new();
    headers.insert(
        AUTHORIZATION,
        HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(),
    );
    let http = reqwest::ClientBuilder::new()
        .default_headers(headers)
        .build()
        .unwrap();
    monaco_sdk::Client::new_with_client(&base_url(), http)
}

pub async fn authenticate() -> (String, String, PrivateKeySigner) {
    let signer = PrivateKeySigner::random();
    authenticate_with_signer(signer).await
}

/// Authenticate with the funded wallet from PRIVATE_KEY env var.
/// Required for tests that create orders (need balance).
pub async fn authenticate_funded() -> (String, String, PrivateKeySigner) {
    load_env();
    let key = std::env::var("PRIVATE_KEY")
        .expect("PRIVATE_KEY env var required for order tests — set it in .env at repo root");
    let signer: PrivateKeySigner = key.parse().unwrap();
    authenticate_with_signer(signer).await
}

/// Authenticate with a fresh wallet and fund it from the faucet. Returns the
/// access token, address, signer, and the mint response — a new wallet is
/// created on every call so callers are not tripped up by the 24h per-address
/// faucet rate limit.
pub async fn authenticate_and_faucet() -> (
    String,
    String,
    PrivateKeySigner,
    monaco_sdk::types::MintTokensResponse,
) {
    let (token, address, signer) = authenticate().await;
    let authed = authed_client(&token);
    let mint = authed
        .mint_tokens()
        .await
        .expect("faucet mint_tokens failed")
        .into_inner();
    (token, address, signer, mint)
}

async fn authenticate_with_signer(signer: PrivateKeySigner) -> (String, String, PrivateKeySigner) {
    let c = client();
    let address = format!("{:?}", signer.address());

    let challenge = c
        .create_challenge(&monaco_sdk::types::ChallengeRequest {
            address: address.parse().unwrap(),
            chain_id: None,
            client_id: None,
        })
        .await
        .unwrap()
        .into_inner();

    let message = challenge.message.unwrap();
    let nonce = challenge.nonce.unwrap();

    let signature = alloy::signers::Signer::sign_message(&signer, message.as_bytes())
        .await
        .unwrap();
    let sig_hex = format!("0x{}", hex::encode(signature.as_bytes()));

    let verify = c
        .verify_signature(&monaco_sdk::types::VerifyRequest {
            address: address.parse().unwrap(),
            chain_id: None,
            client_id: None,
            nonce: nonce.parse().unwrap(),
            signature: sig_hex.parse().unwrap(),
        })
        .await
        .unwrap()
        .into_inner();

    (verify.access_token.unwrap(), address, signer)
}

pub async fn first_pair_id() -> String {
    let pairs = client()
        .list_trading_pairs(None, Some(true), None, None, None, None)
        .await
        .unwrap()
        .into_inner();

    pairs
        .trading_pairs
        .unwrap()
        .into_iter()
        .next()
        .unwrap()
        .id
        .unwrap()
        .to_string()
}
