use parking_lot::Mutex;
use std::sync::Arc;
use chrono::NaiveDate;
use crate::operation::Operation;
use crate::ioperation::IOperation;
use crate::constants::OperationType;
use crate::operation::OperationVariant;


// The key concept here is that we can take alternate operations and their effective periods
// So from a give op1 (in alt-op) -> (p1,p2, p3)  we can get op1 -> non overlaping (nop1, nop2, nop3, nop4)
// where p1 refers to period1  and nop1 non overlapping period1
// So if these effective periods are overlapping we break then into distinct non overlapping periods using normalize periods
// The priority in each non overlapping period is also generated by some rules.

// We then build a timeline of operations that are effective in each period.
// To do this we invoke generate_period_to_effective_operation_map() on the AlternateOperation class
// So we finally get p1 -> ((op1,op1_priroity), (op2, op2_priority), (op3, op3_priority))   p2 -> ((op2, op2_priority), (op3, op3_priority))   p3 -> ((op1, op1_priority), (op4, op4_priority))


// Given a period, we want to keep track of which operations are effective in that period
#[derive(Debug, Clone)]
pub struct PeriodOperationPriority {
    pub from: Option<NaiveDate>,
    pub till: Option<NaiveDate>,
    pub operation_priority: Vec<(Arc<Mutex<Operation>>, i32)>,
}


#[derive(Debug)]
pub struct AlternateOperation {
    name: String,
    alternates: Vec<Arc<Mutex<Operation>>>,
    period_effective_operation_map: Vec<PeriodOperationPriority>,
    generated_period_effective_operation_map: bool,
}

impl IOperation for AlternateOperation {
    fn operation_type(&self) -> OperationType {
        OperationType::Alternate
    }
}

impl AlternateOperation {
    pub fn new(name: String) -> Arc<Mutex<AlternateOperation>> {
        Arc::new(Mutex::new(AlternateOperation {
            name,
            period_effective_operation_map: Vec::new(),
            generated_period_effective_operation_map: false,
            alternates: Vec::new(),
        }))
    }

    pub fn is_generated_period_effective_operation_map(&self) -> bool {
        self.generated_period_effective_operation_map
    }

    pub fn set_generated_period_effective_operation_map(&mut self, value: bool) {
        self.generated_period_effective_operation_map = value;
    }

    pub fn get_period_effective_operation_map(&self) -> &Vec<PeriodOperationPriority> {
        &self.period_effective_operation_map
    }

    pub fn add_alternate(
        &mut self,
        operation: Arc<Mutex<Operation>>,
    ) {
        let op = operation.lock();
        let op_name = op.get_name().to_string();
        drop(op);

        if self.alternates.iter().any(|alt_op| *alt_op.lock().get_name() == op_name) {
            return;
        }

        self.alternates.push(operation.clone());
    }

    pub fn add_operation_as_alternate(
        &mut self,
        operation: Arc<Mutex<Operation>>,
        parent_operation: OperationVariant,
    ) {
        self.add_alternate(operation.clone());
        operation.lock().set_parent_operation(parent_operation.clone());
    }

    pub fn get_name(&self) -> &str {
        &self.name
    }

