use std::cell::RefCell;
use std::cmp::{max, min};
use std::collections::{HashMap, HashSet};
use std::fs;
use std::ops::Deref;
use std::path::Path;

use chrono::Utc;
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use sqlite::{Connection, State, Statement};
use lazy_static::lazy_static;

use crate::blockchain::hash_utils::*;
use crate::blockchain::transaction::{DomainData, DomainState};
use crate::blockchain::types::BlockQuality::*;
use crate::blockchain::types::MineResult::*;
use crate::blockchain::types::{BlockQuality, MineResult, Options, ZoneData};
use crate::commons::constants::*;
use crate::keystore::check_public_key_strength;
use crate::settings::Settings;
use crate::{check_domain, get_domain_zone, is_yggdrasil_record, Block, Bytes, Keystore, Transaction, from_hex};
use rand::prelude::IteratorRandom;

const TEMP_DB_NAME: &str = ":memory:";
const SQL_CREATE_TABLES: &str = include_str!("data/create_db.sql");
const ZONES_TXT: &str = include_str!("data/zones.txt");
const SQL_ADD_BLOCK: &str = "INSERT INTO blocks (id, timestamp, version, difficulty, random, nonce, 'transaction',\
                          prev_block_hash, hash, pub_key, signature) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
const SQL_GET_LAST_BLOCK: &str = "SELECT * FROM blocks ORDER BY id DESC LIMIT 1;";
const SQL_TRUNCATE_BLOCKS: &str = "DELETE FROM blocks WHERE id >= ?;";
const SQL_TRUNCATE_DOMAINS: &str = "DELETE FROM domains WHERE id >= ?;";

const SQL_ADD_DOMAIN: &str = "INSERT INTO domains (id, timestamp, identity, confirmation, data, signing, encryption) VALUES (?, ?, ?, ?, ?, ?, ?)";
const SQL_GET_BLOCK_BY_ID: &str = "SELECT * FROM blocks WHERE id=? LIMIT 1;";
const SQL_GET_LAST_FULL_BLOCK: &str = "SELECT * FROM blocks WHERE id < ? AND `transaction`<>'' ORDER BY id DESC LIMIT 1;";
const SQL_GET_LAST_FULL_BLOCK_FOR_KEY: &str = "SELECT * FROM blocks WHERE id < ? AND `transaction`<>'' AND pub_key = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_DOMAIN_OWNER_BY_ID: &str = "SELECT signing, timestamp FROM domains WHERE id < ? AND identity = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_DOMAIN_BY_ID: &str = "SELECT * FROM domains WHERE identity = ? AND id < ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_DOMAINS_BY_KEY: &str = "SELECT timestamp, identity, data, signing FROM domains WHERE signing = ? ORDER BY id;";
const SQL_GET_DOMAINS_COUNT: &str = "SELECT count(DISTINCT identity) FROM domains;";
const SQL_GET_USERS_COUNT: &str = "SELECT count(DISTINCT pub_key) FROM blocks;";
const SQL_GET_USER_BLOCK_COUNT: &str = "SELECT count(pub_key) FROM blocks WHERE pub_key = ? AND id < ?";
const SQL_GET_DOMAIN_UPDATE_TIME: &str = "SELECT domains.timestamp FROM blocks JOIN domains ON blocks.id = domains.id WHERE difficulty >= 23 AND identity = ? ORDER BY domains.id DESC LIMIT 1;";

const SQL_GET_OPTIONS: &str = "SELECT * FROM options;";

lazy_static! {
    static ref WRONG_HASHES: Vec<Bytes> = vec![
        Bytes::from_bytes(&from_hex("5B2D63CD8BD854B23A34A49AD850BF520BDD8D4514F9B20A3DF01430A59F0000").unwrap()),
        Bytes::from_bytes(&from_hex("4448E0582878FCB982C0DDAFEB441A03A30FB62FA6ECD1EA4D51C29A30980000").unwrap())
    ];
}

/// Max possible block index
const MAX: u64 = i64::MAX as u64;

pub struct Chain {
    origin: Bytes,
    last_block: Option<Block>,
    last_full_block: Option<Block>,
    max_height: u64,
    db: Connection,
    zones: Vec<ZoneData>,
    signers: RefCell<SignersCache>
}

impl Chain {
    pub fn new(settings: &Settings, db_name: &str) -> Self {
        let origin = settings.get_origin();

        let db = sqlite::open(db_name).expect("Unable to open blockchain DB");
        let zones = Self::load_zones();
        let mut chain = Chain { origin, last_block: None, last_full_block: None, max_height: 0, db, zones, signers: SignersCache::new() };
        chain.init_db();
        chain
    }

    /// Reads options from DB or initializes and writes them to DB if not found
    fn init_db(&mut self) {
        let options = self.get_options();
        if !self.origin.is_zero() && !options.origin.is_empty() && self.origin.to_string() != options.origin {
            self.clear_db();
        }
        #[allow(clippy::absurd_extreme_comparisons)]
        if options.version < DB_VERSION {
            self.migrate_db(options.version, DB_VERSION);
        }

        // Trying to get last block from DB to check its version
        // If some block loaded we check its version and determine if we need some migration
        if let Some(block) = self.load_last_block() {
            // Cache some info
            self.last_block = Some(block.clone());
            if block.transaction.is_some() {
                self.last_full_block = Some(block);
            } else {
                self.last_full_block = self.get_last_full_block(MAX, None);
            }
        }
    }

