use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use roboticus_core::{PaymentHandler, Result, RoboticusError};
use serde::{Deserialize, Serialize};
use tracing::debug;

use crate::wallet::Wallet;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentRequirements {
    pub amount: f64,
    pub recipient: String,
    pub chain_id: u64,
}

#[derive(Debug, Clone)]
pub struct X402Handler;

impl X402Handler {
    pub fn new() -> Self {
        Self
    }

    pub fn build_payment_header(amount: f64, recipient: &str, authorization: &str) -> String {
        format!("x402 amount={amount} recipient={recipient} auth={authorization}")
    }

    pub async fn handle_402(response_body: &serde_json::Value, wallet: &Wallet) -> Result<String> {
        let requirements = Self::parse_payment_requirements(response_body)?;

        // Validate that the requested chain_id matches the wallet's configured chain.
        // Without this check, a malicious server could extract a valid payment signature
        // for an arbitrary chain.
        if requirements.chain_id != wallet.chain_id() {
            return Err(RoboticusError::Wallet(format!(
                "payment chain_id {} does not match wallet chain_id {}",
                requirements.chain_id,
                wallet.chain_id()
            )));
        }

        debug!(
            amount = requirements.amount,
            recipient = %requirements.recipient,
            "handling 402 payment"
        );

        let auth_message = format!(
            "pay:{}:{}:{}",
            requirements.amount, requirements.recipient, requirements.chain_id
        );
        let authorization = wallet.sign_message(&auth_message).await?;

        Ok(Self::build_payment_header(
            requirements.amount,
            &requirements.recipient,
            &authorization,
        ))
    }

    pub fn parse_payment_requirements(body: &serde_json::Value) -> Result<PaymentRequirements> {
        let amount = body.get("amount").and_then(|v| v.as_f64()).ok_or_else(|| {
            RoboticusError::Wallet("missing or invalid 'amount' in payment requirements".into())
        })?;

        if !amount.is_finite() || amount <= 0.0 {
            return Err(RoboticusError::Wallet(format!(
                "payment amount must be a positive finite number, got {amount}"
            )));
        }

        let recipient = body
            .get("recipient")
            .and_then(|v| v.as_str())
            .ok_or_else(|| {
                RoboticusError::Wallet(
                    "missing or invalid 'recipient' in payment requirements".into(),
                )
            })?
            .to_string();

        if !is_valid_eth_address(&recipient) {
            return Err(RoboticusError::Wallet(format!(
                "invalid recipient address format: must start with '0x' followed by 40 hex characters, got '{recipient}'"
            )));
        }

        let chain_id = body
            .get("chain_id")
            .and_then(|v| v.as_u64())
            .ok_or_else(|| {
                RoboticusError::Wallet(
                    "missing or invalid 'chain_id' in payment requirements".into(),
                )
            })?;

        Ok(PaymentRequirements {
            amount,
            recipient,
            chain_id,
        })
    }
}

impl Default for X402Handler {
    fn default() -> Self {
        Self::new()
    }
}

/// Bridges `roboticus-wallet` into the generic `PaymentHandler` trait so the
/// LLM HTTP client can autonomously pay for 402-gated resources without a
/// direct dependency on this crate.
pub struct WalletPaymentHandler {
    wallet: Arc<Wallet>,
    treasury_policy: Option<crate::treasury::TreasuryPolicy>,
}

impl WalletPaymentHandler {
    pub fn new(wallet: Arc<Wallet>) -> Self {
        Self {
            wallet,
            treasury_policy: None,
        }
    }

    /// Attach a treasury policy to enforce per-payment caps from configuration.
    pub fn with_treasury_policy(mut self, policy: crate::treasury::TreasuryPolicy) -> Self {
        self.treasury_policy = Some(policy);
        self
    }
}

impl PaymentHandler for WalletPaymentHandler {
    fn handle_payment_required(
        &self,
        response_body: &serde_json::Value,
    ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + '_>> {
        // Clone the body so the future only borrows `&self` (the `'_` lifetime).
        let body = response_body.clone();
        let policy = self.treasury_policy.clone();
        Box::pin(async move {
            // Enforce treasury policy per-payment cap before signing.
            // NOTE: Only check_per_payment is enforced here because hourly/daily/reserve
            // checks require aggregate spending state (recent_hourly_total, balance, etc.)
            // that the 402 handler doesn't track. A full TreasuryGuard with spending
            // ledger integration would be needed for check_all().
            if let (Some(policy), Some(amount)) =
                (policy.as_ref(), body.get("amount").and_then(|v| v.as_f64()))
            {
                policy.check_per_payment(amount)?;
            }
            X402Handler::handle_402(&body, &self.wallet).await
        })
    }
}

/// Validates that a string is a well-formed Ethereum address: starts with "0x"
/// followed by exactly 40 hexadecimal characters.
fn is_valid_eth_address(addr: &str) -> bool {
    addr.len() == 42 && addr.starts_with("0x") && addr[2..].chars().all(|c| c.is_ascii_hexdigit())
}

#[cfg(test)]
mod tests {
    use super::*;
    use roboticus_core::config::WalletConfig;
    use tempfile::TempDir;

    fn sample_body() -> serde_json::Value {
        serde_json::json!({
            "amount": 0.05,
            "recipient": "0xabcdef1234567890abcdef1234567890abcdef12",
            "chain_id": 8453
        })
    }

    #[test]
    fn build_payment_header_format() {
        let header = X402Handler::build_payment_header(0.05, "0xrecipient", "0xsignature");
        assert!(header.contains("amount=0.05"));
        assert!(header.contains("recipient=0xrecipient"));
        assert!(header.contains("auth=0xsignature"));
    }

