use log::error;
use primitives::HashMap;
use revm::database::{CacheDB, EmptyDB};
use revm::primitives::{Address, Bytes, FixedBytes, Log, LogData, U256};

use crate::cmd::semantics::Errors;
use std::{
    fs,
    path::{Path, PathBuf},
};

const SKIP_DIRECTORY: [&str; 5] = [
    "externalContracts",
    "externalSource",
    "experimental",
    "multiSource",
    "isoltestTesting",
];
//in the below, we skip files that are irrelevant for now, that is for example tests for unicode
//escapes or convert_uint_to_fixed_bytes_greater_size
//We also skip test for which we have low understanding: multiple initializations
//We also skip test for difficulty as it gets overwritten by prevrandao
// constructor with param inheritance has a complex structure for us to parse it !
// Block hash not settable, it's fetched via block number!
// codebalance : no need to set-up balances of multiple accounts, we know it works!
// multiple_inheritance : we don't support multiple inheritance
// external_call_at_construction_time: unsure what's being tested
// pass_dynamic_arguments_to_the_base_base_with_gap: multiple inheritance
// same for the below down to transient, for which we need to hardcode further balances to some
// addresses
// virtual functions | array in constructor: nasty inheritance
const SKIP_FILE: [&str; 22] = [
    "library_references_preserve.sol",
    "library_delegatecall_guard_view_staticcall.sol",
    "multiline_comments.sol",
    "unicode_string.sol",
    "multiple_initializations.sol",
    "blockhash_basic.sol",
    "codebalance_assembly.sol",
    "codehash_assembly.sol",
    "codehash.sol",
    "constructor_with_params_inheritance_2.sol",
    "single_copy_with_multiple_inheritance.sol",
    "external_call_at_construction_time.sol",
    "pass_dynamic_arguments_to_the_base_base_with_gap.sol",
    "pass_dynamic_arguments_to_the_base.sol",
    "pass_dynamic_arguments_to_the_base_base.sol",
    "virtual_functions.sol",
    "transient_state_address_variable_members.sol",
    "base_base_overload.sol",
    "arrays_in_constructors.sol",
    "bytes_in_constructors_packer.sol",
    "block_coinbase.sol",
    "function_from_base_contract.sol",
];

pub(crate) fn find_test_files(dir: &Path) -> Result<Vec<PathBuf>, Errors> {
    let mut test_files = Vec::new();
    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();
        if path.is_dir() {
            let should_skip = SKIP_DIRECTORY.iter().any(|&dir| {
                path.file_name()
                    .and_then(|name| name.to_str())
                    .map(|name| name.ends_with(dir))
                    .unwrap_or(false)
            });

            if should_skip {
                continue;
            }
            test_files.extend(find_test_files(&path)?);
        } else if let Some(extension) = path.extension() {
            if extension == "sol" {
                let should_skip_file = SKIP_FILE.iter().any(|&file| {
                    path.file_name()
                        .and_then(|name| name.to_str())
                        .map(|name| name == file)
                        .unwrap_or(false)
                });

                if should_skip_file {
                    continue;
                }

                test_files.push(path);
            }
        }
    }
    Ok(test_files)
}

pub(crate) fn extract_compile_via_yul(content: &str) -> Option<bool> {
    let parts: Vec<&str> = content.split("// ====").collect();
    if parts.len() < 2 {
        return None;
    }

    for line in parts[1].lines() {
        if let Some(flag_part) = line.trim().strip_prefix("// compileViaYul:") {
            return Some(flag_part.trim() == "true");
        }
    }
    None
}

/// Extract the `// optimize:` filter from a test file.
///
/// Syntax (in the `// ====` settings block):
///   - `// optimize: false`      → `Some(vec![])` — skip when optimizer is on
///   - `// optimize: []`         → `Some(vec![])` — same as false
///   - `// optimize: 200`        → `Some(vec![200])`
///   - `// optimize: [1, 200]`   → `Some(vec![1, 200])`
///   - (not present)             → `None` — no filtering
///
/// When `Some(runs_list)` is returned:
///   - if `runs_list` is empty → only run when optimizer is OFF
///   - if `runs_list` is non-empty → only run when optimizer is ON and `--optimizer-runs` is in the list
pub(crate) fn extract_optimize_filter(content: &str) -> Option<Vec<usize>> {
    let parts: Vec<&str> = content.split("// ====").collect();
    if parts.len() < 2 {
        return None;
    }

    for line in parts[1].lines() {
        if let Some(value) = line.trim().strip_prefix("// optimize:") {
            let value = value.trim();
            if value == "false" {
                return Some(vec![]);
            }
            // Strip brackets if present: "[1, 200]" -> "1, 200"
            let inner = value.trim_start_matches('[').trim_end_matches(']').trim();
            if inner.is_empty() {
                return Some(vec![]);
            }
            let runs: Vec<usize> = inner
                .split(',')
                .filter_map(|s| s.trim().parse::<usize>().ok())
                .collect();
            return Some(runs);
        }
    }
    None
}

