// file: kb_lib/src/dex/pump_fun.rs

//! Pump.fun bonding-curve transaction decoder.

const PUMP_FUN_BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
const PUMP_FUN_SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];

/// Decoded Pump.fun `create_v2` token event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PumpFunCreateV2TokenDecoded {
    /// Parent transaction id.
    pub transaction_id: i64,
    /// Parent instruction id.
    pub instruction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Program id.
    pub program_id: std::string::String,
    /// Optional created mint.
    pub mint: std::option::Option<std::string::String>,
    /// Optional bonding curve account.
    pub bonding_curve: std::option::Option<std::string::String>,
    /// Optional associated bonding curve account.
    pub associated_bonding_curve: std::option::Option<std::string::String>,
    /// Optional creator / user account.
    pub creator: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Pump.fun bonding-curve trade event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PumpFunTradeDecoded {
    /// Parent transaction id.
    pub transaction_id: i64,
    /// Parent instruction id.
    pub instruction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Program id.
    pub program_id: std::string::String,
    /// Trade side.
    pub trade_side: crate::SwapTradeSide,
    /// Token mint.
    pub mint: std::option::Option<std::string::String>,
    /// Bonding curve account.
    pub bonding_curve: std::option::Option<std::string::String>,
    /// Associated bonding curve token account.
    pub associated_bonding_curve: std::option::Option<std::string::String>,
    /// User token account.
    pub associated_user: std::option::Option<std::string::String>,
    /// User wallet account.
    pub user: std::option::Option<std::string::String>,
    /// Decoded instruction amount, when available.
    pub amount_raw: std::option::Option<std::string::String>,
    /// Decoded SOL limit/threshold argument, when available.
    pub sol_limit_raw: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Pump.fun event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum PumpFunDecodedEvent {
    /// `create_v2` token creation.
    CreateV2Token(PumpFunCreateV2TokenDecoded),
    /// Buy trade.
    BuyTrade(PumpFunTradeDecoded),
    /// Sell trade.
    SellTrade(PumpFunTradeDecoded),
}

/// Pump.fun decoder.
#[derive(Debug, Clone, Default)]
pub struct PumpFunDecoder;

impl PumpFunDecoder {
    /// Creates a new decoder.
    pub fn new() -> Self {
        return Self;
    }

    /// Decodes one projected transaction into zero or more Pump.fun events.
    pub fn decode_transaction(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::PumpFunDecodedEvent>, crate::Error> {
        let transaction_id_option = transaction.id;
        let transaction_id = match transaction_id_option {
            Some(transaction_id) => transaction_id,
            None => {
                return Err(crate::Error::InvalidState(format!(
                    "chain transaction '{}' has no internal id",
                    transaction.signature
                )));
            },
        };
        if transaction.err_json.is_some() {
            return Ok(std::vec::Vec::new());
        }
        let transaction_json_result =
            serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
        let transaction_json = match transaction_json_result {
            Ok(transaction_json) => transaction_json,
            Err(error) => {
                return Err(crate::Error::Json(format!(
                    "cannot parse transaction_json for signature '{}': {}",
                    transaction.signature, error
                )));
            },
        };
        let log_messages = extract_log_messages(&transaction_json);
        let has_create_v2_log = log_messages_contain_keyword(&log_messages, "create_v2")
            || log_messages_contain_keyword(&log_messages, "createv2");
        let mut decoded_events = std::vec::Vec::new();
        for instruction in instructions {
            if instruction.parent_instruction_id.is_some() {
                continue;
            }
            let program_id_option = &instruction.program_id;
            let program_id = match program_id_option {
                Some(program_id) => program_id,
                None => continue,
            };
            if program_id.as_str() != crate::PUMP_FUN_PROGRAM_ID {
                continue;
            }
            let instruction_id_option = instruction.id;
            let instruction_id = match instruction_id_option {
                Some(instruction_id) => instruction_id,
                None => continue,
            };
            let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
            let accounts = match accounts_result {
                Ok(accounts) => accounts,
                Err(error) => return Err(error),
            };
            let instruction_data = decode_optional_instruction_data(instruction.data_json.as_ref());
            let is_buy = instruction_data_starts_with(
                instruction_data.as_deref(),
                &PUMP_FUN_BUY_DISCRIMINATOR,
            );
            let is_sell = instruction_data_starts_with(
                instruction_data.as_deref(),
                &PUMP_FUN_SELL_DISCRIMINATOR,
            );
            if is_buy || is_sell {
                if accounts.len() < 7 {
                    continue;
                }
                let amount_raw = extract_u64_argument(instruction_data.as_deref(), 8);
                let sol_limit_raw = extract_u64_argument(instruction_data.as_deref(), 16);
                let mint = extract_account(&accounts, 2);
                let bonding_curve = extract_account(&accounts, 3);
                let associated_bonding_curve = extract_account(&accounts, 4);
                let associated_user = extract_account(&accounts, 5);
                let user = extract_account(&accounts, 6);
                let fee_recipient = extract_account(&accounts, 1);
                let token_program = extract_account(&accounts, 8);
                let creator_vault = extract_account(&accounts, 9);
                let payload_json = serde_json::json!({
                    "decoder": "pump_fun",
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "accounts": accounts,
                    "logMessages": log_messages,
                    "eventKind": if is_buy { "buy" } else { "sell" },
                    "poolAccount": bonding_curve,
                    "tokenAMint": mint,
                    "tokenBMint": crate::WSOL_MINT_ID,
                    "bondingCurve": bonding_curve,
                    "associatedBondingCurve": associated_bonding_curve,
                    "associatedUser": associated_user,
                    "user": user,
                    "feeRecipient": fee_recipient,
                    "tokenProgram": token_program,
                    "creatorVault": creator_vault,
                    "poolBaseTokenAccount": associated_bonding_curve,
                    "poolQuoteNativeAccount": bonding_curve,
                    "amountRaw": amount_raw,
                    "solLimitRaw": sol_limit_raw,
                    "tradeSide": if is_buy { "BuyBase" } else { "SellBase" }
                });
                let event = crate::PumpFunTradeDecoded {
                    transaction_id,
                    instruction_id,
                    signature: transaction.signature.clone(),
                    program_id: program_id.clone(),
                    trade_side: if is_buy {
                        crate::SwapTradeSide::BuyBase
                    } else {
                        crate::SwapTradeSide::SellBase
                    },
                    mint,
                    bonding_curve,
                    associated_bonding_curve,
                    associated_user,
                    user,
                    amount_raw,
                    sol_limit_raw,
                    payload_json,
                };
                if is_buy {
                    decoded_events.push(crate::PumpFunDecodedEvent::BuyTrade(event));
                } else {
                    decoded_events.push(crate::PumpFunDecodedEvent::SellTrade(event));
                }
                continue;
            }
            if !has_create_v2_log {
                continue;
            }
            if accounts.len() < 6 {
                continue;
            }
            let mint = extract_account(&accounts, 0);
            let bonding_curve = extract_account(&accounts, 2);
            let associated_bonding_curve = extract_account(&accounts, 3);
            let creator = extract_account(&accounts, 5);
            let payload_json = serde_json::json!({
                "decoder": "pump_fun",
                "eventKind": "create_v2_token",
                "signature": transaction.signature,
                "instructionId": instruction_id,
                "instructionIndex": instruction.instruction_index,
                "accounts": accounts,
                "logMessages": log_messages,
                "mint": mint,
                "bondingCurve": bonding_curve,
                "associatedBondingCurve": associated_bonding_curve,
                "creator": creator
            });
            decoded_events.push(crate::PumpFunDecodedEvent::CreateV2Token(
                crate::PumpFunCreateV2TokenDecoded {
                    transaction_id,
                    instruction_id,
                    signature: transaction.signature.clone(),
                    program_id: program_id.clone(),
                    mint,
                    bonding_curve,
                    associated_bonding_curve,
                    creator,
                    payload_json,
                },
            ));
        }
        return Ok(decoded_events);
    }
}

fn decode_optional_instruction_data(
    data_json: std::option::Option<&std::string::String>,
) -> std::option::Option<std::vec::Vec<u8>> {
    let data_json = match data_json {
        Some(data_json) => data_json,
        None => return None,
    };
    let parsed_result = serde_json::from_str::<serde_json::Value>(data_json.as_str());
    let encoded = match parsed_result {
        Ok(parsed) => match parsed.as_str() {
            Some(text) => text.to_string(),
            None => data_json.clone(),
        },
        Err(_) => data_json.clone(),
    };
    let decode_result = bs58::decode(encoded.as_str()).into_vec();
    match decode_result {
        Ok(decoded) => return Some(decoded),
        Err(_) => return None,
    }
}

fn instruction_data_starts_with(
    instruction_data: std::option::Option<&[u8]>,
    discriminator: &[u8; 8],
) -> bool {
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return false,
    };
    if instruction_data.len() < discriminator.len() {
        return false;
    }
    return &instruction_data[0..discriminator.len()] == discriminator;
}

fn extract_u64_argument(
    instruction_data: std::option::Option<&[u8]>,
    offset: usize,
) -> std::option::Option<std::string::String> {
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return None,
    };
    let end = offset + 8;
    if instruction_data.len() < end {
        return None;
    }
    let mut bytes = [0u8; 8];
    bytes.copy_from_slice(&instruction_data[offset..end]);
    return Some(u64::from_le_bytes(bytes).to_string());
}

