use std::collections::{HashMap, HashSet};
use std::fmt::Display;
use std::io::{stdout, Write};
use std::rc::Rc;

use crate::cache::{RecordPointer, SpecialRecords};
use crate::cli::output::Writer;
use crate::cli::{EntryFormat, MemberOfAttribute, OutputFormat, OutputOptions, TimelineFormat};
use crate::membership_serialization::{CsvSerialization, SerializationType};
use crate::ntds::DataTableRecord;
use crate::ntds::FromDataTable;
use crate::ntds::LinkTable;
use crate::ntds::NtdsAttributeId;
use crate::ntds::Result;
use crate::object_tree::ObjectTree;
use crate::progress_bar::create_progressbar;
use crate::{cache, member_of_attribute, EntryId, Guid};
use crate::{ntds, FormattedValue};
use bodyfile::Bodyfile3Line;
use flow_record::prelude::Serializer;
use getset::Getters;
use maplit::hashset;
use regex::Regex;
use sddl::{Ace, Sid};
use serde::Serialize;
use serde_json::json;

use super::{Computer, Group, ObjectType, Person, Schema, SdTable};

/// wraps a ESEDB Table.
/// This class assumes the a NTDS datatable is being wrapped
#[derive(Getters)]
#[getset(get = "pub")]
pub struct DataTable<'info, 'db> {
    data_table: cache::DataTable<'info, 'db>,
    //database: Option<Weak<CDatabase<'r>>>,
    schema_record_id: RecordPointer,
    object_tree: Rc<ObjectTree>,
    link_table: Rc<LinkTable>,
    sd_table: Option<Rc<SdTable>>,
    schema: Schema,
    special_records: SpecialRecords,
    extended_rights: HashMap<Guid, String>,
}

impl<'info, 'db> DataTable<'info, 'db> {
    /// create a new datatable wrapper
    pub fn new(
        data_table: cache::DataTable<'info, 'db>,
        object_tree: Rc<ObjectTree>,
        schema_record_id: RecordPointer,
        link_table: Rc<LinkTable>,
        sd_table: Option<Rc<SdTable>>,
        schema: Schema,
        special_records: SpecialRecords,
    ) -> Result<Self> {
        let mut extended_rights = HashMap::new();
        for entry in special_records.extended_rights_container().children().borrow().iter() {
            let name = entry.name();
            if let Ok(entry) = data_table.data_table_record_from(*entry.record_ptr()) {
                if let Ok(Some(guid)) = entry.att_object_guid_opt() {
                    extended_rights.insert(guid, name.to_string());
                }
            }
        }

        Ok(Self {
            data_table,
            schema_record_id,
            object_tree,
            link_table,
            sd_table,
            schema,
            special_records,
            extended_rights
        })
    }

    fn find_type_record(
        &'db self,
        object_type: ObjectType,
    ) -> anyhow::Result<Option<DataTableRecord<'info, 'db>>> {
        let mut records = self.find_type_records(hashset! {object_type})?;
        Ok(records.remove(&object_type))
    }

    pub fn find_type_records(
        &'db self,
        mut types: HashSet<ObjectType>,
    ) -> anyhow::Result<HashMap<ObjectType, DataTableRecord<'info, 'db>>> {
        let mut type_records = HashMap::new();
        /*
        let children = self.data_table.children_of(self.schema_record_id);

        if !children.count() > 0 {
            return Err(anyhow::anyhow!(Error::SchemaRecordHasNoChildren));
        }
        */
        for dbrecord in self
            .data_table
            .metadata()
            .children_of(&self.schema_record_id)
        {
            let object_name2 = dbrecord.rdn().to_string();

            log::trace!("found a new type definition: '{}'", object_name2);

            if let Ok(object_type) = &object_name2[..].try_into() {
                if types.remove(object_type) {
                    log::debug!("found requested type definition for '{object_name2}'");
                    let data_record = self
                        .data_table()
                        .data_table_record_from(*dbrecord.record_ptr())
                        .unwrap();
                    type_records.insert(ObjectType::try_from(&object_name2[..])?, data_record);
                }
            }

            if types.is_empty() {
                break;
            }
        }
        log::info!("found {} type definitions", type_records.len());
        Ok(type_records)
    }

    pub fn show_users<T: SerializationType>(&self, options: &OutputOptions) -> anyhow::Result<()> {
        log::debug!("show_users()");
        self.show_typed_objects::<Person<T>>(options, ObjectType::Person)
    }

