use crate::{
    databases::Databases,
    datasets::{AllDatasets, AnyDatasets},
    states::States,
    structs::Height,
    utils::log,
};

#[derive(Default, Debug)]
pub struct Heights {
    pub inserted: Height,
    pub computed: Height,
}

impl Heights {
    pub fn min(&self) -> Height {
        self.inserted.min(self.computed)
    }
}

pub fn find_first_inserted_unsafe_height(
    states: &mut States,
    databases: &mut Databases,
    datasets: &mut AllDatasets,
) -> Heights {
    let min_initial_inserted_last_address_height = datasets
        .address
        .get_min_initial_states()
        .inserted
        .last_height
        .as_ref()
        .cloned();

    let min_initial_inserted_last_address_date = datasets
        .address
        .get_min_initial_states()
        .inserted
        .last_date
        .as_ref()
        .cloned();

    let usable_databases = databases.check_if_usable(
        min_initial_inserted_last_address_height,
        min_initial_inserted_last_address_date,
    );

    states
        .date_data_vec
        .iter()
        .last()
        .map(|date_data| date_data.date)
        .and_then(|last_safe_date| {
            if !usable_databases {
                log("Unusable databases");

                return None;
            }

            let datasets_min_initial_states = datasets.get_min_initial_states().to_owned();

            let min_datasets_inserted_last_height = datasets_min_initial_states.inserted.last_height;
            let min_datasets_inserted_last_date = datasets_min_initial_states.inserted.last_date;

            log(&format!("min_datasets_inserted_last_height: {:?}", min_datasets_inserted_last_height));
            log(&format!("min_datasets_inserted_last_date: {:?}", min_datasets_inserted_last_date));

            let inserted_last_date_is_older_than_saved_state = min_datasets_inserted_last_date.map_or(true, |min_datasets_last_date| min_datasets_last_date < last_safe_date);

            if inserted_last_date_is_older_than_saved_state {
                dbg!(min_datasets_inserted_last_date , *last_safe_date);

                return None;
            }

            datasets
                .date_metadata
                .last_height
                .get_or_import(&last_safe_date)
                .and_then(|last_safe_height| {
                    let inserted_heights_and_dates_are_out_of_sync = min_datasets_inserted_last_height.map_or(true, |min_datasets_inserted_last_height| min_datasets_inserted_last_height < last_safe_height);

                    if inserted_heights_and_dates_are_out_of_sync {
                        log(&format!("last_safe_height ({last_safe_height}) > min_datasets_height ({min_datasets_inserted_last_height:?})"));

                        None
                    } else {
                        let computed = datasets_min_initial_states.computed.last_date.and_then(
                            |last_date| datasets.date_metadata
                                .last_height
                                .get(&last_date)
                                .and_then(|last_date_height| {
                                    if datasets_min_initial_states.computed.last_height.map_or(true, |last_height| {
                                        last_height < last_date_height
                                    }) {
                                        None
                                    } else {
                                        Some(last_date_height + 1_u32)
                                    }
                                })
                        ).unwrap_or_default();

                        Some(Heights {
                            inserted: last_safe_height + 1_u32,
                            computed,
                        })
                    }
                }
            )
        })
        .unwrap_or_else(|| {
            log("Starting over...");

            let include_addresses = !usable_databases
                || min_initial_inserted_last_address_date.is_none()
                || min_initial_inserted_last_address_height.is_none();

            // if include_addresses {
            //     dbg!(include_addresses);
            //     panic!("");
            // }

            states.reset(include_addresses);

            databases.reset(include_addresses);

            Heights::default()
        })
}