fn extract_log_messages(
    transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
    let mut messages = std::vec::Vec::new();
    let meta_option = transaction_json.get("meta");
    let meta = match meta_option {
        Some(meta) => meta,
        None => return messages,
    };
    let logs_option = meta.get("logMessages");
    let logs = match logs_option {
        Some(logs) => logs,
        None => return messages,
    };
    let logs_array_option = logs.as_array();
    let logs_array = match logs_array_option {
        Some(logs_array) => logs_array,
        None => return messages,
    };
    for value in logs_array {
        let text_option = value.as_str();
        if let Some(text) = text_option {
            messages.push(text.to_string());
        }
    }
    return messages;
}

fn log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
    let keyword_normalized = normalize_log_text(keyword);
    for log_message in log_messages {
        let log_normalized = normalize_log_text(log_message.as_str());
        if log_normalized.contains(keyword_normalized.as_str()) {
            return true;
        }
    }
    return false;
}

fn parse_accounts_json(
    accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
    let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
    let values = match values_result {
        Ok(values) => values,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse instruction accounts_json '{}': {}",
                accounts_json, error
            )));
        },
    };
    let mut accounts = std::vec::Vec::new();
    for value in values {
        let text_option = value.as_str();
        if let Some(text) = text_option {
            accounts.push(text.to_string());
        }
    }
    return Ok(accounts);
}