    pub fn show_groups<T: SerializationType>(&self, options: &OutputOptions) -> anyhow::Result<()> {
        log::debug!("show_groups()");
        self.show_typed_objects::<Group<T>>(options, ObjectType::Group)
    }

    pub fn show_computers<T: SerializationType>(
        &self,
        options: &OutputOptions,
    ) -> anyhow::Result<()> {
        log::debug!("show_computers()");
        self.show_typed_objects::<Computer<T>>(options, ObjectType::Computer)
    }

    pub fn show_type_names<T>(&self, options: &OutputOptions) -> anyhow::Result<()>
    where
        T: SerializationType,
    {
        let mut type_names = HashSet::new();
        for dbrecord in self
            .data_table()
            .metadata()
            .children_of(&self.schema_record_id)
        {
            let object_name2 = dbrecord.rdn().to_string();

            type_names.insert(object_name2);

            if type_names.is_empty() {
                break;
            }
        }
        let names = self
            .data_table()
            .metadata()
            .children_of(&self.schema_record_id)
            .map(|dbrecord| dbrecord.rdn().to_string());
        options.format().write_typenames(names)
    }

    pub fn show_tree(&self, max_depth: u8) -> Result<()> {
        let tree = self.object_tree.to_termtree(max_depth);
        println!("{}", tree);
        Ok(())
    }

    pub fn show_entry(&self, entry_id: EntryId, entry_format: EntryFormat) -> Result<()> {
        let record = match entry_id {
            EntryId::Id(id) => self.data_table.metadata().record(&id),
            EntryId::Rid(rid) => self.data_table.metadata().entries_with_rid(rid).next(),
            EntryId::Guid(guid) => self.data_table.metadata().entries_with_guid(&guid).next(),
        };

        match record {
            None => println!("no matching object found"),
            Some(entry) => {
                let record = self
                    .data_table()
                    .data_table_record_from(*entry.record_ptr())?;

                match entry_format {
                    EntryFormat::Simple => {
                        let all_attributes = record.all_attributes();
                        let header_width = all_attributes
                            .keys()
                            .map(|k| {
                                let k: &'static str = k.into();
                                k.len()
                            })
                            .max()
                            .unwrap();
                        let mut sorted_ids: Vec<_> = all_attributes
                            .keys()
                            .map(|id| {
                                let s: &'static str = id.into();
                                (id, s)
                            })
                            .collect();
                        sorted_ids.sort_by(|lhs, rhs| lhs.1.cmp(rhs.1));

                        for header in sorted_ids {
                            let value = all_attributes.get(header.0).unwrap();
                            println!("{: <header_width$}: {}", header.1, value.value());
                        }
                    }
                    EntryFormat::Json => {
                        let _ = serde_json::to_writer_pretty(stdout(), &json!(record));
                    }
                    EntryFormat::Table => {
                        let mut table = term_table::Table::from(&record);

                        if let Some(size) = termsize::get() {
                            let attrib_size = 20;
                            let value_size = size.cols.saturating_sub(attrib_size + 2);
                            table.set_max_column_widths(vec![
                                (0, attrib_size.into()),
                                (1, value_size.into()),
                            ])
                        }
                        println!("{}", table.render())
                    }
                }
            }
        }
        Ok(())
    }

    pub fn search_entries(&self, regex: &str) -> anyhow::Result<()> {
        let re = Regex::new(regex)?;
        let mut table_columns = vec![
            NtdsAttributeId::DsRecordId,
            NtdsAttributeId::DsParentRecordId,
            NtdsAttributeId::AttCommonName,
            NtdsAttributeId::AttRdn,
            NtdsAttributeId::AttObjectCategory,
        ];

        let mut records = Vec::new();

        for record in self.data_table.iter() {
            let matching_columns = record
                .all_attributes()
                .iter()
                .filter(|(_, attribute)| re.is_match(attribute.value().value()))
                .map(|(id, attribute)| {
                    (
                        *id,
                        (
                            attribute.column().to_string(),
                            attribute.attribute().to_string(),
                            attribute.value().to_string(),
                        ),
                    )
                })
                .collect::<HashMap<NtdsAttributeId, (String, String, String)>>();
            if !matching_columns.is_empty() {
                for id in matching_columns.keys() {
                    if !table_columns.contains(id) {
                        table_columns.push(*id);
                    }
                }
                records.push(record);
            }
        }

        let mut csv_wtr = csv::Writer::from_writer(std::io::stdout());
        let empty_string = "".to_owned();
        csv_wtr.write_record(table_columns.iter().map(|c| {
            let s: &str = c.into();
            s
        }))?;
        for record in records.into_iter() {
            let all_attributes = record.all_attributes();
            csv_wtr.write_record(table_columns.iter().map(|a| {
                all_attributes
                    .get(a)
                    .map(|attribute| attribute.value().value())
                    .unwrap_or(&empty_string)
                    .replace('\n', "\\n")
                    .replace('\r', "\\r")
            }))?;
        }
        Ok(())
    }

