use alloy::hex;
use alloy::signers::Signer;
use rpc::RpcProxy;
use sdk_4mica::client::recipient::RecipientClient;
use sdk_4mica::{
    Client, Config, PaymentGuaranteeRequestClaims, SigningScheme, U256, error::RemunerateError,
};
use std::time::{Duration, Instant};

use crate::common::{
    ETH_ASSET_ADDRESS, build_authed_recipient_config, build_authed_user_config,
    get_chain_timestamp, mine_confirmations, wait_for_collateral_increase,
};

mod common;

const REMUNERATION_GRACE_SECS: u64 = 14 * 24 * 60 * 60;
const OVERDUE_BUFFER_SECS: u64 = 5;

fn is_historical_state_error(message: &str) -> bool {
    message.contains("historical state")
        || message.contains("failed to get account")
        || message.contains("not available")
}

async fn ensure_core_available<S>(tag: &str, config: &Config<S>) -> anyhow::Result<()> {
    let rpc_url = config.rpc_url.as_str();
    tokio::time::timeout(Duration::from_secs(5), async {
        let rpc_proxy = RpcProxy::new(rpc_url)
            .map_err(|err| anyhow::anyhow!("[{tag}] core RPC at {rpc_url} unavailable: {err}"))?;
        rpc_proxy
            .get_public_params()
            .await
            .map_err(|err| anyhow::anyhow!("[{tag}] core RPC at {rpc_url} unavailable: {err}"))
    })
    .await
    .map_err(|_| anyhow::anyhow!("[{tag}] timed out waiting for core RPC at {rpc_url}"))??;
    Ok(())
}

async fn wait_for_tab_remunerated<S>(
    recipient_client: &Client<S>,
    tab_id: U256,
) -> anyhow::Result<()> {
    let poll_interval = Duration::from_millis(200);
    let timeout = Duration::from_secs(10);
    let start = Instant::now();

    loop {
        let status = recipient_client
            .recipient
            .get_tab_payment_status(tab_id)
            .await?;
        if status.remunerated {
            return Ok(());
        }

        if start.elapsed() > timeout {
            anyhow::bail!("timed out waiting for tab {tab_id:?} to be remunerated");
        }

        tokio::time::sleep(poll_interval).await;
    }
}

async fn resolve_next_req_id<S>(
    recipient: &RecipientClient<S>,
    tab_id: U256,
) -> anyhow::Result<U256>
where
    S: Signer + Sync,
{
    if let Some(latest) = recipient.get_latest_guarantee(tab_id).await? {
        return Ok(latest.req_id + U256::from(1u64));
    }

    Ok(U256::ZERO)
}

async fn resolve_overdue_timestamp<S>(
    recipient: &RecipientClient<S>,
    config: &Config<S>,
    tab_id: U256,
) -> anyhow::Result<u64>
where
    S: Signer + Sync,
{
    let chain_now = get_chain_timestamp(config).await?;
    let server_now = common::get_now().as_secs();
    let now = std::cmp::min(chain_now, server_now);
    let overdue = now.saturating_sub(REMUNERATION_GRACE_SECS + OVERDUE_BUFFER_SECS);
    if let Some(tab) = recipient.get_tab(tab_id).await?
        && tab.status == "OPEN"
        && tab.start_timestamp > 0
    {
        return Ok(std::cmp::max(tab.start_timestamp as u64, overdue));
    }

    Ok(overdue)
}

async fn ensure_overdue_tab<S>(
    recipient: &RecipientClient<S>,
    config: &Config<S>,
    user_address: &str,
    recipient_address: &str,
    ttl_secs: u64,
) -> anyhow::Result<U256>
where
    S: Signer + Sync,
{
    let chain_now = get_chain_timestamp(config).await?;
    let server_now = common::get_now().as_secs();
    let now = std::cmp::min(chain_now, server_now);
    let overdue_cutoff = now.saturating_sub(REMUNERATION_GRACE_SECS + OVERDUE_BUFFER_SECS);

    let mut tab_id = recipient
        .create_tab(
            user_address.to_string(),
            recipient_address.to_string(),
            None,
            Some(ttl_secs),
            1,
        )
        .await?
        .tab_id;

    if let Some(tab) = recipient.get_tab(tab_id).await?
        && tab.status == "OPEN"
        && tab.start_timestamp > 0
    {
        let start_ts = tab.start_timestamp as u64;
        if start_ts > overdue_cutoff {
            common::close_tab(tab_id).await?;
            tab_id = recipient
                .create_tab(
                    user_address.to_string(),
                    recipient_address.to_string(),
                    None,
                    Some(ttl_secs),
                    1,
                )
                .await?
                .tab_id;
        }
    }

    Ok(tab_id)
}