    pub fn check_chain(&mut self, count: u64) {
        let height = self.get_height();
        let start = if height > count {
            info!("Checking last {} blocks...", count);
            height - count + 1
        } else {
            info!("Local blockchain height is {}, starting full blockchain check...", height);
            1
        };
        let mut last_block: Option<Block> = None;
        let mut last_full_block: Option<Block> = None;
        if start > 1 {
            last_block = self.get_block(start - 1);
            if let Some(last) = &last_block {
                last_full_block = match &last.transaction {
                    None => self.get_last_full_block(last.index, None),
                    Some(_) => Some(last.clone())
                };
            }
        }

        for id in start..=height {
            debug!("Checking block {}", id);
            let block = self.get_block(id);
            match block {
                None => {
                    panic!("Blockchain is corrupted! Please, delete 'blockchain.db' and restart.");
                }
                Some(block) => {
                    if block.index == 1 {
                        if block.hash != self.origin {
                            panic!("Loaded DB is not of origin {:?}! Please, delete 'blockchain.db' and restart.", &self.origin);
                        }
                        debug!("Block {} with hash {:?} is good!", block.index, &block.hash);
                        last_block = Some(block);
                        continue;
                    }

                    if WRONG_HASHES.contains(&block.hash)  {
                        error!("Block {} has bad hash:\n{:?}", block.index, &block);
                        info!("Truncating database from block {}...", block.index);
                        if let Err(e) = self.truncate_db_from_block(block.index) {
                            error!("{}", e);
                            panic!("Error truncating database! Please, delete 'blockchain.db' and restart.");
                        }
                        break;
                    }

                    //let last = self.last_block.clone().unwrap();
                    if self.check_block(&block, &last_block, &last_full_block) != Good {
                        error!("Block {} is bad:\n{:?}", block.index, &block);
                        info!("Truncating database from block {}...", block.index);
                        match self.truncate_db_from_block(block.index) {
                            Ok(_) => {}
                            Err(e) => {
                                error!("{}", e);
                                panic!("Error truncating database! Please, delete 'blockchain.db' and restart.");
                            }
                        }
                        break;
                    }
                    debug!("Block {} with hash {:?} is good!", block.index, &block.hash);
                    if block.transaction.is_some() {
                        self.last_full_block = Some(block.clone());
                    }
                    if block.transaction.is_some() {
                        last_full_block = Some(block.clone());
                    }
                    last_block = Some(block);
                }
            }
        }
        self.last_block = self.load_last_block();
        self.last_full_block = self.get_last_full_block(MAX, None);
        debug!("Last block after chain check: {:?}", &self.last_block);
    }

    fn truncate_db_from_block(&mut self, index: u64) -> sqlite::Result<State> {
        let mut statement = self.db.prepare(SQL_TRUNCATE_BLOCKS)?;
        statement.bind((1, index as i64))?;
        statement.next()?;

        let mut statement = self.db.prepare(SQL_TRUNCATE_DOMAINS)?;
        statement.bind((1, index as i64))?;
        statement.next()
    }

    fn load_last_block(&mut self) -> Option<Block> {
        match self.db.prepare(SQL_GET_LAST_BLOCK) {
            Ok(mut statement) => {
                let mut result = None;
                while statement.next().unwrap() == State::Row {
                    match Self::get_block_from_statement(&mut statement) {
                        None => {
                            error!("Something wrong with block in DB!");
                            panic!();
                        }
                        Some(block) => {
                            debug!("Loaded last block: {:?}", &block);
                            result = Some(block);
                            break;
                        }
                    }
                }
                result
            }
            Err(e) => {
                info!("No blockchain database found. Creating new. {}", e);
                self.db.execute(SQL_CREATE_TABLES).expect("Error creating DB tables");
                None
            }
        }
    }

    fn migrate_db(&mut self, from: u32, to: u32) {
        debug!("Migrating DB from {} to {}", from, to);
    }

    fn clear_db(&mut self) {
        warn!("Clearing DB");
        // We cannot close DB connection and recreate file,
        // therefore we switch our db to temporary file, delete main DB and switch back.
        // I know that this is a crutch, but this way I don't need to use Option<db> :)
        self.db = sqlite::open(TEMP_DB_NAME).expect("Unable to open temporary blockchain DB");
        let file = Path::new(DB_NAME);
        if fs::remove_file(&file).is_err() {
            panic!("Unable to remove database!");
        }
        self.db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB");
        let file = Path::new(TEMP_DB_NAME);
        let _ = fs::remove_file(&file).is_err();
    }

    fn get_options(&self) -> Options {
        let mut options = Options::empty();
        if let Ok(mut statement) = self.db.prepare(SQL_GET_OPTIONS) {
            while let State::Row = statement.next().unwrap() {
                let name: String = statement.read(0).unwrap();
                let value: String = statement.read(1).unwrap();
                match name.as_ref() {
                    "origin" => options.origin = value,
                    "version" => options.version = value.parse().unwrap(),
                    _ => {}
                }
            }
        }
        options
    }

    pub fn add_block(&mut self, block: Block) {
        debug!("Adding block:\n{:?}", &block);
        let index = block.index;
        let timestamp = block.timestamp;
        let owner = block.pub_key.clone();
        let transaction = block.transaction.clone();
        match self.add_block_to_table(block.clone()) {
            Ok(_) => {
                self.last_block = Some(block.clone());
                if let Some(mut transaction) = transaction {
                    self.last_full_block = Some(block);
                    if transaction.signing.is_empty() {
                        transaction.signing = owner;
                    }
                    self.add_transaction_to_table(index, timestamp, &transaction).expect("Error adding transaction");
                }
            }
            Err(e) => {
                warn!("Error adding block to table ({}): {:?}", e, &block);
            }
        }
    }

