//! Contract extraction and analysis service

use eth_etl_core::{EthContract, EthTrace};

/// Extract contracts from traces (looking for 'create' trace types)
pub fn extract_contracts_from_traces(traces: &[EthTrace], bytecode_map: &[(String, String)]) -> Vec<EthContract> {
    let mut contracts = Vec::new();

    for trace in traces {
        // Only process successful create traces
        if trace.trace_type != "create" || trace.status != 1 {
            continue;
        }

        // Get the created contract address
        let address = match &trace.to_address {
            Some(addr) => addr.clone(),
            None => continue,
        };

        // Look up bytecode from the map (fetched via eth_getCode)
        let bytecode = bytecode_map
            .iter()
            .find(|(addr, _)| addr.to_lowercase() == address.to_lowercase())
            .map(|(_, code)| code.clone())
            .unwrap_or_default();

        // Extract function sighashes from bytecode
        let function_sighashes = extract_function_sighashes(&bytecode);

        // Detect token types
        let is_erc20 = is_erc20_contract(&function_sighashes);
        let is_erc721 = is_erc721_contract(&function_sighashes);

        contracts.push(EthContract {
            item_type: "contract".to_string(),
            address,
            bytecode: bytecode.clone(),
            function_sighashes,
            is_erc20,
            is_erc721,
            block_number: trace.block_number,
            block_hash: trace.block_hash.clone(),
            block_timestamp: trace.block_timestamp,
        });
    }

    contracts
}

/// Extract 4-byte function selectors from contract bytecode
pub fn extract_function_sighashes(bytecode: &str) -> Vec<String> {
    let mut sighashes = Vec::new();

    // Skip "0x" prefix if present
    let code = bytecode.strip_prefix("0x").unwrap_or(bytecode);

    if code.len() < 8 {
        return sighashes;
    }

    // Look for PUSH4 opcode (0x63) followed by 4 bytes
    // These are typically function selectors in the jump table
    let bytes: Vec<u8> = match hex::decode(code) {
        Ok(b) => b,
        Err(_) => return sighashes,
    };

    let mut i = 0;
    while i < bytes.len().saturating_sub(4) {
        if bytes[i] == 0x63 {
            // PUSH4 opcode
            let selector = format!("0x{:02x}{:02x}{:02x}{:02x}",
                bytes[i + 1], bytes[i + 2], bytes[i + 3], bytes[i + 4]);
            if !sighashes.contains(&selector) {
                sighashes.push(selector);
            }
            i += 5;
        } else {
            i += 1;
        }
    }

    sighashes
}

/// Check if contract implements ERC20 interface
pub fn is_erc20_contract(sighashes: &[String]) -> bool {
    // Must have at least these core functions
    let required = ["0xa9059cbb", "0x70a08231", "0x18160ddd"]; // transfer, balanceOf, totalSupply
    required.iter().all(|req| {
        sighashes.iter().any(|s| s.to_lowercase() == req.to_lowercase())
    })
}

/// Check if contract implements ERC721 interface
pub fn is_erc721_contract(sighashes: &[String]) -> bool {
    // Must have these core ERC721 functions
    let required = ["0x6352211e", "0x42842e0e", "0x70a08231"]; // ownerOf, safeTransferFrom, balanceOf
    required.iter().all(|req| {
        sighashes.iter().any(|s| s.to_lowercase() == req.to_lowercase())
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use eth_etl_core::{ERC20_SELECTORS, ERC721_SELECTORS};

    #[test]
    fn test_is_erc20() {
        let sighashes: Vec<String> = ERC20_SELECTORS.iter().map(|s| s.to_string()).collect();
        assert!(is_erc20_contract(&sighashes));
    }

    #[test]
    fn test_is_erc721() {
        let sighashes: Vec<String> = ERC721_SELECTORS.iter().map(|s| s.to_string()).collect();
        assert!(is_erc721_contract(&sighashes));
    }
}