#[tokio::test]
#[serial_test::file_serial]
#[test_log::test]
async fn test_recipient_remuneration() -> anyhow::Result<()> {
    if common::skip_without_local_core_stack() {
        return Ok(());
    }

    let user_config = build_authed_user_config(
        "http://localhost:3000",
        "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97",
    )
    .await?;
    let user_config_clone = user_config.clone();

    ensure_core_available("test_recipient_remuneration:user", &user_config_clone).await?;
    let user_address = user_config_clone.signer.address().to_string();
    let user_client = Client::new(user_config).await?;

    let recipient_config = build_authed_recipient_config(
        "http://localhost:3000",
        // Default anvil account with prefunded ETH for gas on local fork/dev networks.
        "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
    )
    .await?;
    let recipient_config_clone = recipient_config.clone();

    let recipient_address = recipient_config_clone.signer.address().to_string();
    let recipient_client = Client::new(recipient_config).await?;

    let user_info = user_client.user.get_user().await?;
    let eth_asset_before =
        common::extract_asset_info(&user_info, ETH_ASSET_ADDRESS).expect("ETH asset not found");

    let core_total_before = recipient_client
        .recipient
        .get_user_asset_balance(user_address.clone(), ETH_ASSET_ADDRESS.to_string())
        .await?
        .map(|info| info.total)
        .unwrap_or(U256::ZERO);
    let deposit_amount = U256::from(2_000_000_000_000_000_000u128); // 2 ETH
    let _receipt = user_client.user.deposit(deposit_amount, None).await?;
    mine_confirmations(&user_config_clone, 1).await?;

    let user_info_after = user_client.user.get_user().await?;
    let eth_asset = common::extract_asset_info(&user_info_after, ETH_ASSET_ADDRESS)
        .expect("ETH asset not found");
    assert_eq!(
        eth_asset.collateral,
        eth_asset_before.collateral + deposit_amount
    );

    wait_for_collateral_increase(
        &recipient_client.recipient,
        &user_address,
        ETH_ASSET_ADDRESS,
        core_total_before,
        deposit_amount,
    )
    .await?;

    let tab_id = ensure_overdue_tab(
        &recipient_client.recipient,
        &recipient_config_clone,
        &user_address,
        &recipient_address,
        3600 * 24 * 21,
    )
    .await?;

    let guarantee_amount = U256::from(1_000_000_000_000_000_000u128); // 1 ETH
    let req_id = resolve_next_req_id(&recipient_client.recipient, tab_id).await?;
    let start_timestamp =
        resolve_overdue_timestamp(&recipient_client.recipient, &recipient_config_clone, tab_id)
            .await?;
    let claims = PaymentGuaranteeRequestClaims {
        user_address: user_address.clone(),
        recipient_address: recipient_address.clone(),
        tab_id,
        req_id,
        amount: guarantee_amount,
        timestamp: start_timestamp,
        asset_address: "0x0000000000000000000000000000000000000000".into(),
    };
    // println!("[recipient] claims struct: {:?}", claims);

    let payment_sig = user_client
        .user
        .sign_payment(claims.clone(), SigningScheme::Eip712)
        .await?;

    let bls_cert = recipient_client
        .recipient
        .issue_payment_guarantee(claims.clone(), payment_sig.signature, payment_sig.scheme)
        .await?;
    println!(
        "[recipient] issued cert:\nclaims=0x{}\nsignature=0x{}",
        bls_cert.claims().to_hex(),
        bls_cert.signature().to_hex()
    );

    let guarantee = recipient_client
        .recipient
        .verify_payment_guarantee(&bls_cert)?;

    println!("[recipient] verified guarantee:\n{:?}", guarantee);

    let claims_bytes = bls_cert.claims().to_vec();
    // println!(
    //     "[recipient] encoded claims bytes=0x{}",
    //     hex::encode(&claims_bytes)
    // );
    let expected_bytes: Vec<u8> = guarantee.clone().try_into()?;
    // println!(
    //     "[recipient] expected claims bytes=0x{}",
    //     hex::encode(&expected_bytes)
    // );
    assert_eq!(claims_bytes, expected_bytes);

    let status_before = recipient_client
        .recipient
        .get_tab_payment_status(tab_id)
        .await?;

    let remunerate_result = recipient_client.recipient.remunerate(bls_cert).await;
    match remunerate_result {
        Ok(_) => {}
        Err(RemunerateError::Transport(msg)) if is_historical_state_error(&msg) => {
            eprintln!("Skipping remunerate assertions due to non-archive RPC: {msg}");
            return Ok(());
        }
        Err(e) => return Err(anyhow::anyhow!("Failed to remunerate the tab: {}", e)),
    }

    wait_for_tab_remunerated(&recipient_client, tab_id).await?;

    let user_info_after = user_client.user.get_user().await?;
    let eth_asset = common::extract_asset_info(&user_info_after, ETH_ASSET_ADDRESS)
        .expect("ETH asset not found");
    let remaining = guarantee.total_amount.saturating_sub(status_before.paid);
    assert_eq!(
        eth_asset.collateral,
        eth_asset_before.collateral + deposit_amount - remaining
    );

    Ok(())
}