    pub fn replace_block(&mut self, block: Block) -> sqlite::Result<()> {
        info!("Replacing block {} with:\n{:?}", block.index, &block);
        self.signers.borrow_mut().clear();
        self.truncate_db_from_block(block.index)?;
        self.add_block(block);
        Ok(())
    }

    pub fn get_sign_block(&self, keys: &[Keystore]) -> Option<(Block, Keystore)> {
        if self.get_height() < BLOCK_SIGNERS_START {
            trace!("Too early to start block signings");
            return None;
        }
        if keys.is_empty() {
            trace!("We can't sign blocks without keys");
            return None;
        }
        if self.get_height() < self.get_max_height() {
            trace!("No signing while syncing");
            return None;
        }

        let block = match self.last_full_block {
            None => {
                return None;
            }
            Some(ref block) => block.clone()
        };
        // TODO maybe make some config option to mine signing blocks above?
        let sign_count = self.get_height() - block.index;
        if sign_count >= BLOCK_SIGNERS_MIN {
            trace!("Block {} has enough signing blocks", block.index);
            return None;
        }
        if let Some(block) = &self.last_block {
            if block.timestamp + 60 > Utc::now().timestamp() {
                info!("Waiting for other blocks before signing.");
                return None;
            }
        }
        let (last_hash, last_index) = match &self.last_block {
            Some(block) => (block.hash.clone(), block.index),
            None => { return None; }
        };

        let signers: HashSet<Bytes> = self.get_block_signers(&block).into_iter().collect();
        let mut rng = rand::thread_rng();
        let keystore = keys
            .iter()
            .filter(|keystore| signers.contains(&keystore.get_public()))
            .filter(|keystore| {
                for index in block.index..=self.get_height() {
                    let b = self.get_block(index).unwrap();
                    if b.pub_key == keystore.get_public() {
                        debug!("We already mined signing block for block {} by {:?}", block.index, &b.pub_key);
                        return false;
                    }
                }
                true
            })
            .choose(&mut rng);
        if let Some(keystore) = keystore {
            info!("We have an honor to mine signing block!");
            let mut block = Block::new(None, Bytes::default(), last_hash, SIGNER_DIFFICULTY);
            block.index = last_index + 1;
            return Some((block, keystore.clone()));
        }
        if !signers.is_empty() {
            info!("Signing block must be mined by other nodes");
        }
        None
    }

    pub fn update_sign_block_for_mining(&self, mut block: Block) -> Option<Block> {
        if let Some(full_block) = &self.last_full_block {
            let sign_count = self.get_height() - full_block.index;
            if sign_count >= BLOCK_SIGNERS_MIN {
                return None;
            }
            if let Some(last) = &self.last_block {
                block.index = last.index + 1;
                block.prev_block_hash = last.hash.clone();
                return Some(block);
            }
        }
        None
    }

    pub fn is_waiting_signers(&self) -> bool {
        if self.get_height() < BLOCK_SIGNERS_START {
            return false;
        }
        if let Some(full_block) = &self.last_full_block {
            let sign_count = self.get_height() - full_block.index;
            if sign_count < BLOCK_SIGNERS_MIN {
                return true;
            }
        }

        false
    }

    /// Adds block to blocks table
    fn add_block_to_table(&mut self, block: Block) -> sqlite::Result<State> {
        let mut statement = self.db.prepare(SQL_ADD_BLOCK)?;
        statement.bind((1, block.index as i64))?;
        statement.bind((2, block.timestamp as i64))?;
        statement.bind((3, block.version as i64))?;
        statement.bind((4, block.difficulty as i64))?;
        statement.bind((5, block.random as i64))?;
        statement.bind((6, block.nonce as i64))?;
        match &block.transaction {
            None => {
                statement.bind((7, ""))?;
            }
            Some(transaction) => {
                statement.bind((7, transaction.to_string().as_str()))?;
            }
        }
        statement.bind((8, block.prev_block_hash.as_slice()))?;
        statement.bind((9, block.hash.as_slice()))?;
        statement.bind((10, block.pub_key.as_slice()))?;
        statement.bind((11, block.signature.as_slice()))?;
        statement.next()
    }

    /// Adds transaction to transactions table
    fn add_transaction_to_table(&mut self, index: u64, timestamp: i64, t: &Transaction) -> sqlite::Result<State> {
        let sql = match t.class.as_ref() {
            CLASS_DOMAIN => SQL_ADD_DOMAIN,
            CLASS_ORIGIN => return Ok(State::Done),
            _ => return Err(sqlite::Error { code: None, message: None })
        };

        let mut statement = self.db.prepare(sql)?;
        statement.bind((1, index as i64))?;
        statement.bind((2, timestamp))?;
        statement.bind((3, t.identity.as_slice()))?;
        statement.bind((4, t.confirmation.as_slice()))?;
        statement.bind((5, t.data.as_ref() as &str))?;
        statement.bind((6, t.signing.as_slice()))?;
        statement.bind((7, t.encryption.as_slice()))?;
        statement.next()
    }