    #[test]
    fn parse_payment_requirements_valid() {
        let body = sample_body();
        let req = X402Handler::parse_payment_requirements(&body).unwrap();
        assert!((req.amount - 0.05).abs() < f64::EPSILON);
        assert_eq!(req.recipient, "0xabcdef1234567890abcdef1234567890abcdef12");
        assert_eq!(req.chain_id, 8453);
    }

    #[test]
    fn parse_payment_requirements_missing_amount() {
        let body = serde_json::json!({"recipient": "0x123", "chain_id": 1});
        assert!(X402Handler::parse_payment_requirements(&body).is_err());
    }

    #[test]
    fn parse_payment_requirements_missing_recipient() {
        let body = serde_json::json!({"amount": 1.0, "chain_id": 1});
        assert!(X402Handler::parse_payment_requirements(&body).is_err());
    }

    #[test]
    fn parse_payment_requirements_missing_chain_id() {
        let body = serde_json::json!({"amount": 1.0, "recipient": "0xabcdef1234567890abcdef1234567890abcdef12"});
        assert!(X402Handler::parse_payment_requirements(&body).is_err());
    }

    #[test]
    fn parse_payment_requirements_invalid_recipient_too_short() {
        let body = serde_json::json!({"amount": 1.0, "recipient": "0x123", "chain_id": 1});
        let err = X402Handler::parse_payment_requirements(&body).unwrap_err();
        assert!(err.to_string().contains("invalid recipient address format"));
    }

    #[test]
    fn parse_payment_requirements_invalid_recipient_no_prefix() {
        let body = serde_json::json!({"amount": 1.0, "recipient": "abcdef1234567890abcdef1234567890abcdef12ab", "chain_id": 1});
        let err = X402Handler::parse_payment_requirements(&body).unwrap_err();
        assert!(err.to_string().contains("invalid recipient address format"));
    }

    #[test]
    fn parse_payment_requirements_invalid_recipient_non_hex() {
        let body = serde_json::json!({"amount": 1.0, "recipient": "0xZZZZZZ1234567890abcdef1234567890abcdef12", "chain_id": 1});
        let err = X402Handler::parse_payment_requirements(&body).unwrap_err();
        assert!(err.to_string().contains("invalid recipient address format"));
    }

    #[tokio::test]
    async fn handle_402_flow() {
        let dir = TempDir::new().unwrap();
        let config = WalletConfig {
            path: dir.path().join("wallet.json"),
            chain_id: 8453,
            rpc_url: "https://mainnet.base.org".into(),
        };
        let wallet = Wallet::load_or_generate(&config).await.unwrap();
        let body = sample_body();

        let header = X402Handler::handle_402(&body, &wallet).await.unwrap();
        assert!(header.starts_with("x402 "));
        assert!(header.contains("amount=0.05"));
        assert!(header.contains("auth=0x"));
    }

    #[test]
    fn parse_payment_requirements_zero_amount() {
        let body = serde_json::json!({"amount": 0.0, "recipient": "0xabcdef1234567890abcdef1234567890abcdef12", "chain_id": 1});
        let err = X402Handler::parse_payment_requirements(&body).unwrap_err();
        assert!(err.to_string().contains("positive finite number"));
    }

    #[test]
    fn parse_payment_requirements_negative_amount() {
        let body = serde_json::json!({"amount": -1.0, "recipient": "0xabcdef1234567890abcdef1234567890abcdef12", "chain_id": 1});
        let err = X402Handler::parse_payment_requirements(&body).unwrap_err();
        assert!(err.to_string().contains("positive finite number"));
    }

    #[test]
    fn parse_payment_requirements_infinity_amount() {
        let body = serde_json::json!({"amount": f64::INFINITY, "recipient": "0xabcdef1234567890abcdef1234567890abcdef12", "chain_id": 1});
        // serde_json::json! cannot represent infinity directly — it becomes null
        // So this tests the "missing amount" path. The is_finite check guards
        // against programmatic construction of PaymentRequirements.
        assert!(X402Handler::parse_payment_requirements(&body).is_err());
    }

    #[test]
    fn x402_handler_new_creates_instance() {
        let handler = X402Handler::new();
        // X402Handler is a unit struct; verify Debug is implemented
        let debug = format!("{:?}", handler);
        assert!(debug.contains("X402Handler"));
    }

    #[test]
    fn x402_handler_default_creates_instance() {
        let handler = X402Handler;
        let debug = format!("{:?}", handler);
        assert!(debug.contains("X402Handler"));
    }

    #[test]
    fn x402_handler_clone() {
        let handler = X402Handler::new();
        let cloned = handler.clone();
        let _ = format!("{:?}", cloned);
    }

    #[tokio::test]
    async fn handle_402_rejects_wrong_chain_id() {
        let dir = TempDir::new().unwrap();
        let config = WalletConfig {
            path: dir.path().join("wallet.json"),
            chain_id: 8453,
            rpc_url: "https://mainnet.base.org".into(),
        };
        let wallet = Wallet::load_or_generate(&config).await.unwrap();
        // Request payment on chain_id 1 (Ethereum mainnet) while wallet is configured for 8453 (Base)
        let body = serde_json::json!({
            "amount": 0.05,
            "recipient": "0xabcdef1234567890abcdef1234567890abcdef12",
            "chain_id": 1
        });
        let err = X402Handler::handle_402(&body, &wallet).await.unwrap_err();
        assert!(err.to_string().contains("does not match wallet chain_id"));
    }
}