pub(crate) fn needs_eof(content: &str) -> bool {
    content
        .lines()
        .find_map(|l| l.trim().strip_prefix("// bytecodeFormat:"))
        .map_or(false, |fmt| matches!(fmt.trim(), ">=EOFv1"))
}

pub(crate) fn extract_functions_from_source(
    path: &str,
) -> Result<HashMap<String, Vec<String>>, Errors> {
    let content = fs::read_to_string(path)?;

    let mut contract_functions: HashMap<String, Vec<String>> = HashMap::default();
    //parent --> child
    let mut inheritance_map: HashMap<String, Vec<String>> = HashMap::default();
    let mut current_contract = String::new();
    let mut collecting_functions = false;

    for line in content.lines() {
        if let Some(contract_declaration) = line.trim().strip_prefix("contract ") {
            // Extract contract name and parent (if any)
            let mut parts = contract_declaration.split_whitespace();
            let name = parts.next().unwrap_or("").trim_matches('{');

            current_contract = name.to_string();
            contract_functions.insert(current_contract.clone(), Vec::new());
            collecting_functions = true;

            // Check for inheritance
            if let Some(is_index) = contract_declaration.find(" is ") {
                let inherited_part = contract_declaration[is_index + 4..].trim();
                let inherited_contracts: Vec<String> = inherited_part
                    .split('{')
                    .map(|s| s.trim().to_string())
                    .collect();
                inheritance_map.insert(
                    current_contract.clone(),
                    vec![inherited_contracts[0].clone()],
                );
            }
        }

        if collecting_functions && line.contains("function ") {
            if let Some(function_name) = line.split_whitespace().nth(1) {
                let function_signature = function_name.split('(').next().unwrap_or("");
                if let Some(functions) = contract_functions.get_mut(&current_contract) {
                    functions.push(function_signature.to_string());
                }
            }
        } else if line.contains("constructor") {
            if let Some(function_name) = line.split_whitespace().next() {
                let function_signature = function_name.split('(').next().unwrap_or("");
                if let Some(functions) = contract_functions.get_mut(&current_contract) {
                    functions.push(function_signature.to_string());
                }
            }
        } else if line.contains("    fallback") || line.contains("    receive") {
            if let Some(functions) = contract_functions.get_mut(&current_contract) {
                functions.push("()".to_string());
            }
        } else if line.contains(" public ") {
            let tokens: Vec<&str> = line.split_whitespace().collect();

            if let Some(pos) = tokens.iter().position(|&t| t == "public") {
                // Skip "immutable" if it's there
                let mut next_pos = pos + 1;
                if next_pos < tokens.len() && tokens[next_pos] == "immutable" {
                    next_pos += 1;
                }

                // Extract the variable name (ignore `;` and assignments)
                if next_pos < tokens.len() {
                    let variable_name = tokens[next_pos]
                        .trim_end_matches(';') // Remove semicolon
                        .trim_end_matches('='); // Remove assignment

                    if let Some(functions) = contract_functions.get_mut(&current_contract) {
                        functions.push(variable_name.to_string());
                    }
                }
            }
        }
    }

    for (parent, child_contracts) in &inheritance_map {
        for child in child_contracts {
            if let Some(child_functions) = contract_functions.clone().get_mut(child) {
                if let Some(parent_functions) = contract_functions.get_mut(parent) {
                    parent_functions.extend(child_functions.clone());
                }
            }
        }
    }
    Ok(contract_functions)
}

pub(crate) fn count_used_bytes_right(bytes: &[u8]) -> usize {
    let mut start = 0;
    while start < bytes.len() && bytes[start] == 0 {
        start += 1;
    }
    bytes.len() - start
}

