use crate::chaindef::OutPointHash;
use crate::store::{db_decode, db_encode, Row};
use bitcoincash::hashes::Hash;
use bitcoincash::Txid;

use super::DBRow;
use anyhow::{Context, Result};

/*
 * Index of utxos on the blockchain.
 */

const OUTPUTINDEX_CF: &str = "output";

#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct OutputIndexKey {
    pub hash: [u8; 32],
}

#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct OutputIndexRow {
    key: OutputIndexKey,
    txid: [u8; 32],
    index: u32,
    value: u64,
    height: u32,
}

fn deserialize_value(value: &[u8]) -> Result<([u8; 32], u32, u64, u32)> {
    db_decode(value).context("failed to parse OutputIndexRow value")
}

impl OutputIndexRow {
    pub fn new(
        txid: &Txid,
        outpoint_hash: &OutPointHash,
        output_value: u64,
        output_index: u32,

        // redundantly store height for faster filtering
        height: u32,
    ) -> OutputIndexRow {
        OutputIndexRow {
            key: OutputIndexKey {
                hash: outpoint_hash.into_inner(),
            },
            txid: txid.into_inner(),
            index: output_index,
            value: output_value,
            height,
        }
    }

    pub fn filter_by_outpointhash(hash: &OutPointHash) -> Vec<u8> {
        db_encode(&OutputIndexKey {
            hash: hash.into_inner(),
        })
        .unwrap()
    }

    pub fn hash(&self) -> OutPointHash {
        OutPointHash::from_inner(self.key.hash)
    }

    pub fn take_hash(self) -> OutPointHash {
        OutPointHash::from_inner(self.key.hash)
    }

    pub fn txid(&self) -> Txid {
        Txid::from_inner(self.txid)
    }

    /// Get a reference to the inner txid bytes
    pub fn txid_inner(&self) -> &[u8; 32] {
        &self.txid
    }

    pub fn index(&self) -> u32 {
        self.index
    }

    pub fn value(&self) -> u64 {
        self.value
    }

    pub(crate) fn txid_from_value(value: &[u8]) -> Result<Txid> {
        let (txid, _index, _value, _height): ([u8; 32], u32, u64, u32) = deserialize_value(value)?;
        Ok(Txid::from_inner(txid))
    }

    #[cfg(nexa)]
    pub(crate) fn txid_and_height_from_value(value: &[u8]) -> Result<(Txid, u32)> {
        let (txid, _index, _value, height): ([u8; 32], u32, u64, u32) = deserialize_value(value)?;
        Ok((Txid::from_inner(txid), height))
    }

    pub fn height(&self) -> u32 {
        self.height
    }
}

impl DBRow for OutputIndexRow {
    const CF: &'static str = OUTPUTINDEX_CF;
    fn to_row(&self) -> Row {
        Row {
            key: db_encode(&self.key).unwrap().into_boxed_slice(),
            value: db_encode(&(self.txid, self.index, self.value, self.height))
                .unwrap()
                .into_boxed_slice(),
        }
    }

    fn from_row(row: &Row) -> OutputIndexRow {
        let (txid, index, value, height) =
            deserialize_value(&row.value).expect("failed to parse OutputIndexRow");
        OutputIndexRow {
            key: db_decode(&row.key).expect("failed to parse OutputIndexKey"),
            txid,
            index,
            value,
            height,
        }
    }
}