    pub fn show_typed_objects<O: ntds::FromDataTable + ntds::IsMemberOf>(
        &self,
        options: &OutputOptions,
        object_type: ObjectType,
    ) -> anyhow::Result<()> {
        let type_record = self
            .find_type_record(object_type)?
            .unwrap_or_else(|| panic!("missing record for type '{object_type}'"));
        let type_record_id = type_record.ds_record_id()?;
        log::info!("found type record with id {type_record_id}");

        let mut csv_wtr = csv::WriterBuilder::new()
            .flexible(false)
            .from_writer(std::io::stdout());
        let bar = create_progressbar(
            format!("loading {object_type} records"),
            (self
                .data_table()
                .metadata()
                .entries_of_type(&type_record_id)
                .count())
            .try_into()?,
        )?;

        let mut records = Vec::new();

        for record in self
            .data_table()
            .metadata()
            .entries_of_type(&type_record_id)
            .map(|e| self.data_table().data_table_record_from(*e.record_ptr()))
        {
            let record = record?;
            let dn = if *options.include_dn() {
                match self.object_tree().dn_of(record.ptr()) {
                    Some(dn) => FormattedValue::Value(dn),
                    None => FormattedValue::NoValue,
                }
            } else {
                FormattedValue::Hide
            };

            let sd = match &self.sd_table {
                Some(sd_table) => record
                    .att_nt_security_descriptor_opt()?
                    .and_then(|sd_id| sd_table.descriptor(&sd_id))
                    .map(Result::unwrap),
                None => None,
            };

            let mut record = O::new(record, options, self, &self.link_table, dn, sd.as_ref())?;

            if member_of_attribute() == MemberOfAttribute::Dn {
                record.update_membership_dn(self.object_tree());
            }

            match options.format() {
                OutputFormat::Csv => {
                    csv_wtr.serialize(record)?;
                    csv_wtr.flush()?;
                }
                OutputFormat::Json => {
                    records.push(record);
                }
                OutputFormat::JsonLines => {
                    println!("{}", serde_json::to_string(&record)?);
                }
            }
            bar.inc(1);
        }

        if *options.format() == OutputFormat::Json {
            println!("{}", serde_json::to_string_pretty(&records)?);
        }

        bar.finish_and_clear();
        drop(csv_wtr);

        Ok(())
    }

    fn timelines_from_supported_type(
        &self,
        record: DataTableRecord,
        record_type: &ObjectType,
        options: &OutputOptions,
        link_table: &LinkTable,
        distinguished_name: FormattedValue<String>,
    ) -> anyhow::Result<Vec<Bodyfile3Line>> {
        Ok(match record_type {
            ObjectType::Person => Vec::<Bodyfile3Line>::from(Person::<CsvSerialization>::new(
                record,
                options,
                self,
                link_table,
                distinguished_name,
                None,
            )?),
            ObjectType::Group => Vec::<Bodyfile3Line>::from(Group::<CsvSerialization>::new(
                record,
                options,
                self,
                link_table,
                distinguished_name,
                None,
            )?),
            ObjectType::Computer => Vec::<Bodyfile3Line>::from(Computer::<CsvSerialization>::new(
                record,
                options,
                self,
                link_table,
                distinguished_name,
                None,
            )?),
        })
    }

