use indicatif::{MultiProgress, ProgressBar};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;

pub trait ProgressTracker: Send + Sync {
    fn contains_section(&self, id: &str) -> bool;
    fn add_section(&self, id: &str, message: &str);
    fn add_task(&self, section_id: &str, task_id: &str, message: &str);
    fn finish_task(&self, id: &str, message: &str);
    fn fail_task(&self, id: &str, message: &str);
    fn skip_task(&self, id: &str, message: &str);
    fn clear(&self);
}

struct Section {
    header_index: usize,
    task_count: usize,
    pb: ProgressBar,
}

struct IndicatifProgressTrackerState {
    sections: HashMap<String, Section>,
    tasks: HashMap<String, ProgressBar>,
    pb_count: usize,
}

#[derive(Clone)]
pub struct IndicatifProgressTracker {
    mp: MultiProgress,
    state: Arc<Mutex<IndicatifProgressTrackerState>>,
}

impl IndicatifProgressTracker {
    pub fn new(base: MultiProgress) -> Self {
        let sections = HashMap::new();
        let tasks = HashMap::new();

        let state = Arc::new(Mutex::new(IndicatifProgressTrackerState {
            sections,
            tasks,
            pb_count: 0,
        }));

        Self { mp: base, state }
    }
}

impl ProgressTracker for IndicatifProgressTracker {
    fn add_section(&self, id: &str, message: &str) {
        let mut state = self.state.lock().unwrap();

        let header_pb = self
            .mp
            .add(ProgressBar::new(1).with_style(crate::theme::SECTION_STYLE.clone()));
        header_pb.finish_with_message(message.to_string());

        let header_index = state.pb_count;
        state.pb_count += 1;

        state.sections.insert(
            id.to_string(),
            Section {
                header_index,
                task_count: 0,
                pb: header_pb,
            },
        );
    }

    fn add_task(&self, section_id: &str, task_id: &str, message: &str) {
        let mut state = self.state.lock().unwrap();

        let insertion_index = {
            let current_section = state
                .sections
                .get(section_id)
                .expect("Section ID not found");
            current_section.header_index + current_section.task_count + 1 // +1 to insert after header
        };

        let pb = self.mp.insert(insertion_index, ProgressBar::new_spinner());
        pb.set_style(crate::theme::SPINNER_STYLE.clone());
        pb.set_prefix("  ");
        pb.set_message(message.to_string());
        pb.enable_steady_tick(Duration::from_millis(80));

        state.pb_count += 1;

        let section = state
            .sections
            .get_mut(section_id)
            .expect("Section ID not found");
        section.task_count += 1;

        // We inserted a new progress bar, so we must update the header_index
        // for all subsequent sections.
        for (id, s) in state.sections.iter_mut() {
            if id != section_id && s.header_index >= insertion_index {
                s.header_index += 1;
            }
        }

        state.tasks.insert(task_id.to_string(), pb);
    }

    fn finish_task(&self, id: &str, message: &str) {
        let state = self.state.lock().unwrap();
        if let Some(pb) = state.tasks.get(id) {
            pb.set_style(crate::theme::SUCCESS_SPINNER_STYLE.clone());
            pb.finish_with_message(message.to_string());
        }
    }

    fn fail_task(&self, id: &str, message: &str) {
        let state = self.state.lock().unwrap();
        if let Some(pb) = state.tasks.get(id) {
            pb.set_style(crate::theme::ERROR_SPINNER_STYLE.clone());
            pb.finish_with_message(message.to_string());
        }
    }

    fn skip_task(&self, id: &str, message: &str) {
        let state = self.state.lock().unwrap();
        if let Some(pb) = state.tasks.get(id) {
            pb.set_style(crate::theme::SKIP_SPINNER_STYLE.clone());
            pb.finish_with_message(message.to_string());
        }
    }

    fn contains_section(&self, id: &str) -> bool {
        let state = self.state.lock().unwrap();
        state.sections.contains_key(id)
    }

    fn clear(&self) {
        let mut state = self.state.lock().unwrap();

        state.tasks.values().for_each(|p| self.mp.remove(p));
        state.tasks.clear();
        state.sections.values().for_each(|s| self.mp.remove(&s.pb));
        state.sections.clear();
        state.pb_count = 0;

        let _ = self.mp.clear();
    }
}