    pub fn get_block(&self, index: u64) -> Option<Block> {
        match self.db.prepare(SQL_GET_BLOCK_BY_ID) {
            Ok(mut statement) => {
                statement.bind((1, index as i64)).expect("Error in bind");
                if statement.next().unwrap() == State::Row {
                    return match Self::get_block_from_statement(&mut statement) {
                        None => {
                            error!("Something wrong with block in DB!");
                            None
                        }
                        Some(block) => {
                            //trace!("Loaded block: {:?}", &block);
                            Some(block)
                        }
                    };
                }
                None
            }
            Err(_) => {
                warn!("Can't find requested block {}", index);
                None
            }
        }
    }

    /// Gets last block that has a Transaction within
    pub fn get_last_full_block(&self, before: u64, pub_key: Option<&[u8]>) -> Option<Block> {
        if let Some(block) = &self.last_full_block {
            if block.index < before {
                match pub_key {
                    None => {
                        return Some(block.clone());
                    }
                    Some(key) => {
                        if block.pub_key.deref().eq(key) {
                            return Some(block.clone());
                        }
                    }
                }
            }
        }

        let mut statement = match pub_key {
            None => {
                let mut statement = self.db.prepare(SQL_GET_LAST_FULL_BLOCK).expect("Unable to prepare");
                statement.bind((1, before as i64)).expect("Unable to bind");
                statement
            }
            Some(pub_key) => {
                let mut statement = self.db.prepare(SQL_GET_LAST_FULL_BLOCK_FOR_KEY).expect("Unable to prepare");
                statement.bind((1, before as i64)).expect("Unable to bind");
                statement.bind((2, pub_key)).expect("Unable to bind");
                statement
            }
        };
        if statement.next().unwrap() == State::Row {
            return match Self::get_block_from_statement(&mut statement) {
                None => {
                    error!("Something wrong with block in DB!");
                    None
                }
                Some(block) => {
                    //trace!("Got last full block: {:?}", &block);
                    Some(block)
                }
            };
        }
        None
    }

    pub fn can_mine_domain(&self, height: u64, domain: &str, pub_key: &Bytes) -> MineResult {
        let name = domain.to_lowercase();
        if !check_domain(&name, true) {
            return WrongName;
        }
        let zone = get_domain_zone(&name);
        if !self.is_available_zone(&zone) {
            return WrongZone;
        }

        let (transaction, state) = self.get_domain_transaction_and_state(&name);
        if let Some(transaction) = transaction {
            let owner = transaction.signing.eq(pub_key);
            match state {
                DomainState::NotFound => {}
                DomainState::Alive { .. } => if !owner {
                    return NotOwned;
                },
                DomainState::Expired { .. } => if !owner {
                    return NotOwned;
                },
                DomainState::Free { .. } => {}
            }
        }
        let identity_hash = hash_identity(&name, None);
        self.can_mine_identity(&identity_hash, height, Utc::now().timestamp(), pub_key)
    }

    fn can_mine_identity(&self, identity_hash: &Bytes, height: u64, time: i64, pub_key: &Bytes) -> MineResult {
        if let Some(last) = self.get_last_full_block(height, Some(pub_key)) {
            // If this domain/identity is new
            let want_new_domain = !self.is_domain_in_blockchain(height, &identity_hash);
            // And the user hasn't mined a domain in previous 24h, then we allow her to mine
            let time = last.timestamp + NEW_DOMAINS_INTERVAL - time;
            if want_new_domain && time > 0 {
                return Cooldown { time };
            }
        }
        Fine
    }

    /// Checks if this identity is free or is owned by the same pub_key
    pub fn is_id_available(&self, height: u64, time: i64, identity: &Bytes, public_key: &Bytes) -> bool {
        let (transaction, state) = self.get_identity_transaction_and_state(identity, height, time);
        if transaction.is_none() {
            return true;
        }
        if transaction.unwrap().signing.eq(public_key) {
            return true;
        }
        match state {
            DomainState::NotFound => true,
            DomainState::Alive { .. } => false,
            DomainState::Expired { .. } => false,
            DomainState::Free { .. } => true
        }
    }

    pub fn get_zones(&self) -> &Vec<ZoneData> {
        &self.zones
    }

    fn load_zones() -> Vec<ZoneData> {
        let mut result: Vec<ZoneData> = Vec::new();
        let zones_text = ZONES_TXT.replace("\r", "");
        let zones: Vec<_> = zones_text.split('\n').collect();
        for zone in zones {
            let yggdrasil = zone == "ygg" || zone == "anon";
            result.push(ZoneData { name: zone.to_owned(), yggdrasil })
        }
        result
    }

    pub fn get_zones_hash() -> Bytes {
        Bytes::from_bytes(hash_sha256(ZONES_TXT.as_bytes()).as_slice())
    }

    /// Checks if some zone exists in our blockchain
    pub fn is_available_zone(&self, zone: &str) -> bool {
        for z in &self.zones {
            if z.name == zone {
                return true;
            }
        }

        false
    }

    /// Checks if some id exists in our blockchain
    pub fn is_domain_in_blockchain(&self, height: u64, id: &Bytes) -> bool {
        // Checking for existing domain in DB
        let mut statement = self.db.prepare(SQL_GET_DOMAIN_OWNER_BY_ID).unwrap();
        statement.bind((1, height as i64)).expect("Error in bind");
        statement.bind((2, id.as_slice())).expect("Error in bind");
        if let State::Row = statement.next().unwrap() {
            // If there is such an ID
            return true;
        }
        false
    }