    // This function generates a list of PeriodOperationPriority objects
    // Each PeriodOperationPriority object contains a list of (operation, priority) tuples
    // The list of PeriodOperationPriority objects is stored in self.period_effective_operation_map
    // The PeriodOperationPriority objects are created by collecting all unique dates from the effective periods of all alternates
    // and then creating periods between these dates.
    // The (operation, priority) tuples are added to the PeriodOperationPriority objects based on their effective periods and priorities
    // Currently if none of the alternates have effective periods, we add all operations to the first period that is unbounded. Priority is set to i32::MAX(Lowest)
    // If however one (or more) of them have effectivity but some of them dont then the alternate operation that dont have effectivity 
    // are not considered for planning.
    // TODO: We could later on handle the case differently where some alternates have effectivity but some of them dont
    pub fn generate_period_to_effective_operation_map(&mut self) {
        // Collect all unique dates from alternates' effective periods
        let mut all_dates: Vec<NaiveDate> = Vec::new();
        for alt in &self.alternates {
            let op = alt.lock();
            for period in op.get_effective_periods() {
                if let Some(from) = period.from {
                    all_dates.push(from);
                }
                if let Some(till) = period.till {
                    all_dates.push(till);
                }
            }
        }
        all_dates.sort();
        all_dates.dedup();

        self.generated_period_effective_operation_map = true;

        if all_dates.is_empty() {
            // Handle case where all periods are unbounded
            let mut operations_with_priority: Vec<(Arc<Mutex<Operation>>, i32)> = self.alternates
                .iter()
                .map(|op| {
                    let priority = op.lock().get_effective_periods()
                        .first()
                        .map_or(i32::MAX, |p| p.priority);
                    (op.clone(), priority)
                })
                .collect();
            
            if !operations_with_priority.is_empty() {
                // Sort by priority and name
                operations_with_priority.sort_by(|a, b| {
                    match a.1.cmp(&b.1) {
                        std::cmp::Ordering::Equal => {
                            a.0.lock().get_name().cmp(b.0.lock().get_name())
                        },
                        other => other
                    }
                });

                self.period_effective_operation_map = vec![PeriodOperationPriority {
                    from: None,
                    till: None,
                    operation_priority: operations_with_priority,
                }];
            }
            return;
        }

        let mut periods = Vec::new();

        // Handle period before first date if any operations are unbounded at start
        let unbounded_start_ops: Vec<(Arc<Mutex<Operation>>, i32)> = self.alternates
            .iter()
            .filter(|alt| alt.lock().get_effective_periods().iter().any(|p| p.from.is_none()))
            .map(|op| {
                let priority = op.lock().get_effective_periods()
                    .first()
                    .map_or(i32::MAX, |p| p.priority);
                (op.clone(), priority)
            })
            .collect();

        if !unbounded_start_ops.is_empty() {
            let mut ops = unbounded_start_ops;
            ops.sort_by(|a, b| {
                match a.1.cmp(&b.1) {
                    std::cmp::Ordering::Equal => {
                        a.0.lock().get_name().cmp(b.0.lock().get_name())
                    },
                    other => other
                }
            });
            periods.push(PeriodOperationPriority {
                from: None,
                till: Some(all_dates[0]),
                operation_priority: ops,
            });
        }

        // Process periods between dates
        for window in all_dates.windows(2) {
            let start_date = window[0];
            let end_date = window[1];

            let mut effective_ops: Vec<(Arc<Mutex<Operation>>, i32)> = self.alternates
                .iter()
                .filter(|alt| {
                    alt.lock().get_effective_periods().iter().any(|period| {
                        let after_start = match period.from {
                            Some(from) => from <= start_date,
                            None => true,
                        };
                        let before_end = match period.till {
                            Some(till) => till > start_date,
                            None => true,
                        };
                        after_start && before_end
                    })
                })
                .map(|op| {
                    let priority = op.lock().get_effective_periods()
                        .iter()
                        .filter(|period| {
                            let after_start = match period.from {
                                Some(from) => from <= start_date,
                                None => true,
                            };
                            let before_end = match period.till {
                                Some(till) => till > start_date,
                                None => true,
                            };
                            after_start && before_end
                        })
                        .map(|p| p.priority)
                        .min()
                        .unwrap_or(i32::MAX);
                    (op.clone(), priority)
                })
                .collect();

            if !effective_ops.is_empty() {
                effective_ops.sort_by(|a, b| {
                    match a.1.cmp(&b.1) {
                        std::cmp::Ordering::Equal => {
                            a.0.lock().get_name().cmp(b.0.lock().get_name())
                        },
                        other => other
                    }
                });
                periods.push(PeriodOperationPriority {
                    from: Some(start_date),
                    till: Some(end_date),
                    operation_priority: effective_ops,
                });
            }
        }

        // Handle period after last date if any operations are unbounded at end
        let unbounded_end_ops: Vec<(Arc<Mutex<Operation>>, i32)> = self.alternates
            .iter()
            .filter(|alt| alt.lock().get_effective_periods().iter().any(|p| p.till.is_none()))
            .map(|op| {
                let priority = op.lock().get_effective_periods()
                    .iter()
                    .filter(|p| p.till.is_none())
                    .map(|p| p.priority)
                    .min()
                    .unwrap_or(i32::MAX);
                (op.clone(), priority)
            })
            .collect();

        if !unbounded_end_ops.is_empty() {
            let mut ops = unbounded_end_ops;
            ops.sort_by(|a, b| {
                match a.1.cmp(&b.1) {
                    std::cmp::Ordering::Equal => {
                        a.0.lock().get_name().cmp(b.0.lock().get_name())
                    },
                    other => other
                }
            });
            periods.push(PeriodOperationPriority {
                from: Some(all_dates[all_dates.len() - 1]),
                till: None,
                operation_priority: ops,
            });
        }

        self.period_effective_operation_map = periods;
    }