    fn show_timeline_for_records<'a, W>(
        &self,
        options: &OutputOptions,
        format: &TimelineFormat,
        ser: &mut Serializer<W>,
        link_table: &LinkTable,
        records: impl Iterator<Item = &'a RecordPointer>,
    ) -> anyhow::Result<()>
    where
        W: Write,
    {
        let known_types: HashMap<_, _> = self
            .schema
            .supported_type_entries()
            .iter()
            .map(|(ot, ptr)| (ptr.ds_record_id(), ot))
            .collect();

        records
            .map(|ptr| &self.data_table().metadata()[ptr])
            .map(|e| self.data_table().data_table_record_from(*e.record_ptr()))
            .try_for_each(|r| {
                let record = r?;
                match format {
                    TimelineFormat::Bodyfile => {
                        let lines = if let Some(object_type) = record.att_object_type_id_opt()? {
                            if let Some(record_type) = known_types.get(&object_type) {
                                self.timelines_from_supported_type(
                                    record,
                                    record_type,
                                    options,
                                    link_table,
                                    FormattedValue::Hide,
                                )?
                            } else {
                                record.to_bodyfile(self.data_table().metadata())?
                            }
                        } else {
                            record.to_bodyfile(self.data_table().metadata())?
                        };

                        for line in lines.into_iter() {
                            println!("{line}");
                        }
                    }
                    TimelineFormat::Record => {
                        match record.to_flow_record(self.data_table().metadata()) {
                            Ok(r) => ser.serialize(r)?,
                            Err(why) => log::warn!("{why}"),
                        }
                    }
                }
                Ok(())
            })
    }

    pub fn show_timeline(
        &self,
        options: &OutputOptions,
        link_table: &LinkTable,
        include_deleted: bool,
        format: &TimelineFormat,
    ) -> anyhow::Result<()> {
        let types = if *options.show_all_objects() {
            self.schema
                .all_type_entries()
                .iter()
                .map(|e| *e.ds_record_id())
                .collect()
        } else {
            self.schema
                .supported_type_entries()
                .values()
                .map(|e| *e.ds_record_id())
                .collect()
        };

        let mut serializer = Serializer::new(stdout());

        self.show_timeline_for_records(
            options,
            format,
            &mut serializer,
            link_table,
            self.data_table()
                .metadata()
                .entries_of_types(types)
                .map(|e| e.record_ptr()),
        )?;

        if include_deleted {
            let deleted_objects_records: HashSet<_> = HashSet::from_iter(
                self.data_table()
                    .metadata()
                    .children_ptr_of(self.special_records().deleted_objects().record_ptr()),
            );

            let records_with_deleted_from_container_guid: HashSet<_> = HashSet::from_iter(
                self.data_table()
                    .metadata()
                    .entries_with_deleted_from_container_guid(),
            );
            let records = deleted_objects_records.union(&records_with_deleted_from_container_guid);

            self.show_timeline_for_records(
                options,
                format,
                &mut serializer,
                link_table,
                records.copied(),
            )
            .unwrap();
        }

        Ok(())
    }

    pub(crate) fn show_objects_by_permission(
        &self,
        include_dn: bool,
        format: &OutputFormat,
        ace_filter: fn(&Ace) -> Option<&sddl::Sid>,
    ) -> std::result::Result<(), anyhow::Error> {
        assert!(self.sd_table().is_some());

        let bar = create_progressbar(
            "analyzing security descriptors".to_owned(),
            (self.sd_table().as_ref().unwrap().len()).try_into()?,
        )?;
        let mut interesting_security_descriptors = HashMap::new();
        for (id, sd_result) in self.sd_table().as_ref().unwrap().descriptors() {
            match sd_result {
                Err(_) => log::error!("ignoring this security descriptor"),
                Ok(sd) => {
                    let mut sids = HashSet::new();
                    if let Some(dacl) = sd.as_ref().dacl() {
                        for ace in dacl.ace_list() {
                            if let Some(sid) = ace_filter(ace) {
                                sids.insert(sid.clone());
                            }
                        }
                    }
                    if let Some(sacl) = sd.as_ref().sacl() {
                        for ace in sacl.ace_list() {
                            if let Some(sid) = ace_filter(ace) {
                                sids.insert(sid.clone());
                            }
                        }
                    }

                    if !sids.is_empty() {
                        interesting_security_descriptors.insert(id, InterestingAcl { _sd: sd, sids });
                    }
                }
            }
            bar.inc(1);
        }
        bar.finish_and_clear();

        let bar = create_progressbar(
            "finding objects with interesting ACLs".to_owned(),
            (self.data_table().metadata().iter().count()).try_into()?,
        )?;

        let mut csv_wtr = csv::WriterBuilder::new()
            .flexible(false)
            .from_writer(std::io::stdout());

        let mut interesting_permissions = Vec::new();
        let mut interesting_subjects = HashMap::new();
        for record in self.data_table().iter() {
            if let Some(sd_id) = record.att_nt_security_descriptor_opt()? {
                if let Some(acl) = interesting_security_descriptors.get(&sd_id) {
                    let mut subject_sids = Vec::new();
                    let mut subject_names = Vec::new();
                    for subject_sid in acl.sids.iter() {
                        if !interesting_subjects.contains_key(subject_sid) {
                            interesting_subjects.insert(subject_sid.clone(), 1);
                        }

                        subject_sids.push(subject_sid.to_string());

                        let mut subject_name = None;
                        // try to get the samAccountName
                        if let Some(rid) = subject_sid.sub_authority().last() {
                            if let Some(entry) =
                                self.data_table.metadata().entries_with_rid(*rid).next()
                            {
                                if let Some(sam_account_name) = entry.sam_account_name() {
                                    subject_name = Some(sam_account_name.clone());
                                }
                            }
                        }
                        // if that did not work, check if there is a known alias
                        // for the current sid. If not, take the SID
                        let subject_name = subject_name.unwrap_or(
                            subject_sid
                                .alias()
                                .and_then(|a| Some(a.long_name().to_string()))
                                .unwrap_or(subject_sid.to_string()),
                        );
                        subject_names.push(subject_name);
                    }

                    let object_rdn = self
                        .object_tree()
                        .relative_distinguished_name_of(record.ptr())
                        .unwrap();
                    let object_dn = if include_dn {
                        match self.object_tree().dn_of(record.ptr()) {
                            Some(dn) => FormattedValue::Value(dn),
                            None => FormattedValue::NoValue,
                        }
                    } else {
                        FormattedValue::Hide
                    };
                    let object_sid = record.att_object_sid_opt()?;

                    let access = AdministrativeAccess {
                        object_rdn,
                        object_dn,
                        object_sid,
                        subject_sids: subject_sids.join(","),
                        subject_names: subject_names.join(","),
                    };

                    match format {
                        OutputFormat::Csv => {
                            csv_wtr.serialize(access)?;
                            csv_wtr.flush()?;
                        }
                        OutputFormat::Json => {
                            interesting_permissions.push(access);
                        }
                        OutputFormat::JsonLines => {
                            println!("{}", serde_json::to_string(&record)?);
                        }
                    }
                }
            }
            bar.inc(1);
        }
        if *format == OutputFormat::Json {
            println!(
                "{}",
                serde_json::to_string_pretty(&interesting_permissions)?
            );
        }

        drop(csv_wtr);
        bar.finish_and_clear();

        Ok(())
    }