    pub fn get_domain_renewal_time(&self, time: i64, identity_hash: &Bytes) -> Option<i64> {
        let mut statement = self.db.prepare(SQL_GET_DOMAIN_UPDATE_TIME).unwrap();
        statement.bind((1, identity_hash.as_slice())).expect("Error in bind");
        if let State::Row = statement.next().unwrap() {
            let timestamp: i64 = statement.read(0).unwrap();
            if timestamp < time - DOMAIN_LIFETIME {
                // This domain is too old
                return None;
            }
            return Some(timestamp);
        }
        None
    }

    pub fn get_domain_update_time(&self, identity_hash: &Bytes, height: u64, time: i64) -> Option<i64> {
        let mut statement = self.db.prepare(SQL_GET_DOMAIN_BY_ID).unwrap();
        statement.bind((1, identity_hash.as_slice())).expect("Error in bind");
        statement.bind((2, height as i64)).expect("Error in bind");
        if let State::Row = statement.next().unwrap() {
            let timestamp: i64 = statement.read(1).unwrap();
            if timestamp < time - DOMAIN_LIFETIME {
                // This domain is too old
                return None;
            }
            return Some(timestamp);
        }
        None
    }

    pub fn get_identity_transaction_and_state(&self, identity_hash: &Bytes, height: u64, time: i64) -> (Option<Transaction>, DomainState) {
        let mut statement = self.db.prepare(SQL_GET_DOMAIN_BY_ID).unwrap();
        statement.bind((1, identity_hash.as_slice())).expect("Error in bind");
        statement.bind((2, height as i64)).expect("Error in bind");
        if let State::Row = statement.next().unwrap() {
            let timestamp: i64 = statement.read(1).unwrap();
            // Determine current state of the domain
            let state = if timestamp + DOMAIN_LIFETIME >= time {
                DomainState::Alive { renewed_time: timestamp, until: timestamp + DOMAIN_LIFETIME }
            } else if timestamp + DOMAIN_LIFETIME + DOMAIN_RENEW_TIME >= time {
                DomainState::Expired { renewed_time: timestamp, until: timestamp + DOMAIN_LIFETIME + DOMAIN_RENEW_TIME }
            } else {
                DomainState::Free { renewed_time: timestamp }
            };
            let identity = Bytes::from_bytes(&statement.read::<Vec<u8>, usize>(2).unwrap());
            let confirmation = Bytes::from_bytes(&statement.read::<Vec<u8>, usize>(3).unwrap());
            let class = String::from(CLASS_DOMAIN);
            let data: String = statement.read(4).unwrap();
            let signing = Bytes::from_bytes(&statement.read::<Vec<u8>, usize>(5).unwrap());
            let encryption = Bytes::from_bytes(&statement.read::<Vec<u8>, usize>(6).unwrap());
            let transaction = Transaction { identity, confirmation, class, data, signing, encryption };
            return (Some(transaction), state);
        }
        (None, DomainState::NotFound)
    }

    /// Gets full Transaction info for any domain. Used by DNS part.
    pub fn get_domain_transaction_and_state(&self, domain: &str) -> (Option<Transaction>, DomainState) {
        if domain.is_empty() {
            return (None, DomainState::NotFound);
        }
        let identity_hash = hash_identity(domain, None);
        let (transaction, state) = self.get_identity_transaction_and_state(&identity_hash, self.get_height(), Utc::now().timestamp());
        if let Some(transaction) = transaction {
            debug!("Found transaction for domain {}: {:?}", domain, &transaction);
            if transaction.check_identity(domain) {
                return (Some(transaction), state);
            }
        }
        (None, DomainState::NotFound)
    }

    pub fn get_domain_info(&self, domain: &str) -> Option<String> {
        match self.get_domain_transaction_and_state(domain) {
            (None, _) => None,
            (Some(transaction), state) => {
                if matches!(state, DomainState::Alive {..}) {
                    Some(transaction.data)
                } else {
                    None
                }
            }
        }
    }

    pub fn get_domains_count(&self) -> i64 {
        let mut statement = self.db.prepare(SQL_GET_DOMAINS_COUNT).unwrap();
        if let State::Row = statement.next().unwrap() {
            return statement.read::<i64, usize>(0).unwrap();
        }
        0
    }

    pub fn get_users_count(&self) -> i64 {
        let mut statement = self.db.prepare(SQL_GET_USERS_COUNT).unwrap();
        if let State::Row = statement.next().unwrap() {
            return statement.read::<i64, usize>(0).unwrap();
        }
        0
    }

    pub fn get_user_block_count(&self, pub_key: &Bytes, max_height: u64) -> i64 {
        let mut statement = self.db.prepare(SQL_GET_USER_BLOCK_COUNT).unwrap();
        statement.bind((1, pub_key.as_slice())).expect("Error in bind");
        statement.bind((2, max_height as i64)).expect("Error in bind");
        if let State::Row = statement.next().unwrap() {
            return statement.read::<i64, usize>(0).unwrap();
        }
        0i64
    }