    // Find the period (and associated operations) containing ask_date
    pub fn find_period_effective_operations_on_date(&mut self, ask_date: NaiveDate) -> Option<&PeriodOperationPriority> {
        if !self.is_generated_period_effective_operation_map() {
            self.generate_period_to_effective_operation_map();
        }
        self.period_effective_operation_map
            .iter()
            .find(|period| {
                let after_start = match period.from {
                    Some(from) => from <= ask_date,
                    None => true,
                };
                let before_end = match period.till {
                    Some(till) => till > ask_date,
                    None => true,   
                };
                after_start && before_end
            })
    }

    pub fn find_periods_reverse_iter<'a>(
        &'a mut self,
        date: NaiveDate,
    ) -> impl Iterator<Item = &'a PeriodOperationPriority> {
        if !self.is_generated_period_effective_operation_map() {
            self.generate_period_to_effective_operation_map();
        }

        self.period_effective_operation_map
            .iter()
            .rev()
            .filter(move |period| {
                // Check if period encloses the date
                let encloses = match (period.from, period.till) {
                    (Some(from), Some(till)) => date >= from && date < till,
                    (None, Some(till)) => date < till,
                    (Some(from), None) => date >= from,
                    (None, None) => true,
                };

                // Check if period is entirely before the date
                let is_before = match period.till {
                    Some(till) => till <= date,
                    None => false,
                };

                encloses || is_before
            })
    }
}


#[cfg(test)]
mod tests {
    use super::*;
    use crate::flow::Flow;
    use crate::operation::MaterialFlowVariant;
    use crate::sku::SKU;
    use crate::operation::ResourceFlowVariant;
    use serial_test::serial;

    fn setup() {
        Operation::clear_repository();
    }

    fn create_test_operation(name: &str) -> Arc<Mutex<Operation>> {
        let sku = SKU::from_name("test_sku");
        let flow = Flow::new(false, 1.0, sku);
        let flow_wrapper = MaterialFlowVariant::Single(flow);
        
        Operation::new(
            name.to_string(),
            1,
            0,
            0,
            flow_wrapper,
            MaterialFlowVariant::None,
            ResourceFlowVariant::None,
        )
    }

    #[test]
    #[serial]
    fn test_alternate_operation() {
        setup();
        let alt_operation = AlternateOperation::new("test_alternate".to_string());
        
        let operation1 = create_test_operation("op1");
        let operation2 = create_test_operation("op2");
        let operation3 = create_test_operation("op3");

        let date_from = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
        let date_till = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();

        operation1.lock().add_period(Some(date_from), Some(date_till), 3);
        operation2.lock().add_period(None, None, 2);
        operation3.lock().add_period(None, None, 1);
        
        let mut alt_op = alt_operation.lock();
        alt_op.add_alternate(operation1.clone());
        alt_op.add_alternate(operation2.clone());
        alt_op.add_alternate(operation3.clone());

        alt_op.generate_period_to_effective_operation_map();
        let period_effective_operation_map = &alt_op.period_effective_operation_map;
        assert_eq!(period_effective_operation_map.len(), 3);
        assert_eq!(period_effective_operation_map[0].operation_priority.len(), 2);
        assert_eq!(period_effective_operation_map[1].operation_priority.len(), 3);
        assert_eq!(period_effective_operation_map[2].operation_priority.len(), 2);

    }

