use crate::*;
use bitcoin::{Transaction, Txid, TxOut, Block};
use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
use crate::db::utxo::UtxoEntry;
use crate::rocks_db::{Serialize, Deserialize};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TxDBKey {
    txid: Txid,
}
impl Serialize for TxDBKey {
    fn serialize(&self) -> Vec<u8> {
        consensus_encode(&self.txid)
    }
}
impl Deserialize for TxDBKey {
    fn deserialize(buf: &[u8]) -> Self {
        Self {
            txid: consensus_decode(buf)
        }
    }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TxDBValue {
    pub confirmed_height: Option<u32>,
    pub tx: Transaction,
    pub previous_txouts: Vec<TxOut>,
}
impl TxDBValue {
    pub fn deserialize_as_rawtx(buf: &[u8]) -> (Option<u32>, Vec<u8>, Vec<TxOut>) {
        let confirmed_height = bytes_to_i32(&buf[0..4]);
        let confirmed_height = if confirmed_height >= 0 {
            Some(confirmed_height as u32)
        } else {
            None
        };
        let tx_len = bytes_to_u32(&buf[4..8]) as usize;
        let tx = buf[8..tx_len+8].to_vec();
        let mut offset: usize = tx_len + 8;
        let mut previous_txouts = Vec::new();
        while offset < buf.len() {
            let txout_len = bytes_to_u32(&buf[offset..offset+4]) as usize;
            offset += 4;
            let txout = consensus_decode(&buf[offset..txout_len+offset]);
            offset += txout_len;
            previous_txouts.push(txout);
        }
        (confirmed_height, tx, previous_txouts)
    }
}
impl Serialize for TxDBValue {
    fn serialize(&self) -> Vec<u8> {
        let mut ret = Vec::new();
        let confirmed_height = self.confirmed_height.map_or_else(|| -1i32, |confirmed_height| confirmed_height as i32);
        ret.push(confirmed_height.to_le_bytes().to_vec());
        let tx = consensus_encode(&self.tx);
        let tx_len = tx.len() as u32;
        ret.push(tx_len.to_le_bytes().to_vec());
        ret.push(tx);
        for txout in self.previous_txouts.iter() {
            let txout = consensus_encode(txout);
            let txout_len = txout.len() as u32;
            ret.push(txout_len.to_le_bytes().to_vec());
            ret.push(txout);
        }
        ret.concat()
    }
}
impl Deserialize for TxDBValue {
    fn deserialize(buf: &[u8]) -> Self {
        let (confirmed_height, tx, previous_txouts) = Self::deserialize_as_rawtx(buf);
        Self {
            confirmed_height,
            tx: consensus_decode(&tx),
            previous_txouts,
        }
    }
}
#[derive(Debug)]
pub struct TxDB {
    db: RocksDB<TxDBKey, TxDBValue>,
}
impl TxDB {
    pub fn path(coin: &str) -> String {
        format!("{}/{}/tx", data_dir(), coin)
    }
    pub fn new(coin: &str, temporary: bool) -> Self {
        let path = Self::path(coin);
        Self {
            db: RocksDB::new(&path, temporary),
        }
    }
    pub fn put(&self, txid: &Txid, value: &TxDBValue) {
        self.db.put(&TxDBKey { txid: *txid }, value);
    }
    pub fn put_tx(&self, tx: &Transaction, confirmed_height: Option<u32>) -> Result<(TxDBValue, Vec<UtxoEntry>), Txid> {
        let mut previous_txouts = Vec::new();
        let mut previous_utxos = Vec::new();
        for vin in tx.input.iter() {
            if !vin.previous_output.is_null() {
                let previous_txid = vin.previous_output.txid;
                match self.get(&previous_txid) {
                    Some(previous_tx) => {
                        let previous_txout = previous_tx.tx.output[vin.previous_output.vout as usize].clone();
                        previous_utxos.push(UtxoEntry {
                            script_pubkey: previous_txout.script_pubkey.clone(),
                            txid: previous_txid,
                            vout: vin.previous_output.vout,
                            value: previous_txout.value,
                        });
                        previous_txouts.push(previous_txout);
                    },
                    None => return Err(previous_txid),
                }
            }
        }
        let value = TxDBValue {
            confirmed_height,
            tx: (*tx).clone(),
            previous_txouts,
        };
        self.put(&tx.txid(), &value);
        Ok((value, previous_utxos))
    }
    pub fn get(&self, txid: &Txid) -> Option<TxDBValue> {
        self.db.get(&TxDBKey { txid: *txid })
    }
    pub fn get_as_rest(&self, txid: &Txid, config: &Config) -> Option<chainseeker::Transaction> {
        
        let buf = self.db.get_raw(&TxDBKey { txid: *txid });
        
        buf.map_or_else(|| None, |buf| {
            
            let (confirmed_height, rawtx, previous_txouts) = TxDBValue::deserialize_as_rawtx(&buf);
            let tx: Transaction = consensus_decode(&rawtx);
            let mut input_value = 0;
            let mut vin = Vec::new();
            let mut previous_txout_index = 0;
            for input in tx.input.iter() {
                if input.previous_output.is_null() {
                    vin.push(create_vin(input, &None, config));
                } else {
                    input_value += previous_txouts[previous_txout_index].value;
                    vin.push(create_vin(input, &Some(previous_txouts[previous_txout_index].clone()), config));
                    previous_txout_index += 1;
                }
            }
            let output_value: u64 = tx.output.iter().map(|output| output.value).sum();
            let tx = chainseeker::Transaction {
                confirmed_height,
                hex: hex::encode(&rawtx),
                txid: tx.txid().to_string(),
                hash: tx.wtxid().to_string(),
                size: tx.get_size(),
                
                
                vsize: (tx.get_weight() + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR,
                weight: tx.get_weight(),
                version: tx.version,
                locktime: tx.lock_time,
                vin,
                vout: tx.output.iter().enumerate().map(|(n, vout)| create_vout(vout, n, config)).collect(),
                
                fee: (input_value as i64) - (output_value as i64),
            };
            
            Some(tx)
        })
    }
    
    pub fn process_block(&self, confirmed_height: u32, block: &Block, previous_utxos: &[UtxoEntry]) {
        let mut previous_utxo_index = 0;
        for tx in block.txdata.iter() {
            
            let mut previous_txouts = Vec::new();
            for vin in tx.input.iter() {
                if !vin.previous_output.is_null() {
                    let txout = TxOut {
                        value: previous_utxos[previous_utxo_index].value,
                        script_pubkey: previous_utxos[previous_utxo_index].script_pubkey.clone(),
                    };
                    previous_txouts.push(txout);
                    previous_utxo_index += 1;
                }
            }
            let value = TxDBValue {
                confirmed_height: Some(confirmed_height),
                tx: (*tx).clone(),
                previous_txouts,
            };
            self.put(&tx.txid(), &value);
        }
    }
}
#[cfg(test)]
mod tests {
    use std::str::FromStr;
    use crate::db::utxo::UtxoDB;
    use super::*;
    const TXID: &str = "503e4e9824282eb06f1a328484e2b367b5f4f93a405d6e7b97261bafabfb53d5";
    #[test]
    fn key_deserialize() {
        assert_eq!(
            TxDBKey {
                txid: Txid::from_str(TXID).unwrap(),
            },
            TxDBKey::deserialize(&Txid::from_hex(TXID).unwrap()),
        );
    }
    #[test]
    fn put_unconfirmed() {
        let tx = &fixtures::regtest_blocks()[0].txdata[0];
        let tx_db = TxDB::new("test/tx/unconfirmed", true);
        tx_db.put_tx(&tx, None).unwrap();
        assert_eq!(
            tx_db.get(&tx.txid()).unwrap(),
            TxDBValue {
                confirmed_height: None,
                tx: (*tx).clone(),
                previous_txouts: Vec::new(),
            },
        );
    }
    #[test]
    fn put_confirmed() {
        let blocks = fixtures::regtest_blocks();
        let mut utxo_db = UtxoDB::new("test/tx/confirmed", true);
        let tx_db = TxDB::new("test/tx/confirmed", true);
        let mut previous_utxos_vec = Vec::new();
        for (height, block) in blocks.iter().enumerate() {
            let previous_utxos = utxo_db.process_block(&block, true);
            tx_db.process_block(height as u32, &block, &previous_utxos);
            previous_utxos_vec.push(previous_utxos);
        }
        
        assert_eq!(
            tx_db.put_tx(&consensus_decode(&hex::decode("01000000000101d553fbabaf1b26977b6e5d403af9f4b567b3e28484321a6fb02e2824984e3e5000000000171600142b2296c588ec413cebd19c3cbc04ea830ead6e78ffffffff01be1611020000000017a91487e4e5a7ff7bf78b8a8972a49381c8a673917f3e870247304402205f39ccbab38b644acea0776d18cb63ce3e37428cbac06dc23b59c61607aef69102206b8610827e9cb853ea0ba38983662034bd3575cc1ab118fb66d6a98066fa0bed01210304c01563d46e38264283b99bb352b46e69bf132431f102d4bd9a9d8dab075e7f00000000").unwrap()), Some(500_000)).unwrap_err(),
            Txid::from_str(TXID).unwrap(),
        );
        for (height, block) in blocks.iter().enumerate() {
            let mut previous_utxo_index = 0;
            for tx in block.txdata.iter() {
                let mut previous_txout_index = 0;
                let value = tx_db.get(&tx.txid()).unwrap();
                assert_eq!(value.confirmed_height, Some(height as u32));
                assert_eq!(value.tx, *tx);
                for vin in tx.input.iter() {
                    if !vin.previous_output.is_null() {
                        let txout = TxOut {
                            value: previous_utxos_vec[height][previous_utxo_index].value,
                            script_pubkey: previous_utxos_vec[height][previous_utxo_index].script_pubkey.clone(),
                        };
                        assert_eq!(value.previous_txouts[previous_txout_index], txout);
                        previous_utxo_index += 1;
                        previous_txout_index += 1;
                    }
                }
            }
        }
    }
}