fn extract_account(
    accounts: &[std::string::String],
    index: usize,
) -> std::option::Option<std::string::String> {
    if index >= accounts.len() {
        return None;
    }
    return Some(accounts[index].clone());
}

fn normalize_log_text(value: &str) -> std::string::String {
    let mut normalized = std::string::String::new();
    for character in value.chars() {
        if character.is_ascii_alphanumeric() {
            normalized.push(character.to_ascii_lowercase());
        }
    }
    return normalized;
}

#[cfg(test)]
mod tests {
    fn make_transaction() -> crate::ChainTransactionDto {
        let mut dto = crate::ChainTransactionDto::new(
            "sig-pump-fun-test-1".to_string(),
            Some(777001),
            Some(1779200001),
            Some("helius_primary_http".to_string()),
            Some("0".to_string()),
            None,
            None,
            serde_json::json!({
                "slot": 777001,
                "meta": {
                    "logMessages": [
                        "Program log: Instruction: CreateV2"
                    ]
                },
                "transaction": {
                    "message": {
                        "instructions": []
                    }
                }
            })
            .to_string(),
        );
        dto.id = Some(91);
        return dto;
    }

    fn make_instruction() -> crate::ChainInstructionDto {
        let mut dto = crate::ChainInstructionDto::new(
            91,
            None,
            0,
            None,
            Some(crate::PUMP_FUN_PROGRAM_ID.to_string()),
            Some("pump".to_string()),
            Some(1),
            serde_json::json!([
                "MintCreate111",
                "MintAuthority111",
                "BondingCurve111",
                "AssociatedBondingCurve111",
                "Global111",
                "Creator111",
                "System111",
                "Token2022Program111",
                "AtaProgram111"
            ])
            .to_string(),
            None,
            None,
            None,
        );
        dto.id = Some(17);
        return dto;
    }

    #[test]
    fn pump_fun_create_v2_is_detected() {
        let decoder = crate::PumpFunDecoder::new();
        let transaction = make_transaction();
        let instructions = vec![make_instruction()];
        let decoded_result = decoder.decode_transaction(&transaction, &instructions);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::PumpFunDecodedEvent::CreateV2Token(event) => {
                assert_eq!(event.transaction_id, 91);
                assert_eq!(event.instruction_id, 17);
                assert_eq!(event.mint, Some("MintCreate111".to_string()));
                assert_eq!(event.bonding_curve, Some("BondingCurve111".to_string()));
                assert_eq!(
                    event.associated_bonding_curve,
                    Some("AssociatedBondingCurve111".to_string())
                );
                assert_eq!(event.creator, Some("Creator111".to_string()));
            },
            crate::PumpFunDecodedEvent::BuyTrade(_) => {
                panic!("unexpected pump_fun buy trade event");
            },
            crate::PumpFunDecodedEvent::SellTrade(_) => {
                panic!("unexpected pump_fun sell trade event");
            },
        }
    }

    #[test]
    fn pump_fun_create_v2_returns_none_without_expected_log() {
        let decoder = crate::PumpFunDecoder::new();
        let mut transaction = make_transaction();
        transaction.transaction_json = serde_json::json!({
            "slot": 777001,
            "meta": {
                "logMessages": [
                    "Program log: Instruction: Buy"
                ]
            },
            "transaction": {
                "message": {
                    "instructions": []
                }
            }
        })
        .to_string();
        let instructions = vec![make_instruction()];
        let decoded_result = decoder.decode_transaction(&transaction, &instructions);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 0);
    }
}