/*    
    pub fn show_extended_rights (&self) -> std::result::Result<(), anyhow::Error> {
        let mut extended_rights = HashMap::new();
        
        let bar = create_progressbar(
            "analyzing security descriptors".to_owned(),
            (self.sd_table().as_ref().unwrap().len()).try_into()?,
        )?;

        let mut interesting_security_descriptors = HashMap::new();
        let has_control_rights = |ace: &Ace| ace.can_have_object_type() && ace.object_type().is_some();
        for (id, sd_result) in self.sd_table().as_ref().unwrap().descriptors() {
            match sd_result {
                Err(_) => log::error!("ignoring this security descriptor"),
                Ok(sd) => {
                    let mut sids = HashSet::new();
                    if let Some(dacl) = sd.as_ref().dacl() {
                        for ace in dacl.ace_list() {
                            if let Some(guid) = ace.object_type() {

                            }
                            if let Some(sid) = ace_filter(ace) {
                                sids.insert(sid.clone());
                            }
                        }
                    }
                    if let Some(sacl) = sd.as_ref().sacl() {
                        for ace in sacl.ace_list() {
                            if let Some(sid) = ace_filter(ace) {
                                sids.insert(sid.clone());
                            }
                        }
                    }

                    if !sids.is_empty() {
                        interesting_security_descriptors.insert(id, InterestingAcl { _sd: sd, sids });
                    }
                }
            }
            bar.inc(1);
        }
        bar.finish_and_clear();

        for (guid, name) in self.extended_rights().iter() {
        }
    }

*/}

struct InterestingAcl {
    _sd: crate::win32_types::SecurityDescriptor,
    sids: HashSet<Sid>,
}

#[derive(Serialize)]
struct AdministrativeAccess<T: Display> {
    object_rdn: String,
    object_dn: FormattedValue<T>,
    object_sid: Option<crate::win32_types::Sid>,
    subject_sids: String,
    subject_names: String,
    //sddl: String,
}