    pub fn get_my_domains(&self, keystore: Option<&Keystore>) -> HashMap<Bytes, (String, i64, DomainData)> {
        if keystore.is_none() {
            return HashMap::new();
        }

        let mut result = HashMap::new();
        let keystore = keystore.unwrap();
        let pub_key = keystore.get_public();
        let mut statement = self.db.prepare(SQL_GET_DOMAINS_BY_KEY).unwrap();
        statement.bind((1, pub_key.as_slice())).expect("Error in bind");
        let height = self.get_height();
        while let State::Row = statement.next().unwrap() {
            let timestamp = statement.read::<i64, usize>(0).unwrap();
            let identity = Bytes::from_bytes(&statement.read::<Vec<u8>, usize>(1).unwrap());
            let data = statement.read::<String, usize>(2).unwrap();
            let signing = Bytes::from_bytes(&statement.read::<Vec<u8>, usize>(3).unwrap());

            // Get the last transaction for this id and check if it is still ours
            // TODO use state to show it in UI
            let (transaction, _state) = self.get_identity_transaction_and_state(&identity, height, Utc::now().timestamp());
            if let Some(transaction) = transaction {
                if transaction.signing != signing {
                    trace!("Identity {:?} is not ours anymore, skipping", &identity);
                    continue;
                }
            }

            //trace!("Found transaction for domain {:?}", &transaction);
            if let Ok(data) = serde_json::from_str::<DomainData>(&data) {
                let decrypted = keystore.decrypt(data.encrypted.as_slice());
                let mut domain = String::from_utf8(decrypted.to_vec()).unwrap();
                if domain.is_empty() {
                    domain = String::from("unknown");
                }
                // TODO optimize
                match self.get_domain_renewal_time(timestamp, &identity) {
                    None => result.insert(identity, (domain, timestamp, data)),
                    Some(t) => result.insert(identity, (domain, t, data))
                };
            }
        }
        result
    }

    pub fn last_block(&self) -> Option<Block> {
        self.last_block.clone()
    }

    pub fn get_height(&self) -> u64 {
        match self.last_block {
            None => 0u64,
            Some(ref block) => block.index
        }
    }

    pub fn get_last_hash(&self) -> Bytes {
        match &self.last_block {
            None => Bytes::default(),
            Some(block) => block.hash.clone()
        }
    }

    pub fn get_soa_serial(&self) -> u32 {
        match &self.last_full_block {
            None => 0,
            Some(block) => block.timestamp as u32
        }
    }

    pub fn next_allowed_full_block(&self) -> u64 {
        match self.last_full_block {
            None => self.get_height() + 1,
            Some(ref block) => {
                if block.index < BLOCK_SIGNERS_START {
                    self.get_height() + 1
                } else {
                    max(block.index + BLOCK_SIGNERS_MIN, self.get_height() + 1)
                }
            }
        }
    }

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

    pub fn update_max_height(&mut self, height: u64) {
        self.max_height = height;
    }

    pub fn check_new_block(&self, block: &Block) -> BlockQuality {
        self.check_block(block, &self.last_block, &self.last_full_block)
    }

    /// Check if this block can be added to our blockchain
    pub fn check_block(&self, block: &Block, last_block: &Option<Block>, last_full_block: &Option<Block>) -> BlockQuality {
        if block.version > CHAIN_VERSION {
            warn!("Ignoring block from unsupported version:\n{:?}", &block);
            return Bad;
        }
        if WRONG_HASHES.contains(&block.hash) {
            warn!("Got block with hash from wrong hashes.");
            return Bad;
        }
        let timestamp = Utc::now().timestamp();
        if block.timestamp > timestamp + 60 {
            warn!("Ignoring block from the future:\n{:?}", &block);
            return Bad;
        }
        if let Some(last) = last_block {
            if block.index > last.index + 1 {
                debug!("Got future block {}", block.index);
                return Future;
            }
        }
        let difficulty = match &block.transaction {
            None => {
                if block.index == 1 {
                    ORIGIN_DIFFICULTY
                } else {
                    SIGNER_DIFFICULTY
                }
            }
            Some(t) => self.get_difficulty_for_transaction(t, block.index, block.timestamp)
        };
        if block.difficulty < difficulty {
            warn!("Block difficulty is lower than needed: {} < {}", block.difficulty, difficulty);
            return Bad;
        }
        if hash_difficulty(&block.hash) < block.difficulty {
            warn!("Ignoring block with low difficulty:\n{:?}", &block);
            return Bad;
        }
        if !check_block_hash(block) {
            warn!("Ignoring block with wrong hash:\n{:?}", &block);
            return Bad;
        }
        if !check_block_signature(block) {
            warn!("Ignoring block with wrong signature:\n{:?}", &block);
            return Bad;
        }
        if let Some(prev_block) = self.get_block(block.index - 1) {
            // https://en.bitcoinwiki.org/wiki/Limited_Confidence_Proof-of-Activity
            if block.prev_block_hash.ne(&prev_block.hash) {
                if block.index < self.get_height() - LIMITED_CONFIDENCE_DEPTH {
                    warn!("Ignoring block from shorter chain:\n{:?}", &block);
                    return Bad;
                } else {
                    warn!("Rewinding chain of block with wrong previous hash:\n{:?}", &block);
                    return Rewind;
                }
            }
        }

        if let Some(transaction) = &block.transaction {
            if !check_public_key_strength(&block.pub_key, KEYSTORE_DIFFICULTY) {
                warn!("Ignoring block with weak public key:\n{:?}", &block);
                return Bad;
            }
            // If this domain is not available to this public key
            if !self.is_id_available(block.index - 1, timestamp, &transaction.identity, &block.pub_key) {
                warn!("Block {:?} is trying to spoof an identity!", &block);
                return Bad;
            }
            if self.can_mine_identity(&transaction.identity, block.index, block.timestamp, &block.pub_key) != Fine {
                warn!("Block {:?} is mined too early!", &block);
                return Bad;
            }
            // Check if yggdrasil only property of zone is not violated
            if let Some(block_data) = transaction.get_domain_data() {
                if block_data.records.len() > MAX_RECORDS {
                    warn!("Someone mined too many records!");
                    return Bad;
                }
                let zones = self.get_zones();
                for z in zones {
                    if z.name == block_data.zone && z.yggdrasil {
                        for record in &block_data.records {
                            if !is_yggdrasil_record(record) {
                                warn!("Someone mined domain with clearnet records for Yggdrasil only zone!");
                                return Bad;
                            }
                            if let Some(data) = record.get_data() {
                                if data.len() > MAX_DATA_LEN {
                                    warn!("Someone mined too long record!");
                                    return Bad;
                                }
                            }
                        }
                    }
                }
            }
        }
        match last_block {
            None => {
                if !block.is_genesis() {
                    warn!("Block is from the future, how is this possible?");
                    return Future;
                }
                if !self.origin.is_zero() && block.hash.ne(&self.origin) {
                    warn!("Mining gave us a bad block:\n{:?}", &block);
                    return Bad;
                }
            }
            Some(last_block) => {
                if block.timestamp < last_block.timestamp && block.index > last_block.index {
                    warn!("Ignoring block with timestamp/index collision:\n{:?}", &block);
                    return Bad;
                }
                if last_block.index + 1 < block.index {
                    warn!("Block {} arrived too early.", block.index);
                    return Future;
                }
                if block.index > BLOCK_SIGNERS_START {
                    // If this block is main, signed part of blockchain
                    if !self.is_good_sign_block(block, last_full_block) {
                        return Bad;
                    }
                }

                if block.index <= last_block.index {
                    if block.index == last_block.index && last_block.hash == block.hash {
                        debug!("Ignoring block {}, we already have it", block.index);
                        return Twin;
                    }
                    if let Some(my_block) = self.get_block(block.index) {
                        return if my_block.hash.ne(&block.hash) {
                            warn!("Got forked block {} with hash {:?} instead of {:?}", block.index, block.hash, last_block.hash);
                            Fork
                        } else {
                            debug!("Ignoring block {}, we already have it", block.index);
                            Twin
                        };
                    }
                } else if block.prev_block_hash.ne(&last_block.hash) {
                    warn!("Ignoring block with wrong previous hash:\n{:?}", &block);
                    return Bad;
                }
            }
        }

        Good
    }