#[tokio::test]
#[serial_test::file_serial]
#[test_log::test]
async fn test_double_remuneration_fails() -> anyhow::Result<()> {
    if common::skip_without_local_core_stack() {
        return Ok(());
    }

    let user_config = build_authed_user_config(
        "http://localhost:3000",
        "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
    )
    .await?;
    let user_config_clone = user_config.clone();

    let user_address = user_config_clone.signer.address().to_string();
    ensure_core_available("test_double_remuneration_fails:user", &user_config_clone).await?;
    let user_client = Client::new(user_config).await?;

    let recipient_config = build_authed_recipient_config(
        "http://localhost:3000",
        "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a",
    )
    .await?;
    let recipient_config_clone = recipient_config.clone();

    let recipient_address = recipient_config_clone.signer.address().to_string();
    let recipient_client = Client::new(recipient_config).await?;

    let core_total_before = recipient_client
        .recipient
        .get_user_asset_balance(user_address.clone(), ETH_ASSET_ADDRESS.to_string())
        .await?
        .map(|info| info.total)
        .unwrap_or(U256::ZERO);
    let deposit_amount = U256::from(2_000_000_000_000_000_000u128); // 2 ETH
    let _receipt = user_client.user.deposit(deposit_amount, None).await?;
    mine_confirmations(&user_config_clone, 1).await?;

    wait_for_collateral_increase(
        &recipient_client.recipient,
        &user_address,
        ETH_ASSET_ADDRESS,
        core_total_before,
        deposit_amount,
    )
    .await?;

    let tab_id = ensure_overdue_tab(
        &recipient_client.recipient,
        &recipient_config_clone,
        &user_address,
        &recipient_address,
        3600 * 24 * 21,
    )
    .await?;

    let guarantee_amount = U256::from(1_000_000_000_000_000_000u128); // 1 ETH
    let req_id = resolve_next_req_id(&recipient_client.recipient, tab_id).await?;
    let start_timestamp =
        resolve_overdue_timestamp(&recipient_client.recipient, &recipient_config_clone, tab_id)
            .await?;
    let claims = PaymentGuaranteeRequestClaims {
        user_address: user_address.clone(),
        recipient_address: recipient_address.clone(),
        tab_id,
        req_id,
        amount: guarantee_amount,
        timestamp: start_timestamp,
        asset_address: "0x0000000000000000000000000000000000000000".into(),
    };
    println!("[double] claims struct: {:?}", claims);

    let payment_sig = user_client
        .user
        .sign_payment(claims.clone(), SigningScheme::Eip712)
        .await?;

    let bls_cert = recipient_client
        .recipient
        .issue_payment_guarantee(claims.clone(), payment_sig.signature, payment_sig.scheme)
        .await?;
    println!(
        "[double] issued cert: claims=0x{} signature=0x{}",
        bls_cert.claims().to_hex(),
        bls_cert.signature().to_hex()
    );

    let guarantee = recipient_client
        .recipient
        .verify_payment_guarantee(&bls_cert)?;

    let claims_bytes = bls_cert.claims().to_vec();
    println!(
        "[double] encoded claims bytes=0x{}",
        hex::encode(&claims_bytes)
    );
    let expected_bytes: Vec<u8> = guarantee.try_into()?;
    println!(
        "[double] expected claims bytes=0x{}",
        hex::encode(&expected_bytes)
    );
    assert_eq!(claims_bytes, expected_bytes);

    let first_result = recipient_client
        .recipient
        .remunerate(bls_cert.clone())
        .await;
    match first_result {
        Ok(_) => {}
        Err(RemunerateError::Transport(msg)) if is_historical_state_error(&msg) => {
            eprintln!("Skipping double-remuneration assertions due to non-archive RPC: {msg}");
            return Ok(());
        }
        Err(e) => return Err(e.into()),
    }

    wait_for_tab_remunerated(&recipient_client, tab_id).await?;

    let result = recipient_client.recipient.remunerate(bls_cert).await;

    assert!(matches!(
        result,
        Err(RemunerateError::TabPreviouslyRemunerated)
    ));

    Ok(())
}
