use std::collections::{HashMap, VecDeque};

use crate::chaindef::Transaction;
#[cfg(nexa)]
use crate::encode::{compute_outpoint_hash_from_tx, outpoint_hash};
use crate::mempool::MempoolEntry;
use bitcoincash::hash_types::Txid;

#[cfg(all(test, not(nexa)))]
use std::collections::HashSet;

// TTOR sort a list of transactions (old algo; used in tests for comparison)
#[cfg(all(test, not(nexa)))]
pub fn ttor_sorted(txs: Vec<Transaction>) -> Vec<Transaction> {
    let txs = {
        let mut queue: VecDeque<Transaction> = txs.into_iter().collect();

        let mut queue_txids: HashSet<Txid> = queue.iter().map(|tx| tx.txid()).collect();

        let mut txs: Vec<Transaction> = Vec::with_capacity(queue.len());

        while let Some(tx) = queue.pop_front() {
            let mut has_parent = false;

            for i in &tx.input {
                if queue_txids.contains(&i.previous_output.txid) {
                    // depends on parent
                    has_parent = true;
                    break;
                };
            }

            if has_parent {
                queue.push_back(tx);
            } else {
                queue_txids.remove(&tx.txid());
                txs.push(tx);
            }
        }
        txs
    };
    txs
}

/// Alternative implementation using Kahn's algorithm for topological sorting
/// This is more efficient with O(V + E) complexity and handles cycles gracefully
///
/// Note: This implementation may produce different but equally valid topological orderings
/// compared to the original `ttor_sorted` function. Both implementations respect
/// transaction dependencies, but may order independent transactions differently.
pub fn ttor_sorted_kahn(txs: Vec<Transaction>) -> Vec<Transaction> {
    if txs.is_empty() {
        return Vec::new();
    }

    // Build adjacency list and in-degree count
    let mut graph: HashMap<Txid, Vec<Txid>> = HashMap::new();
    let mut in_degree: HashMap<Txid, usize> = HashMap::new();
    let mut tx_map: HashMap<Txid, Transaction> = HashMap::new();

    // Initialize data structures
    for tx in txs {
        let txid = tx.txid();
        tx_map.insert(txid, tx);
        in_degree.insert(txid, 0);
        graph.insert(txid, Vec::new());
    }

    // Build the dependency graph
    // First, create a map of outpointhash -> txid for all outputs in our transaction set
    #[cfg(nexa)]
    let mut outpoint_to_txid: HashMap<crate::chaindef::OutPointHash, Txid> = HashMap::new();
    #[cfg(nexa)]
    for (txid, tx) in &tx_map {
        for (out_idx, _) in tx.output.iter().enumerate() {
            let oph = compute_outpoint_hash_from_tx(tx, out_idx as u32);
            outpoint_to_txid.insert(oph, *txid);
        }
    }

    for (txid, tx) in &tx_map {
        for input in &tx.input {
            #[cfg(bch)]
            let parent_txid = input.previous_output.txid;

            #[cfg(nexa)]
            let parent_txid = {
                let input_oph = outpoint_hash(&input.previous_output);
                match outpoint_to_txid.get(&input_oph) {
                    Some(&parent) => parent,
                    None => continue, // Parent not in our transaction set
                }
            };

            // Only consider dependencies within our transaction set
            if tx_map.contains_key(&parent_txid) {
                // Add edge from parent to current transaction
                graph.entry(parent_txid).or_default().push(*txid);

                // Increment in-degree of current transaction
                *in_degree.entry(*txid).or_default() += 1;
            }
        }
    }

    // Kahn's algorithm: find nodes with no incoming edges
    let mut queue: VecDeque<Txid> = VecDeque::new();
    for (txid, &degree) in &in_degree {
        if degree == 0 {
            queue.push_back(*txid);
        }
    }

    let mut result: Vec<Transaction> = Vec::with_capacity(tx_map.len());

    // Process nodes in topological order
    while let Some(txid) = queue.pop_front() {
        result.push(tx_map.remove(&txid).unwrap());

        // Remove edges from this node and update in-degrees
        if let Some(children) = graph.get(&txid) {
            for &child_txid in children {
                if let Some(degree) = in_degree.get_mut(&child_txid) {
                    *degree -= 1;
                    if *degree == 0 {
                        queue.push_back(child_txid);
                    }
                }
            }
        }
    }

    // Add any remaining transactions (handles cycles and orphaned transactions)
    // This matches the original implementation's behavior
    for (_, tx) in tx_map {
        result.push(tx);
    }

    result
}