    /// Checks if this block is a good signature block
    fn is_good_sign_block(&self, block: &Block, last_full_block: &Option<Block>) -> bool {
        // If this is not a signing block
        if block.transaction.is_some() {
            return true;
        }
        if let Some(full_block) = &last_full_block {
            let sign_count = self.get_height() - full_block.index;
            if sign_count < BLOCK_SIGNERS_MIN {
                // Last full block is not locked enough
                if block.index > full_block.index && block.transaction.is_some() {
                    warn!("Not enough signing blocks over full {} block!", full_block.index);
                    return false;
                } else if !self.is_good_signer_for_block(block, full_block) {
                    return false;
                }
            } else if sign_count < BLOCK_SIGNERS_ALL && block.transaction.is_none()
                && !self.is_good_signer_for_block(block, full_block) {
                return false;
            }
        }
        true
    }

    /// Check if this block's owner is a good candidate to sign last full block
    fn is_good_signer_for_block(&self, block: &Block, full_block: &Block) -> bool {
        // If we got a signing block
        let signers: HashSet<Bytes> = self.get_block_signers(full_block).into_iter().collect();
        if !signers.contains(&block.pub_key) {
            warn!("Ignoring block {} from '{:?}', as wrong signer!", block.index, &block.pub_key);
            return false;
        }
        // If this signers' public key has already locked/signed that block we return error
        for i in (full_block.index + 1)..block.index {
            let signer = self.get_block(i).expect("Error in DB!");
            if signer.pub_key == block.pub_key {
                warn!("Ignoring block {} from '{:?}', already signed by this key", block.index, &block.pub_key);
                return false;
            }
        }
        true
    }

    fn get_difficulty_for_transaction(&self, transaction: &Transaction, height: u64, time: i64) -> u32 {
        match transaction.class.as_ref() {
            CLASS_DOMAIN => {
                // If this domain is already in blockchain we approve slightly smaller difficulty
                let discount = self.get_identity_discount(&transaction.identity, false, height, time);
                // TODO move this check somewhere appropriate
                return match serde_json::from_str::<DomainData>(&transaction.data) {
                    Ok(_) => DOMAIN_DIFFICULTY - discount,
                    Err(_) => {
                        warn!("Error parsing DomainData from {:?}", transaction);
                        u32::MAX
                    }
                }
            }
            CLASS_ORIGIN => ORIGIN_DIFFICULTY,
            _ => u32::MAX
        }
    }

    pub fn get_identity_discount(&self, identity: &Bytes, renewal: bool, height: u64, time: i64) -> u32 {
        match self.get_domain_update_time(identity, height, time) {
            None => 0u32,
            Some(timestamp) => {
                if renewal || self.get_height() < BLOCKS_WITHOUT_DISCOUNT {
                    return 1;
                }
                // Weeks since this domain was changed + 1, but not more than 7 weeks.
                // So max discount will be 8 bits of difficulty.
                (min((time - timestamp) / ONE_WEEK, 7i64) + 1) as u32
            }
        }
    }

