//! Real PayU integration, replacing the mocked Beeceptor calls in
//! `create_order`/`verify_payment`. Matches the checkout contract the
//! frontend (`nxtgauge-frontend-solid/src/lib/payu.ts`) already expects:
//! a form-redirect to `{payu_base_url}/_payment` with a request hash, and
//! a reverse-hash-verified response on callback.
//!
//! Hash formulas are PayU's standard v1 scheme (documented publicly by
//! PayU): request hash covers key/txnid/amount/productinfo/firstname/
//! email/udf1-10/salt; response verification reverses that order and
//! substitutes `status` for the trailing empty slots.

use sha2::{Digest, Sha512};

#[derive(Clone)]
pub struct PayuConfig {
    pub merchant_key: String,
    pub merchant_salt: String,
    pub base_url: String,
    pub surl: String,
    pub furl: String,
}

impl PayuConfig {
    pub fn from_env() -> Self {
        Self {
            merchant_key: std::env::var("PAYU_MERCHANT_KEY").unwrap_or_default(),
            merchant_salt: std::env::var("PAYU_MERCHANT_SALT").unwrap_or_default(),
            base_url: std::env::var("PAYU_BASE_URL")
                .unwrap_or_else(|_| "https://test.payu.in".to_string()),
            surl: std::env::var("PAYU_SURL")
                .unwrap_or_else(|_| "/api/payments/payu/callback".to_string()),
            furl: std::env::var("PAYU_FURL")
                .unwrap_or_else(|_| "/api/payments/payu/callback".to_string()),
        }
    }
}

fn sha512_hex(input: &str) -> String {
    let mut hasher = Sha512::new();
    hasher.update(input.as_bytes());
    hex::encode(hasher.finalize())
}

#[allow(clippy::too_many_arguments)]
pub fn request_hash(
    config: &PayuConfig,
    txnid: &str,
    amount: &str,
    productinfo: &str,
    firstname: &str,
    email: &str,
    udf1: &str,
    udf2: &str,
) -> String {
    let seq = [
        config.merchant_key.as_str(),
        txnid,
        amount,
        productinfo,
        firstname,
        email,
        udf1,
        udf2,
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        config.merchant_salt.as_str(),
    ];
    sha512_hex(&seq.join("|"))
}

/// Verify a PayU response hash. `status` is PayU's returned status string
/// (e.g. "success"/"failure") -- it is part of the hashed sequence, not
/// just metadata, per PayU's reverse-hash formula.
#[allow(clippy::too_many_arguments)]
pub fn verify_response_hash(
    config: &PayuConfig,
    status: &str,
    txnid: &str,
    amount: &str,
    productinfo: &str,
    firstname: &str,
    email: &str,
    udf1: &str,
    udf2: &str,
    received_hash: &str,
) -> bool {
    let seq = [
        config.merchant_salt.as_str(),
        status,
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        udf2,
        udf1,
        email,
        firstname,
        productinfo,
        amount,
        txnid,
        config.merchant_key.as_str(),
    ];
    let expected = sha512_hex(&seq.join("|"));
    expected.eq_ignore_ascii_case(received_hash)
}

pub fn generate_txnid() -> String {
    uuid::Uuid::new_v4().to_string().replace('-', "")
}

/// Format an integer-paise amount as the decimal-rupee string PayU expects
/// in its hash and form fields (e.g. 9900 paise -> "99.00").
pub fn paise_to_rupee_string(paise: i32) -> String {
    format!("{:.2}", paise as f64 / 100.0)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn test_config() -> PayuConfig {
        PayuConfig {
            merchant_key: "testkey".to_string(),
            merchant_salt: "testsalt".to_string(),
            base_url: "https://test.payu.in".to_string(),
            surl: "/s".to_string(),
            furl: "/f".to_string(),
        }
    }

    #[test]
    fn request_hash_is_deterministic_and_order_sensitive() {
        let config = test_config();
        let h1 = request_hash(&config, "txn1", "99.00", "Starter AI Credits", "John", "john@test.com", "u1", "u2");
        let h2 = request_hash(&config, "txn1", "99.00", "Starter AI Credits", "John", "john@test.com", "u1", "u2");
        assert_eq!(h1, h2);

        let h3 = request_hash(&config, "txn2", "99.00", "Starter AI Credits", "John", "john@test.com", "u1", "u2");
        assert_ne!(h1, h3, "different txnid must produce a different hash");
    }

    #[test]
    fn response_hash_round_trips_with_request_hash_shape() {
        let config = test_config();
        // Build a response hash the way PayU would for a success callback,
        // then confirm verify_response_hash accepts it.
        let seq = [
            config.merchant_salt.as_str(),
            "success",
            "",
            "",
            "",
            "",
            "",
            "",
            "",
            "",
            "u2",
            "u1",
            "john@test.com",
            "John",
            "Starter AI Credits",
            "99.00",
            "txn1",
            config.merchant_key.as_str(),
        ];
        let hash = sha512_hex(&seq.join("|"));

        assert!(verify_response_hash(
            &config, "success", "txn1", "99.00", "Starter AI Credits", "John", "john@test.com", "u1", "u2", &hash,
        ));

        // Tampering with the amount must invalidate the hash.
        assert!(!verify_response_hash(
            &config, "success", "txn1", "999.00", "Starter AI Credits", "John", "john@test.com", "u1", "u2", &hash,
        ));
    }
}