/// Sort MempoolEntry by TTOR (Transaction Time Ordering Rule)
/// This ensures that parent transactions are added before their children
pub fn ttor_sorted_mempool_entries(entries: Vec<MempoolEntry>) -> Vec<MempoolEntry> {
    if entries.is_empty() {
        return Vec::new();
    }

    // Create a map from txid to MempoolEntry for efficient lookup
    let mut entry_map: HashMap<Txid, MempoolEntry> =
        entries.into_iter().map(|e| (e.tx().txid(), e)).collect();

    // Extract transactions for sorting (borrow from entries in map)
    let txs: Vec<Transaction> = entry_map.values().map(|e| e.tx().clone()).collect();
    let sorted_txs = ttor_sorted_kahn(txs);

    // Reorder entries according to sorted transaction order
    sorted_txs
        .into_iter()
        .filter_map(|tx| entry_map.remove(&tx.txid()))
        .collect()
}

#[cfg(all(test, not(nexa)))]
mod tests {
    use super::*;
    use bitcoin_hashes::{sha256d, Hash};
    use bitcoincash::{OutPoint, PackedLockTime, Script, Sequence, TxIn, TxOut, Witness};

    fn create_mock_transaction(txid: [u8; 32], inputs: Vec<[u8; 32]>) -> Transaction {
        let tx_inputs: Vec<TxIn> = inputs
            .into_iter()
            .map(|input_txid| TxIn {
                previous_output: OutPoint {
                    txid: Txid::from_hash(sha256d::Hash::from_inner(input_txid)),
                    vout: 0,
                },
                script_sig: Script::new(),
                sequence: Sequence(0),
                witness: Witness::new(),
            })
            .collect();

        // Create a transaction with a unique txid by using the provided txid parameter
        // We need to create a transaction that has the desired txid when txid() is called
        let mut tx = Transaction {
            version: 1,
            lock_time: PackedLockTime(0),
            input: tx_inputs,
            output: vec![TxOut {
                value: 1000,
                script_pubkey: Script::new(),
                token: None,
            }],
        };

        // For testing purposes, we'll create transactions with different outputs to ensure different txids
        // The txid is calculated from the transaction content, so we need to make them different
        tx.output[0].value = txid[0] as u64 * 1000; // Use first byte of txid to make value unique

        tx
    }

    /// Create a mock transaction with proper dependency relationships
    /// This function creates transactions where input txids actually match the txids of referenced transactions
    fn create_mock_transaction_with_deps(
        base_txid: [u8; 32],
        parent_txs: &[Transaction],
    ) -> Transaction {
        let tx_inputs: Vec<TxIn> = parent_txs
            .iter()
            .map(|parent_tx| TxIn {
                previous_output: OutPoint {
                    txid: parent_tx.txid(),
                    vout: 0,
                },
                script_sig: Script::new(),
                sequence: Sequence(0),
                witness: Witness::new(),
            })
            .collect();

        let mut tx = Transaction {
            version: 1,
            lock_time: PackedLockTime(0),
            input: tx_inputs,
            output: vec![TxOut {
                value: 1000,
                script_pubkey: Script::new(),
                token: None,
            }],
        };

        // Make the transaction unique by using the base_txid
        tx.output[0].value = base_txid[0] as u64 * 1000;

        tx
    }

    #[test]
    fn test_simple_chain() {
        // Create a simple chain: A -> B -> C
        let tx_a = create_mock_transaction([1; 32], vec![]);
        let tx_b = create_mock_transaction_with_deps([2; 32], &[tx_a.clone()]);
        let tx_c = create_mock_transaction_with_deps([3; 32], &[tx_b.clone()]);

        let txs = vec![tx_c.clone(), tx_a.clone(), tx_b.clone()];

        let result_original = ttor_sorted(txs.clone());
        let result_kahn = ttor_sorted_kahn(txs);

        // Both should produce the same number of transactions
        assert_eq!(result_original.len(), 3);
        assert_eq!(result_kahn.len(), 3);

        // Both should contain all transactions
        let original_txids: HashSet<Txid> = result_original.iter().map(|tx| tx.txid()).collect();
        let kahn_txids: HashSet<Txid> = result_kahn.iter().map(|tx| tx.txid()).collect();
        assert_eq!(original_txids, kahn_txids);

        // Verify dependencies are respected in both results
        verify_dependencies_respected(&result_original);
        verify_dependencies_respected(&result_kahn);
    }

    #[test]
    fn test_diamond_dependency() {
        // Create a diamond dependency: A -> B, A -> C, B -> D, C -> D
        let tx_a = create_mock_transaction([1; 32], vec![]);
        let tx_b = create_mock_transaction_with_deps([2; 32], &[tx_a.clone()]);
        let tx_c = create_mock_transaction_with_deps([3; 32], &[tx_a.clone()]);
        let tx_d = create_mock_transaction_with_deps([4; 32], &[tx_b.clone(), tx_c.clone()]);

        let txs = vec![tx_d.clone(), tx_c.clone(), tx_b.clone(), tx_a.clone()];

        let result_original = ttor_sorted(txs.clone());
        let result_kahn = ttor_sorted_kahn(txs);

        // Both should produce the same number of transactions
        assert_eq!(result_original.len(), 4);
        assert_eq!(result_kahn.len(), 4);

        // Both should contain all transactions
        let original_txids: HashSet<Txid> = result_original.iter().map(|tx| tx.txid()).collect();
        let kahn_txids: HashSet<Txid> = result_kahn.iter().map(|tx| tx.txid()).collect();
        assert_eq!(original_txids, kahn_txids);

        // Verify dependencies are respected in both results
        verify_dependencies_respected(&result_original);
        verify_dependencies_respected(&result_kahn);
    }