    /// Gets public keys of a node that needs to mine "signature" block above this block
    /// block - last full block
    pub fn get_block_signers(&self, block: &Block) -> Vec<Bytes> {
        let minimum_block_count = if block.index < 855 {
            1i64
        } else {
            block.index as i64 / 100
        };
        let mut result = Vec::new();
        if block.index < BLOCK_SIGNERS_START || self.get_height() < block.index {
            return result;
        }

        assert!(block.transaction.is_some());
        if self.signers.borrow().has_signers_for(block.index) {
            return self.signers.borrow().signers.clone();
        }

        let mut set = HashSet::new();
        let mut tail = block.signature.get_tail_u64();
        let mut count = 1;
        let mut mitigated = false;
        let window = block.index - 1; // Without the last block
        trace!("Calculating signers, tail: {}, window: {}", tail, window);
        while set.len() < BLOCK_SIGNERS_ALL as usize {
            let index = (tail.wrapping_mul(count) % window) + 1; // We want it to start from 1
            if let Some(b) = self.get_block(index) {
                let block_count = self.get_user_block_count(&b.pub_key, block.index);
                if block_count < minimum_block_count {
                    //debug!("Skipping public key {:?} from block {}, it has too little {} blocks", &b.pub_key, index, block_count);
                    count += 1;
                    continue;
                }
                if b.pub_key != block.pub_key && !set.contains(&b.pub_key) {
                    result.push(b.pub_key.clone());
                    set.insert(b.pub_key);
                }
            }
            count += 1;
            if !mitigated && count > 5000 {
                tail = tail / 13;
                mitigated = true;
            }
        }
        debug!("Got signers for block {}: {:?}, loop count {}", block.index, &result, count);
        let mut signers = self.signers.borrow_mut();
        signers.index = block.index;
        signers.signers = result.clone();
        result
    }

    fn get_block_from_statement(statement: &mut Statement) -> Option<Block> {
        let index = statement.read::<i64, usize>(0).unwrap() as u64;
        let timestamp = statement.read::<i64, usize>(1).unwrap();
        let version = statement.read::<i64, usize>(2).unwrap() as u32;
        let difficulty = statement.read::<i64, usize>(3).unwrap() as u32;
        let random = statement.read::<i64, usize>(4).unwrap() as u32;
        let nonce = statement.read::<i64, usize>(5).unwrap() as u64;
        let transaction = Transaction::from_json(&statement.read::<String, usize>(6).unwrap());
        let prev_block_hash = Bytes::from_bytes(statement.read::<Vec<u8>, usize>(7).unwrap().as_slice());
        let hash = Bytes::from_bytes(statement.read::<Vec<u8>, usize>(8).unwrap().as_slice());
        let pub_key = Bytes::from_bytes(statement.read::<Vec<u8>, usize>(9).unwrap().as_slice());
        let signature = Bytes::from_bytes(statement.read::<Vec<u8>, usize>(10).unwrap().as_slice());
        Some(Block::from_all_params(index, timestamp, version, difficulty, random, nonce, prev_block_hash, hash, pub_key, signature, transaction))
    }
}

struct SignersCache {
    index: u64,
    signers: Vec<Bytes>
}

impl SignersCache {
    pub fn new() -> RefCell<SignersCache> {
        let cache = SignersCache { index: 0, signers: Vec::new() };
        RefCell::new(cache)
    }

    pub fn has_signers_for(&self, index: u64) -> bool {
        self.index == index && !self.signers.is_empty()
    }

    pub fn clear(&mut self) {
        self.index = 0;
        self.signers.clear();
    }
}

#[cfg(test)]
pub mod tests {
    use log::LevelFilter;
    #[allow(unused_imports)]
    use log::{debug, error, info, trace, warn};
    use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode, LevelPadding, format_description};

    use crate::{Block, Chain, Settings};

    fn init_logger() {
        let config = ConfigBuilder::new()
            .add_filter_ignore_str("mio::poll")
            .set_thread_level(LevelFilter::Off)
            .set_location_level(LevelFilter::Off)
            .set_target_level(LevelFilter::Error)
            .set_level_padding(LevelPadding::Right)
            .set_time_level(LevelFilter::Error)
            .set_time_format_custom(format_description!("[hour]:[minute]:[second].[subsecond digits:3]"))
            .build();
        if let Err(e) = TermLogger::init(LevelFilter::Trace, config, TerminalMode::Stdout, ColorChoice::Auto) {
            println!("Unable to initialize logger!\n{}", e);
        }
    }

    #[test]
    pub fn load_and_check() {
        init_logger();
        let settings = Settings::default();
        let mut chain = Chain::new(&settings, "./tests/blockchain.db");
        chain.check_chain(u64::MAX);
        assert_eq!(chain.get_height(), 149);
    }

    #[test]
    pub fn check_serde() {
        let settings = Settings::default();
        let chain = Chain::new(&settings, "./tests/blockchain.db");

        // Check the first block, its transaction doesn't have identity
        let block = chain.get_block(1).unwrap();
        let buf = serde_cbor::to_vec(&block).unwrap();
        let block2: Block = serde_cbor::from_slice(&buf[..]).unwrap();
        assert_eq!(block, block2);

        // Check second block, it is common "full" block with domain
        let block = chain.get_block(2).unwrap();
        let buf = serde_cbor::to_vec(&block).unwrap();
        let block2: Block = serde_cbor::from_slice(&buf[..]).unwrap();
        assert_eq!(block, block2);

        // Check block 36, it is an "empty" block, used to sign full blocks
        let block = chain.get_block(36).unwrap();
        let buf = serde_cbor::to_vec(&block).unwrap();
        let block2: Block = serde_cbor::from_slice(&buf[..]).unwrap();
        assert_eq!(block, block2);
    }
}