    #[test]
    #[serial]
    fn test_multiple_effectivities_for_operation() {
        setup();
        let alt_operation = AlternateOperation::new("test_alternate".to_string());
        let operation = create_test_operation("op1");
        let mut op1 = operation.lock();
        
        // Create dates for testing
        let jan_1 = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
        let mar_31 = NaiveDate::from_ymd_opt(2024, 3, 31).unwrap();
        let feb_1 = NaiveDate::from_ymd_opt(2024, 2, 1).unwrap();
        let may_1 = NaiveDate::from_ymd_opt(2024, 5, 1).unwrap();
        
        op1.add_period(
            Some(jan_1),
            Some(may_1),
            1
        );

        op1.add_period(
            Some(feb_1),
            Some(mar_31),
            3  // Higher priority period
        );
        
        // Test initial state
        assert_eq!(op1.get_effective_periods().len(), 2);
        assert_eq!(op1.get_effective_periods()[0].priority, 1);
        assert_eq!(op1.get_effective_periods()[1].priority, 3);
        
        // Test period normalization
        op1.split_into_non_overlapping_periods();
        assert_eq!(op1.get_effective_periods().len(), 3);
        
        let mut alt_op = alt_operation.lock();
        
        // Test initial state
        assert_eq!(op1.get_effective_periods().len(), 3);
        assert_eq!(op1.get_effective_periods()[0].priority, 1);
        assert_eq!(op1.get_effective_periods()[1].priority, 3);
        
        // Test period normalization
        op1.split_into_non_overlapping_periods();
        
        // Should now have three distinct periods:
        // 1. Jan 1 - Feb 1 (priority 1)
        // 2. Feb 1 - Mar 31 (priority 3)
        // 3. Mar 31 - May 1 (priority 1)
        assert_eq!(op1.get_effective_periods().len(), 3);
        
        // Verify first period (before overlap)
        assert_eq!(op1.get_effective_periods()[0].from, Some(jan_1));
        assert_eq!(op1.get_effective_periods()[0].till, Some(feb_1));
        assert_eq!(op1.get_effective_periods()[0].priority, 1);
        
        // Verify second period (during overlap - should have higher priority)
        assert_eq!(op1.get_effective_periods()[1].from, Some(feb_1));
        assert_eq!(op1.get_effective_periods()[1].till, Some(mar_31));
        assert_eq!(op1.get_effective_periods()[1].priority, 3);
        
        // Verify third period (after overlap)
        assert_eq!(op1.get_effective_periods()[2].from, Some(mar_31));
        assert_eq!(op1.get_effective_periods()[2].till, Some(may_1));
        assert_eq!(op1.get_effective_periods()[2].priority, 1);
        
        drop(op1);
        alt_op.add_alternate(operation.clone());

        // Test period to operation mapping
        alt_op.generate_period_to_effective_operation_map();
        
        // Should have three periods in the mapping
        assert_eq!(alt_op.period_effective_operation_map.len(), 3);
        
        // Verify operations are present in all periods
        for period in &alt_op.period_effective_operation_map {
            assert_eq!(period.operation_priority.len(), 1);
            assert_eq!(period.operation_priority[0].0.lock().get_name(), "op1");
        }
    }


/*Time ─────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────►
          Jan 1      Mar 1      Apr 1      May 1      Jun 1
          2024       2024       2024       2024       2024

Initial periods:
P1    p=1 ──────────────────►
P2    p=2          ──────────────────────────────►
P3  p=100 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━►
P4    p=8                    ▒▒▒▒▒▒▒▒►

After normalization:
         p=100     p=1       p=2       p=8       p=2       p=100
         ━━━━━━━━►──────────►──────────►▒▒▒▒▒▒▒▒►──────────►━━━━━━━━►
         (-∞)      (1/1)     (3/1)     (4/1)     (5/1)     (6/1 → ∞)

Legend:
━━━━► Priority 100 (unbounded period)
────► Priority 1-2
▒▒▒▒► Priority 8
*/

    #[test]
    #[serial]
    fn test_split_into_non_overlapping_periods() {
        setup();
        let oper = create_test_operation("op1");
        let mut op1 = oper.lock();

        op1.add_period(
            Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
            Some(NaiveDate::from_ymd_opt(2024, 4, 1).unwrap()),
            3
        );
        
        // Add overlapping period
        op1.add_period(
            Some(NaiveDate::from_ymd_opt(2024, 3, 1).unwrap()),
            Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()), // End is exclusive            
            2
        );  
        
        op1.split_into_non_overlapping_periods();
        
        // Should now have three periods:
        // 1. [Jan 1 - Mar 1) (priority 1)
        // 2. [Mar 1 - Apr 1) (priority 2, from smaller period)
        // 3. [Apr 1 - Jun 1) (priority 2)
        assert_eq!(op1.get_effective_periods().len(), 3);