    #[test]
    fn test_independent_transactions() {
        // Create independent transactions (no dependencies)
        let tx_a = create_mock_transaction([1; 32], vec![]);
        let tx_b = create_mock_transaction([2; 32], vec![]);
        let tx_c = create_mock_transaction([3; 32], vec![]);

        let txs = vec![tx_c.clone(), tx_a.clone(), tx_b.clone()];

        let result_original = ttor_sorted(txs.clone());
        let result_kahn = ttor_sorted_kahn(txs);

        // Both should produce the same number of transactions
        assert_eq!(result_original.len(), 3);
        assert_eq!(result_kahn.len(), 3);

        // Both should contain all transactions
        let original_txids: HashSet<Txid> = result_original.iter().map(|tx| tx.txid()).collect();
        let kahn_txids: HashSet<Txid> = result_kahn.iter().map(|tx| tx.txid()).collect();
        assert_eq!(original_txids, kahn_txids);

        // For independent transactions, both should produce valid orderings
        verify_dependencies_respected(&result_original);
        verify_dependencies_respected(&result_kahn);
    }

    #[test]
    fn test_empty_input() {
        let txs: Vec<Transaction> = vec![];

        let result_original = ttor_sorted(txs.clone());
        let result_kahn = ttor_sorted_kahn(txs);

        assert_eq!(result_original.len(), 0);
        assert_eq!(result_kahn.len(), 0);
    }

    #[test]
    fn test_single_transaction() {
        let tx = create_mock_transaction([1; 32], vec![]);
        let txs = vec![tx.clone()];

        let result_original = ttor_sorted(txs.clone());
        let result_kahn = ttor_sorted_kahn(txs);

        assert_eq!(result_original.len(), 1);
        assert_eq!(result_kahn.len(), 1);
        assert_eq!(result_original[0].txid(), tx.txid());
        assert_eq!(result_kahn[0].txid(), tx.txid());
    }

    #[test]
    fn test_complex_dependency() {
        // Create a more complex dependency graph
        // A -> B -> D
        // A -> C -> D
        // E -> F -> G
        // H (independent)
        let tx_a = create_mock_transaction([1; 32], vec![]);
        let tx_b = create_mock_transaction_with_deps([2; 32], &[tx_a.clone()]);
        let tx_c = create_mock_transaction_with_deps([3; 32], &[tx_a.clone()]);
        let tx_d = create_mock_transaction_with_deps([4; 32], &[tx_b.clone(), tx_c.clone()]);
        let tx_e = create_mock_transaction([5; 32], vec![]);
        let tx_f = create_mock_transaction_with_deps([6; 32], &[tx_e.clone()]);
        let tx_g = create_mock_transaction_with_deps([7; 32], &[tx_f.clone()]);
        let tx_h = create_mock_transaction([8; 32], vec![]);

        let txs = vec![
            tx_g.clone(),
            tx_f.clone(),
            tx_e.clone(),
            tx_d.clone(),
            tx_c.clone(),
            tx_b.clone(),
            tx_a.clone(),
            tx_h.clone(),
        ];

        let result_original = ttor_sorted(txs.clone());
        let result_kahn = ttor_sorted_kahn(txs);

        // Both should produce the same number of transactions
        assert_eq!(result_original.len(), 8);
        assert_eq!(result_kahn.len(), 8);

        // Both should contain all transactions
        let original_txids: HashSet<Txid> = result_original.iter().map(|tx| tx.txid()).collect();
        let kahn_txids: HashSet<Txid> = result_kahn.iter().map(|tx| tx.txid()).collect();
        assert_eq!(original_txids, kahn_txids);

        // Verify dependencies are respected in both results
        verify_dependencies_respected(&result_original);
        verify_dependencies_respected(&result_kahn);
    }

    /// Helper function to verify that dependencies are respected in a transaction ordering
    fn verify_dependencies_respected(txs: &[Transaction]) {
        let tx_positions: HashMap<Txid, usize> = txs
            .iter()
            .enumerate()
            .map(|(pos, tx)| (tx.txid(), pos))
            .collect();

        for (pos, tx) in txs.iter().enumerate() {
            for input in &tx.input {
                if let Some(&parent_pos) = tx_positions.get(&input.previous_output.txid) {
                    // Parent transaction should come before this transaction
                    assert!(
                        parent_pos < pos,
                        "Dependency violation: transaction {} (pos {}) depends on {} (pos {})",
                        tx.txid(),
                        pos,
                        input.previous_output.txid,
                        parent_pos
                    );
                }
            }
        }
    }
}
