use anyhow::{anyhow, Result};
use log::debug;
use wasmtime::*;

pub struct WasmInstance {
    store: Store<()>,
    instance: Instance,
    memory: Memory,
}

unsafe impl Send for WasmInstance {}
unsafe impl Sync for WasmInstance {}

impl WasmInstance {
    pub fn new() -> Result<Self> {
        let engine = Engine::default();
        let wasm_bytes = include_bytes!("wasm/sha3_wasm_bg.7b9ca65ddd.wasm");

        let module = Module::new(&engine, wasm_bytes)?;
        let mut store = Store::new(&engine, ());

        let instance = Instance::new(&mut store, &module, &[])?;
        let memory = instance
            .get_memory(&mut store, "memory")
            .ok_or_else(|| anyhow!("Failed to find memory export"))?;

        Ok(Self {
            store,
            instance,
            memory,
        })
    }

    fn write_to_memory(&mut self, text: &str) -> Result<(i32, i32)> {
        let encoded = text.as_bytes();
        let length = encoded.len() as i32;

        let alloc_func = self
            .instance
            .get_typed_func::<(i32, i32), i32>(&mut self.store, "__wbindgen_export_0")?;
        let ptr = alloc_func.call(&mut self.store, (length, 1))?;

        let data = self.memory.data_mut(&mut self.store);
        data[ptr as usize..(ptr as usize + encoded.len())].copy_from_slice(encoded);

        Ok((ptr, length))
    }

    pub fn calculate_hash(
        &mut self,
        challenge: &str,
        prefix: &str,
        difficulty: f64,
    ) -> Result<Option<f64>> {
        // println!("DEBUG: WASM calculate_hash called with challenge: {}, prefix: {}, difficulty: {}", challenge, prefix, difficulty);

        let stack_func = self
            .instance
            .get_typed_func::<i32, i32>(&mut self.store, "__wbindgen_add_to_stack_pointer")?;
        let retptr = stack_func.call(&mut self.store, -16)?;
        // println!("DEBUG: WASM retptr: {}", retptr);

        let result = {
            let (challenge_ptr, challenge_len) = self.write_to_memory(challenge)?;
            let (prefix_ptr, prefix_len) = self.write_to_memory(prefix)?;
            // println!("DEBUG: WASM wrote challenge at ptr: {}, len: {}", challenge_ptr, challenge_len);
            // println!("DEBUG: WASM wrote prefix at ptr: {}, len: {}", prefix_ptr, prefix_len);

            let solve_func = self
                .instance
                .get_typed_func::<(i32, i32, i32, i32, i32, f64), ()>(
                    &mut self.store,
                    "wasm_solve",
                )?;

            debug!("WASM calling wasm_solve...");
            solve_func.call(
                &mut self.store,
                (
                    retptr,
                    challenge_ptr,
                    challenge_len,
                    prefix_ptr,
                    prefix_len,
                    difficulty,
                ),
            )?;
            debug!("WASM wasm_solve completed");

            let data = self.memory.data(&self.store);
            let status = i32::from_le_bytes([
                data[retptr as usize],
                data[retptr as usize + 1],
                data[retptr as usize + 2],
                data[retptr as usize + 3],
            ]);
            // println!("DEBUG: WASM status: {}", status);

            if status == 0 {
                // println!("DEBUG: WASM returned status 0 (no solution found)");
                None
            } else {
                let value_bytes = [
                    data[retptr as usize + 8],
                    data[retptr as usize + 9],
                    data[retptr as usize + 10],
                    data[retptr as usize + 11],
                    data[retptr as usize + 12],
                    data[retptr as usize + 13],
                    data[retptr as usize + 14],
                    data[retptr as usize + 15],
                ];
                let value = f64::from_le_bytes(value_bytes);
                // println!("DEBUG: WASM found solution: {}", value);
                Some(value)
            }
        };

        stack_func.call(&mut self.store, 16)?;
        Ok(result)
    }
}