pub(crate) fn parse_string_with_escapes(s: &str) -> Result<Vec<u8>, Errors> {
    let mut bytes = Vec::new();
    let mut chars = s.chars().peekable();

    while let Some(c) = chars.next() {
        if c == '\\' {
            match chars.next() {
                Some('x') => {
                    // Hex escape sequence \xNN
                    let hex_digits: String = chars.by_ref().take(2).collect();
                    if hex_digits.len() != 2 {
                        return Err(Errors::InvalidInput);
                    }
                    let byte =
                        u8::from_str_radix(&hex_digits, 16).map_err(|_| Errors::InvalidInput)?;
                    bytes.push(byte);
                }
                Some('u') => {
                    // Unicode escape sequence \uXXXX or \u{XXXXXX}
                    let mut hex = String::new();
                    if chars.peek() == Some(&'{') {
                        chars.next(); // Consume '{'
                        while let Some(&next_char) = chars.peek() {
                            if next_char == '}' {
                                chars.next(); // Consume '}'
                                break;
                            } else {
                                hex.push(chars.next().unwrap());
                            }
                        }
                    } else {
                        // Expect 4 hex digits
                        hex = chars.by_ref().take(4).collect();
                    }
                    let codepoint =
                        u32::from_str_radix(&hex, 16).map_err(|_| Errors::InvalidInput)?;
                    let s = std::char::from_u32(codepoint).ok_or(Errors::InvalidInput)?;
                    let mut buf = [0; 4];
                    let encoded = s.encode_utf8(&mut buf);
                    bytes.extend_from_slice(encoded.as_bytes());
                }
                Some('n') => bytes.push(b'\n'),
                Some('r') => bytes.push(b'\r'),
                Some('t') => bytes.push(b'\t'),
                Some('0') => bytes.push(b'\0'),
                Some('\'') => bytes.push(b'\''),
                Some('"') => bytes.push(b'"'),
                Some('\\') => bytes.push(b'\\'),
                Some(_) => {
                    return Err(Errors::InvalidInput);
                }
                None => {
                    return Err(Errors::InvalidInput);
                }
            }
        } else {
            // Regular character
            bytes.extend(c.to_string().as_bytes());
        }
    }
    Ok(bytes)
}

/// Verifies that the emitted logs (ignoring address and topics) match the expected events.
pub(crate) fn verify_emitted_events(
    expected_events: &[LogData],
    emitted_logs: &[Log],
) -> Result<(), Errors> {
    if expected_events.len() != emitted_logs.len() {
        error!(
            "Expected {} events, but {} were emitted",
            expected_events.len(),
            emitted_logs.len()
        );
        return Err(Errors::LogMismatch);
    }
    for (expected, log) in expected_events.iter().zip(emitted_logs.iter()) {
        if expected.topics() != log.topics() {
            error!(
                "Mismatch in event topics. Expected: {:?}, Got: {:?}",
                expected.topics(),
                log.topics()
            );
            return Err(Errors::LogMismatch);
        }
        if expected.data != log.data.data {
            error!(
                "Mismatch in event data. Expected: {:?}, Got: {:?}",
                expected.data, log.data.data
            );
            return Err(Errors::LogMismatch);
        }
    }
    Ok(())
}

pub(crate) fn verify_expected_balances(
    mut db: CacheDB<EmptyDB>,
    expected: &HashMap<Address, U256>,
    deployed_contract_address: Address,
) -> Result<(), Errors> {
    for (addr, exp_balance) in expected {
        let account = db
            .load_account(if addr == &Address::ZERO {
                deployed_contract_address
            } else {
                *addr
            })
            .unwrap();

        if account.info.balance != *exp_balance {
            error!(
                "Balance mismatch for {}: expected {}, got {}",
                addr, exp_balance, account.info.balance
            );
            return Err(Errors::BalanceMismatch);
        }
    }
    Ok(())
}

pub(crate) fn verify_storage_empty(
    mut db: CacheDB<EmptyDB>,
    contract_address: Address,
    expected_empty: bool,
) -> Result<(), Errors> {
    let storage_entries = &db.load_account(contract_address).unwrap().storage;

    let all_slots_zero = storage_entries
        .iter()
        .all(|(_, value)| value.value == U256::ZERO);

    if all_slots_zero != expected_empty {
        error!(
            "Storage mismatch for {}: expected empty = {}, but storage contains non-zero values",
            contract_address, expected_empty
        );
        return Err(Errors::StorageMismatch);
    }
    Ok(())
}

// Helper function to convert Bytes into FixedBytes<32>
pub(crate) fn bytes_to_fixed(bytes: Bytes) -> FixedBytes<32> {
    let slice = bytes.as_ref();
    let mut fixed = [0u8; 32];
    fixed.copy_from_slice(slice);
    fixed.into()
}