        // Check first period
        assert_eq!(op1.get_effective_periods()[0].from,
                  Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[0].till,
                  Some(NaiveDate::from_ymd_opt(2024, 3, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[0].priority, 3);
        
        // Check overlapping period
        assert_eq!(op1.get_effective_periods()[1].from,
                  Some(NaiveDate::from_ymd_opt(2024, 3, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[1].till,
                  Some(NaiveDate::from_ymd_opt(2024, 4, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[1].priority, 2);

        // Add biggest containing period with low priority of 100
        op1.add_period(
            None,
            None,
            100
        );
        op1.add_period(
            Some(NaiveDate::from_ymd_opt(2024, 4, 1).unwrap()),
            Some(NaiveDate::from_ymd_opt(2024, 5, 1).unwrap()),
            8
        );

        // Repeat period. Currently ignored
        op1.add_period(
            Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
            Some(NaiveDate::from_ymd_opt(2024, 3, 1).unwrap()),
            1
        );
        
        // [EffectivePeriod { from: None, till: Some(2024-01-01), priority: 100 }, EffectivePeriod { from: Some(2024-01-01), till: Some(2024-03-01), priority: 3 }, EffectivePeriod { from: Some(2024-03-01), till: Some(2024-04-01), priority: 2 }, EffectivePeriod { from: Some(2024-04-01), till: Some(2024-05-01), priority: 8 }, EffectivePeriod { from: Some(2024-05-01), till: Some(2024-06-01), priority: 2 }, EffectivePeriod { from: Some(2024-06-01), till: None, priority: 100 }]
        op1.split_into_non_overlapping_periods();

        assert_eq!(op1.get_effective_periods().len(), 6);
        assert_eq!(op1.get_effective_periods()[0].from, None);
        assert_eq!(op1.get_effective_periods()[0].till, Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[0].priority, 100);

        assert_eq!(op1.get_effective_periods()[1].from, Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[1].till, Some(NaiveDate::from_ymd_opt(2024, 3, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[1].priority, 3);    

        assert_eq!(op1.get_effective_periods()[2].from, Some(NaiveDate::from_ymd_opt(2024, 3, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[2].till, Some(NaiveDate::from_ymd_opt(2024, 4, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[2].priority, 2);

        assert_eq!(op1.get_effective_periods()[3].from, Some(NaiveDate::from_ymd_opt(2024, 4, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[3].till, Some(NaiveDate::from_ymd_opt(2024, 5, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[3].priority, 8);

        assert_eq!(op1.get_effective_periods()[4].from, Some(NaiveDate::from_ymd_opt(2024, 5, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[4].till, Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[4].priority, 2);

        assert_eq!(op1.get_effective_periods()[5].from, Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()));
        assert_eq!(op1.get_effective_periods()[5].till, None);
        assert_eq!(op1.get_effective_periods()[5].priority, 100); 

    }

    #[test]
    #[serial]
    fn test_generate_period_to_effective_operation_map() {
        setup();
        let alt_operation = AlternateOperation::new("test_alternate".to_string());
        
        let operation1 = create_test_operation("op1");
        let operation2 = create_test_operation("op2");
        let operation3 = create_test_operation("op3");

        let mut op1 = operation1.lock();
        let mut op2 = operation2.lock();
        let mut op3 = operation3.lock();

        // Create dates for testing
        let jan_1 = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
        let mar_1 = NaiveDate::from_ymd_opt(2024, 3, 1).unwrap();
        let jun_1 = NaiveDate::from_ymd_opt(2024, 6, 1).unwrap();

        op1.add_period(Some(jan_1), Some(mar_1), 1);
        op2.add_period(None, Some(jun_1), 2);
        op3.add_period(Some(mar_1), None, 3);
        drop(op1);
        drop(op2);
        drop(op3);  

        // Add alternates with different periods
        let mut alt_op = alt_operation.lock();
        alt_op.add_alternate(operation1.clone());  // Jan-Mar
        alt_op.add_alternate(operation2.clone());         // -∞ to Jun
        alt_op.add_alternate(operation3.clone());         // Mar to ∞

        alt_op.generate_period_to_effective_operation_map();

        // Should have 4 distinct periods:
        // 1. (-∞ to Jan 1): [op2]
        // 2. (Jan 1 to Mar 1): [op1, op2]
        // 3. (Mar 1 to Jun 1): [op2, op3]
        // 4. (Jun 1 to ∞): [op3]
        assert_eq!(alt_op.period_effective_operation_map.len(), 4);

        // Check first period (before Jan 1)
        assert_eq!(alt_op.period_effective_operation_map[0].from, None);
        assert_eq!(alt_op.period_effective_operation_map[0].till, Some(jan_1));
        assert_eq!(alt_op.period_effective_operation_map[0].operation_priority.len(), 1);

        // Check second period (Jan 1 to Mar 1)
        assert_eq!(alt_op.period_effective_operation_map[1].from, Some(jan_1));
        assert_eq!(alt_op.period_effective_operation_map[1].till, Some(mar_1));
        assert_eq!(alt_op.period_effective_operation_map[1].operation_priority.len(), 2);

        // Check third period (Mar 1 to Jun 1)
        assert_eq!(alt_op.period_effective_operation_map[2].from, Some(mar_1));
        assert_eq!(alt_op.period_effective_operation_map[2].till, Some(jun_1));
        assert_eq!(alt_op.period_effective_operation_map[2].operation_priority.len(), 2);

        // Check fourth period (after Jun 1)
        assert_eq!(alt_op.period_effective_operation_map[3].from, Some(jun_1));
        assert_eq!(alt_op.period_effective_operation_map[3].till, None);
        assert_eq!(alt_op.period_effective_operation_map[3].operation_priority.len(), 1);
    }

    #[test]
    #[serial]
    fn test_generate_period_to_effective_operation_map_with_multiple_periods() {
        setup();
        let alt_operation = AlternateOperation::new("test_alternate".to_string());
        
        let operation1 = create_test_operation("op1");
        let operation2 = create_test_operation("op2");
        let operation3 = create_test_operation("op3");

        let mut op1 = operation1.lock();
        let mut op2 = operation2.lock();
        let mut op3 = operation3.lock();  

        // Create dates for testing
        let jan_1 = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
        let mar_1 = NaiveDate::from_ymd_opt(2024, 3, 1).unwrap();
        let may_1 = NaiveDate::from_ymd_opt(2024, 5, 1).unwrap();
        let jul_1 = NaiveDate::from_ymd_opt(2024, 7, 1).unwrap();
        let sep_1 = NaiveDate::from_ymd_opt(2024, 9, 1).unwrap();
        let nov_1 = NaiveDate::from_ymd_opt(2024, 11, 1).unwrap();

        let mut alt_op = alt_operation.lock();

        // Add op1 with two periods: Jan-May and Sep-Nov
        op1.add_period(Some(jan_1), Some(may_1), 1);
        op1.add_period(Some(sep_1), Some(nov_1), 1);

        // Add op2 with three periods: unbounded start-Mar, May-Jul, and unbounded end
        op2.add_period(None, Some(mar_1), 2);
        op2.add_period(Some(may_1), Some(jul_1), 2);
        op2.add_period(Some(nov_1), None, 2);

        // Add op3 with overlapping periods: Mar-Sep
        op3.add_period(Some(mar_1), Some(sep_1), 3);

        drop(op1);
        drop(op2);
        drop(op3);

        alt_op.add_alternate(operation1.clone());
        alt_op.add_alternate(operation2.clone());
        alt_op.add_alternate(operation3.clone());

        alt_op.generate_period_to_effective_operation_map();

        // Should have 8 distinct periods:
        // 1. (-∞ to Jan 1): [op2]
        // 2. (Jan 1 to Mar 1): [op1, op2]
        // 3. (Mar 1 to May 1): [op1, op3]
        // 4. (May 1 to Jul 1): [op2, op3]
        // 5. (Jul 1 to Sep 1): [op3]
        // 6. (Sep 1 to Nov 1): [op1]
        // 7. (Nov 1 to ∞): [op2]
        assert_eq!(alt_op.period_effective_operation_map.len(), 7);

        // Check first period (before Jan 1)
        assert_eq!(alt_op.period_effective_operation_map[0].from, None);
        assert_eq!(alt_op.period_effective_operation_map[0].till, Some(jan_1));
        assert_eq!(alt_op.period_effective_operation_map[0].operation_priority.len(), 1);
        // Verify it's op2
        assert_eq!(alt_op.period_effective_operation_map[0].operation_priority[0].0.lock().get_name(), "op2");

        // Check second period (Jan 1 to Mar 1)
        assert_eq!(alt_op.period_effective_operation_map[1].from, Some(jan_1));
        assert_eq!(alt_op.period_effective_operation_map[1].till, Some(mar_1));
        assert_eq!(alt_op.period_effective_operation_map[1].operation_priority.len(), 2);
        // Should contain op1 and op2
        assert_eq!(alt_op.period_effective_operation_map[1].operation_priority[0].0.lock().get_name(), "op1");
        assert_eq!(alt_op.period_effective_operation_map[1].operation_priority[1].0.lock().get_name(), "op2");
        assert_eq!(alt_op.period_effective_operation_map[1].operation_priority[0].1, 1);
        assert_eq!(alt_op.period_effective_operation_map[1].operation_priority[1].1, 2);

        // Check third period (Mar 1 to May 1)
        assert_eq!(alt_op.period_effective_operation_map[2].from, Some(mar_1));
        assert_eq!(alt_op.period_effective_operation_map[2].till, Some(may_1));
        assert_eq!(alt_op.period_effective_operation_map[2].operation_priority.len(), 2);
        // Should contain op1 and op3
        assert_eq!(alt_op.period_effective_operation_map[2].operation_priority[0].0.lock().get_name(), "op1");
        assert_eq!(alt_op.period_effective_operation_map[2].operation_priority[1].0.lock().get_name(), "op3");
        assert_eq!(alt_op.period_effective_operation_map[2].operation_priority[0].1, 1);
        assert_eq!(alt_op.period_effective_operation_map[2].operation_priority[1].1, 3);

        // Check fourth period (May 1 to Jul 1)
        assert_eq!(alt_op.period_effective_operation_map[3].from, Some(may_1));
        assert_eq!(alt_op.period_effective_operation_map[3].till, Some(jul_1));
        assert_eq!(alt_op.period_effective_operation_map[3].operation_priority.len(), 2);
        // Should contain op2 and op3
        assert_eq!(alt_op.period_effective_operation_map[3].operation_priority[0].0.lock().get_name(), "op2");
        assert_eq!(alt_op.period_effective_operation_map[3].operation_priority[1].0.lock().get_name(), "op3");
        assert_eq!(alt_op.period_effective_operation_map[3].operation_priority[0].1, 2);
        assert_eq!(alt_op.period_effective_operation_map[3].operation_priority[1].1, 3);    

        // Check fifth period (Jul 1 to Sep 1)
        assert_eq!(alt_op.period_effective_operation_map[4].from, Some(jul_1));
        assert_eq!(alt_op.period_effective_operation_map[4].till, Some(sep_1));
        assert_eq!(alt_op.period_effective_operation_map[4].operation_priority.len(), 1);
        // Should contain only op3
        assert_eq!(alt_op.period_effective_operation_map[4].operation_priority[0].0.lock().get_name(), "op3");
        assert_eq!(alt_op.period_effective_operation_map[4].operation_priority[0].1, 3);

        // Check sixth period (Sep 1 to Nov 1)
        assert_eq!(alt_op.period_effective_operation_map[5].from, Some(sep_1));
        assert_eq!(alt_op.period_effective_operation_map[5].till, Some(nov_1));
        assert_eq!(alt_op.period_effective_operation_map[5].operation_priority.len(), 1);
        // Should contain only op1
        assert_eq!(alt_op.period_effective_operation_map[5].operation_priority[0].0.lock().get_name(), "op1");
        assert_eq!(alt_op.period_effective_operation_map[5].operation_priority[0].1, 1);

        // Check seventh period (after Nov 1)
        assert_eq!(alt_op.period_effective_operation_map[6].from, Some(nov_1));
        assert_eq!(alt_op.period_effective_operation_map[6].till, None);
        assert_eq!(alt_op.period_effective_operation_map[6].operation_priority.len(), 1);
        // Should contain only op2
        assert_eq!(alt_op.period_effective_operation_map[6].operation_priority[0].0.lock().get_name(), "op2");
        assert_eq!(alt_op.period_effective_operation_map[6].operation_priority[0].1, 2);
    }

    #[test]
    #[serial]
    fn test_add_alternate_with_existing_operation() {
        setup();
        let alt_operation = AlternateOperation::new("test_alternate".to_string());
        let operation1 = create_test_operation("op1");
        let mut op1 = operation1.lock();
        
        let jan_1 = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
        let mar_1 = NaiveDate::from_ymd_opt(2024, 3, 1).unwrap();
        let _jun_1 = NaiveDate::from_ymd_opt(2024, 6, 1).unwrap();
        
        let mut alt_op = alt_operation.lock();
        // Add first period for op1
        op1.add_period(Some(jan_1), Some(mar_1), 1);
        drop(op1);
        alt_op.add_alternate(operation1.clone());
        drop(alt_op);
        
        // Add second period for same operation but through find to ensure it is same operation
        let found_op = Operation::find("op1");
        let mut alt_op = alt_operation.lock();
        if found_op.is_some() {
            alt_op.add_alternate(found_op.unwrap());
        }
        //drop(alt_op);
        // Should only have one operation effectivity with two periods
        //let mut alt_op = alt_operation.lock();
        assert_eq!(alt_op.alternates.len(), 1);

    }

    #[test]
    #[serial]
    fn test_generate_period_to_effective_operation_map_with_missing_periods() {
        setup();
        let alt_operation = AlternateOperation::new("test_alternate".to_string());
        
        let operation1 = create_test_operation("op1");
        let operation2 = create_test_operation("op2");

        // First test: Only operation1 has periods
        let jan_1 = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
        let mar_1 = NaiveDate::from_ymd_opt(2024, 3, 1).unwrap();
        operation1.lock().add_period(Some(jan_1), Some(mar_1), 1);
        
        let mut alt_op = alt_operation.lock();
        alt_op.add_alternate(operation1.clone());
        alt_op.add_alternate(operation2.clone()); // operation2 has no periods

        alt_op.generate_period_to_effective_operation_map();

        // Should have one period matching operation1's period
        assert_eq!(alt_op.period_effective_operation_map.len(), 1);

        let period = &alt_op.period_effective_operation_map[0];
        assert_eq!(period.from, Some(jan_1));
        assert_eq!(period.till, Some(mar_1));
        
        // Should only contain operation1 since operation2 has no effective periods
        assert_eq!(period.operation_priority.len(), 1);
        assert_eq!(period.operation_priority[0].0.lock().get_name(), "op1");
        assert_eq!(period.operation_priority[0].1, 1);

        // Second test: Neither operation has periods
        let alt_operation2 = AlternateOperation::new("test_alternate2".to_string());
        let operation3 = create_test_operation("op3");
        let operation4 = create_test_operation("op4");

        let mut alt_op2 = alt_operation2.lock();
        alt_op2.add_alternate(operation3);
        alt_op2.add_alternate(operation4);

        alt_op2.generate_period_to_effective_operation_map();

        // Should have exactly one unbounded period containing both operations
        assert_eq!(alt_op2.period_effective_operation_map.len(), 1);
        let unbounded_period = &alt_op2.period_effective_operation_map[0];
        assert_eq!(unbounded_period.from, None);
        assert_eq!(unbounded_period.till, None);
        assert_eq!(unbounded_period.operation_priority.len(), 2);
        
        // Operations should be sorted by name since they have equal (max) priority
        assert_eq!(unbounded_period.operation_priority[0].0.lock().get_name(), "op3");
        assert_eq!(unbounded_period.operation_priority[1].0.lock().get_name(), "op4");
        assert_eq!(unbounded_period.operation_priority[0].1, i32::MAX);
        assert_eq!(unbounded_period.operation_priority[1].1, i32::MAX);
    }

    #[test]
    #[serial]
    fn test_add_operation_as_alternate() {
        setup();
        let alt_operation = AlternateOperation::new("test_alternate".to_string());
        let operation = create_test_operation("op1");
        let parent_op = OperationVariant::Alternate(alt_operation.clone());
        
        let mut alt_op = alt_operation.lock();
        alt_op.add_operation_as_alternate(operation.clone(), parent_op);

        // Verify operation was added to alternates
        assert_eq!(alt_op.alternates.len(), 1);
        assert_eq!(alt_op.alternates[0].lock().get_name(), "op1");

    }

    #[test]
    #[serial]
    fn test_operations_sorted_by_priority_with_equals() {
        setup();
        let alt_operation = AlternateOperation::new("test_alternate".to_string());
        
        let operation1 = create_test_operation("op1");
        let operation2 = create_test_operation("op2");
        let operation3 = create_test_operation("op3");
        let operation4 = create_test_operation("op4");

        let date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
        let end_date = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();

        // Set priorities with some equals
        operation1.lock().add_period(Some(date), Some(end_date), 2); // Equal priority
        operation2.lock().add_period(Some(date), Some(end_date), 1); // Highest priority
        operation3.lock().add_period(Some(date), Some(end_date), 2); // Equal priority
        operation4.lock().add_period(Some(date), Some(end_date), 3); // Lowest priority
        
        let mut alt_op = alt_operation.lock();
        // Add in random order
        alt_op.add_alternate(operation3.clone());
        alt_op.add_alternate(operation1.clone());
        alt_op.add_alternate(operation4.clone());
        alt_op.add_alternate(operation2.clone());

        alt_op.generate_period_to_effective_operation_map();

        // Check that operations are sorted by priority and then by name
        for period in &alt_op.period_effective_operation_map {
            let op_info: Vec<(String, i32)> = period.operation_priority
                .iter()
                .map(|op| {
                    let op_ref = op.0.lock();
                    (
                        op_ref.get_name().to_string(),
                        op_ref.get_effective_periods()[0].priority
                    )
                })
                .collect();
            
            // Verify specific order:
            // op2 (priority 1)
            // op1 (priority 2) - comes before op3 due to name
            // op3 (priority 2)
            // op4 (priority 3)
            assert_eq!(op_info[0], ("op2".to_string(), 1));
            assert_eq!(op_info[1], ("op1".to_string(), 2));
            assert_eq!(op_info[2], ("op3".to_string(), 2));
            assert_eq!(op_info[3], ("op4".to_string(), 3));
        }
    }
}