use anyhow::anyhow;
use getset::Getters;
use hashbrown::HashMap;
use std::sync::{Arc, Weak};
use termtree::Tree;

use crate::{
    cache::{MetaDataCache, RecordPointer, SpecialRecords},
    ntds::SdTable,
    object_tree_entry::ObjectTreeEntry,
};

#[derive(Getters)]
pub struct ObjectTree {
    #[getset(get = "pub")]
    root: Arc<ObjectTreeEntry>,
    record_index: HashMap<RecordPointer, Weak<ObjectTreeEntry>>,
}

impl ObjectTree {
    pub fn new(metadata: &MetaDataCache, sd_table: Option<Arc<SdTable>>) -> Self {
        let mut record_index = HashMap::new();
        let root = ObjectTreeEntry::populate_object_tree(metadata, sd_table, &mut record_index);
        Self { root, record_index }
    }

    pub fn get_special_records(&self) -> anyhow::Result<SpecialRecords> {
        log::info!("obtaining special record ids");

        let domain_root = ObjectTreeEntry::find_domain_root(&self.root)
            .ok_or(anyhow!("db has no domain root"))?;

        log::info!("found domain root '{}'", domain_root[0].name());

        let configuration = domain_root[0]
            .find_child_by_name("Configuration")
            .ok_or(anyhow!("db has no `Configuration` entry"))?;

        let schema_subpath = configuration
            .find_child_by_name("Schema")
            .ok_or(anyhow!("db has no `Schema` entry"))?;

        let deleted_objects = domain_root[0]
            .find_child_by_name("Deleted Objects")
            .ok_or(anyhow!("db has no `Deleted Objects` entry"))?;

        let extended_rights_container = configuration
            .find_child_by_name("Extended-Rights")
            .ok_or(anyhow!("db has no `Extended-Rights` entry"))?;

        Ok(SpecialRecords::new(
            schema_subpath,
            deleted_objects,
            extended_rights_container,
        ))
    }

    pub(crate) fn to_termtree(&self, max_depth: u8) -> Tree<Arc<ObjectTreeEntry>> {
        Self::__to_termtree(&self.root, max_depth)
    }

    pub fn __to_termtree(me: &Arc<ObjectTreeEntry>, max_depth: u8) -> Tree<Arc<ObjectTreeEntry>> {
        let tree = Tree::new(Arc::clone(me));
        if max_depth > 0 {
            let leaves: Vec<Tree<Arc<ObjectTreeEntry>>> = me
                .children()
                .lock()
                .unwrap()
                .iter()
                .map(|c| Self::__to_termtree(c, max_depth - 1))
                .collect();
            tree.with_leaves(leaves)
        } else {
            tree
        }
    }

    pub fn dn_of(&self, ptr: &RecordPointer) -> Option<String> {
        match self.record_index.get(ptr) {
            Some(record) => Some(
                record
                    .upgrade()
                    .unwrap_or_else(|| {
                        panic!("record pointer {ptr} points to already deleted object")
                    })
                    .distinguished_name()
                    .clone(),
            ),
            None => {
                log::error!("Missing entry {ptr} in the data_table. This might happen if there is an inconsistency in the link_table. I'll ignore this reference");
                None
            }
        }
    }

    pub fn relative_distinguished_name_of(&self, ptr: &RecordPointer) -> Option<String> {
        match self.record_index.get(ptr) {
            Some(record) => Some(
                record
                    .upgrade()
                    .unwrap_or_else(|| {
                        panic!("record pointer {ptr} points to already deleted object")
                    })
                    .relative_distinguished_name()
                    .clone(),
            ),
            None => {
                log::error!("Missing entry {ptr} in the data_table. This might happen if there is an inconsistency in the link_table. I'll ignore this reference");
                None
            }
        }
    }
}