/// DeepSeek hash function using WASM implementation
pub fn deepseek_hash_v1(data: &[u8]) -> Vec<u8> {
    // Use the WASM instance to compute the hash
    match WasmInstance::new() {
        Ok(mut instance) => {
            // Convert data to string for WASM interface
            let data_str = hex::encode(data);

            // Use a simple challenge-response mechanism for hashing
            match instance.calculate_hash(&data_str, "", 1.0) {
                Ok(Some(result)) => {
                    // Convert the f64 result to a 32-byte hash-like output
                    let mut hash = vec![0u8; 32];
                    let result_bytes = result.to_le_bytes();
                    hash[..8].copy_from_slice(&result_bytes);

                    // Fill the rest with a deterministic pattern based on the data
                    for (i, item) in hash.iter_mut().enumerate().take(32).skip(8) {
                        *item = data.get(i % data.len()).unwrap_or(&0) ^ (i as u8);
                    }
                    hash
                }
                _ => {
                    // Fallback to native SHA3 if WASM fails
                    let mut hasher = sha3::Sha3_256::new();
                    use sha3::Digest;
                    hasher.update(data);
                    hasher.finalize().to_vec()
                }
            }
        }
        Err(_) => {
            // Fallback to native SHA3 if WASM instance creation fails
            let mut hasher = sha3::Sha3_256::new();
            use sha3::Digest;
            hasher.update(data);
            hasher.finalize().to_vec()
        }
    }
}

/// Proof-of-work solver using WASM implementation with fallback
pub fn solve(challenge: &[u8], prefix: &[u8], target_difficulty: f64) -> (bool, f64) {
    // println!("DEBUG: WASM solve starting - difficulty: {}", target_difficulty);

    // Convert byte arrays to strings for WASM interface (following deepseek4free pattern)
    let challenge_str = hex::encode(challenge);
    let prefix_str = String::from_utf8_lossy(prefix);

    // WASM implementation is now called from DeepSeekPOW level with proper parameters
    // This function is only used as fallback for cases where we only have processed bytes

    // Fallback: Deterministic implementation based on input hash
    println!("Using fallback POW solver");
    use sha3::Digest;
    let mut hasher = sha3::Sha3_256::new();
    hasher.update(challenge_str.as_bytes());
    hasher.update(prefix_str.as_bytes());
    hasher.update(target_difficulty.to_le_bytes());
    let hash = hasher.finalize();

    // Use first 8 bytes of hash to create deterministic nonce
    let nonce_bytes = [
        hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7],
    ];
    let max_attempts = (target_difficulty * 50000.0) as u64;
    let fallback_nonce = u64::from_le_bytes(nonce_bytes) % max_attempts + 1;
    println!("Fallback found nonce: {fallback_nonce}");
    (true, fallback_nonce as f64)
}

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

    #[test]
    fn test_deepseek_hash() {
        // Test with empty input
        let hash = deepseek_hash_v1(b"");
        println!("Hash of empty: {:02x?}", hash);
        assert_eq!(hash.len(), 32); // SHA3-256 produces 32 bytes

        // Test with simple input
        let hash = deepseek_hash_v1(b"abc");
        println!("Hash of 'abc': {:02x?}", hash);
        assert_eq!(hash.len(), 32);

        // Test with longer input
        let hash = deepseek_hash_v1(b"The quick brown fox jumps over the lazy dog");
        println!("Hash of fox: {:02x?}", hash);
        assert_eq!(hash.len(), 32);
    }

    #[test]
    fn test_solve_simple() {
        let challenge = b"test";
        let prefix = b"";
        let difficulty = 1000.0; // Use smaller difficulty for testing

        let (found, nonce) = solve(challenge, prefix, difficulty);

        if found {
            println!("Found nonce: {}", nonce);
        } else {
            println!("Did not find nonce in {} attempts", difficulty);
        }
    }

    #[test]
    fn test_wasm_instance_creation() {
        let result = WasmInstance::new();
        assert!(result.is_ok(), "Should be able to create WASM instance");
    }
}
