use serde::{Deserialize, Serialize};

use crate::{files, learner::profile, printers::{self, clear_console, print_boxed, print_screen}, utilities::questions::{ask_mcq, ask_multi_select, ask_plaintext}, Module};
use super::units;

const REQUIRED_FOR_MASTERY: f64 = 0.1;

#[derive(Serialize, Deserialize, Debug)]
pub struct UnitStory {
    unit: Module,
    title: String,
    screens: Vec<Screen>
}
#[derive(Serialize, Deserialize, Debug)]
enum ScreenType {
    Text,
    Mcq,
    Checkboxes,
    FreeResponse,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Screen {
    screen_type: ScreenType,
    text: String,
    question: Option<String>,
    options: Option<Vec<String>>,
    correct_indices: Option<Vec<usize>>,
    correct_text: Option<Vec<String>>,
    hints: Option<Vec<String>>,
}

pub fn outer_loop(learner: &mut profile::Learner) {
    loop {
        let next_unit = select_next_unit(&learner);
        if next_unit.is_none() {
            print_boxed("Congratulations! You have completed all the modules. You are now a master of the galaxy! I have nothing new to teach you. Well done.", printers::StatementType::CorrectFeedback);
            println!("Saving your progress...");
            files::save_to_file(&learner.filename, &learner)
                .expect("Failed to save profile file");
            println!("Progress saved. Goodbye!");
            return;
        }
        let next_unit = next_unit.unwrap();
        inner_loop(next_unit.clone(), learner);
        if learner.progress.get(&next_unit).map_or(true, |p| p.get_probability() >= 1.0) {
            print_boxed(&format!("You have completed {:?}", next_unit), printers::StatementType::GeneralFeedback);
        } else {
            print_boxed(&format!("I think we still need to practice {:?}. Let's keep practicing before we move on.", next_unit), printers::StatementType::GeneralFeedback);
        }
        let answer = ask_mcq("Do you want to keep going or leave and save?", &[
            "Keep working (and save the galaxy)",
            "Leave and save (risking all life in the galaxy)",
        ]).unwrap();
        if answer == "Keep working (and save the galaxy)" {
            print_boxed(&format!("Great! Let's carry on."), printers::StatementType::GeneralFeedback);
            println!("Saving your progress...");
            files::save_to_file(&learner.filename, &learner)
                .expect("Failed to save profile file");
            println!("Progress saved. Let's keep going!");
            learner.display_progress();
            continue;
        } else {
            println!("Bummer. We were really counting on you, but I guess you probably have human needs to attend to.");
            println!("Saving your progress...");
            files::save_to_file(&learner.filename, &learner)
                .expect("Failed to save profile file");
            println!("Progress saved. Goodbye!");
            return;
        }
    }
}

pub fn inner_loop(unit: Module, learner: &mut profile::Learner) {
    print_boxed(&format!("The unit is {}", unit), printers::StatementType::GeneralFeedback);
    let filename = unit.get_filename();
    let unit_story = files::parse_unit(&filename).unwrap();
    let mut milestones = 0;
    printers::unit_screen(&unit_story.title);
    for screen in &unit_story.screens {
        match screen.screen_type {
            ScreenType::Text => { }
            _ => {
                milestones += 1;
            }
        }
    }
    if let Some(progress) = learner.progress.get_mut(&unit) {
        progress.set_milestones(milestones);
        progress.set_completed_milestones(0);
    }
    for screen in unit_story.screens {
        let mut attempts = 0;
        match screen.screen_type {
            ScreenType::Text => {
                clear_console();
                print_screen(&screen.text);
            }
            ScreenType::Mcq => {
                let options: Vec<&str> = screen.options.as_ref().unwrap().iter().map(|s| s.as_str()).collect();
                let mut options = screen.options.clone().unwrap();
                options.push("Get hint".to_string());
                let options: Vec<&str> = options.iter().map(|s| s.as_str()).collect();
                loop {
                    let answer = ask_mcq(&screen.question.clone().unwrap(), &options).unwrap();
                    let correct = if screen.correct_indices.as_ref().unwrap().contains(&options.iter().position(|x| *x == answer).unwrap_or(usize::MAX)) {
                        print_boxed("Correct!", printers::StatementType::CorrectFeedback);
                        true
                    } else {
                        if screen.hints.is_some() {
                            if attempts == screen.hints.iter().len() {
                                let correct_indices_text: Vec<_> = screen.correct_indices.unwrap().iter().map(|&i| options[i]).collect();
                                print_boxed(&format!("It seems like these hints aren't working. The answer is: {:?}", correct_indices_text), printers::StatementType::IncorrectFeedback);
                                break;
                            } else {
                                let hint = screen.hints.as_ref().unwrap()[attempts].clone();
                                print_boxed(&format!("Please try the question again. Here's a hint: {}", hint), printers::StatementType::IncorrectFeedback);
                                println!("Please try the question again. Here's a hint: {}", hint);
                                attempts += 1;
                            }
                        }
                        false
                    };
                    learner.update_progress(unit.clone(), correct);
                    learner.progress.get_mut(&unit).unwrap().print_progress();
                    if correct {
                        break;
                    }
                }
            }
            ScreenType::Checkboxes => {
                let options: Vec<&str> = screen.options.as_ref().unwrap().iter().map(|s| s.as_str()).collect();
                let mut options = screen.options.clone().unwrap();
                options.push("Get hint".to_string());
                let options: Vec<&str> = options.iter().map(|s| s.as_str()).collect();
                loop {
                    let answers = ask_multi_select(&screen.question.clone().unwrap(), &options).unwrap();
                    let correct_indices: Vec<usize> = screen.correct_indices.as_ref().unwrap().to_vec();
                    let selected_indices: Vec<usize> = answers.iter().filter_map(|answer| options.iter().position(|x| *x == *answer)).collect();
                    let correct = selected_indices.iter().all(|&index| correct_indices.contains(&index)) && selected_indices.len() == correct_indices.len();
                    if correct {
                        print_boxed("Correct!", printers::StatementType::CorrectFeedback);
                        learner.update_progress(unit.clone(), true);
                        learner.progress.get_mut(&unit).unwrap().print_progress();
                        break;
                    } else {
                        if screen.hints.is_some() {
                            if attempts == screen.hints.iter().len() {
                                print_boxed(&format!("It seems like these hints aren't working. The correct answers are: {:?}", correct_indices.iter().map(|&i| options[i]).collect::<Vec<_>>()), printers::StatementType::IncorrectFeedback);
                                break;
                            } else {
                                let hint = screen.hints.as_ref().unwrap()[attempts].clone();
                                let hint = format!("{}\nThere are {} correct answers.", hint, correct_indices.len());
                                print_boxed(&format!("Please try the question again. Here's a hint: {}", hint), printers::StatementType::IncorrectFeedback);
                                println!("Please try the question again. Here's a hint: {}", hint);
                                attempts += 1;
                            }
                        }
                        learner.update_progress(unit.clone(), false);
                        learner.progress.get_mut(&unit).unwrap().print_progress();
                    }
                }
            }
            ScreenType::FreeResponse => {
                loop {
                    let answer= ask_plaintext(&screen.question.clone().unwrap()).unwrap();
                    let correct_text: Vec<String> = screen.correct_text.as_ref().unwrap().to_vec();
                    let normalized_answer = answer.trim().to_lowercase().replace(|c: char| !c.is_alphanumeric(), "");
                    let correct = screen.correct_text.as_ref().unwrap().iter().any(|i| {
                        let normalized_correct_answer = i.trim().to_lowercase().replace(|c: char| !c.is_alphanumeric(), "");
                        normalized_answer == normalized_correct_answer
                    });
                    if correct {
                        print_boxed("Correct!", printers::StatementType::CorrectFeedback);
                        learner.update_progress(unit.clone(), true);
                        learner.progress.get_mut(&unit).unwrap().print_progress();
                        break;
                    } else {
                        if screen.hints.is_some() {
                            if attempts == screen.hints.iter().len() {
                                print_boxed(&format!("It seems like these hints aren't working. The correct answers are: {:?}", correct_text), printers::StatementType::IncorrectFeedback);
                                break;
                            } else {
                                let hint = screen.hints.as_ref().unwrap()[attempts].clone();
                                print_boxed(&format!("Please try the question again. Here's a hint: {}", hint), printers::StatementType::IncorrectFeedback);
                                println!("Please try the question again. Here's a hint: {}", hint);
                                attempts += 1;
                            }
                        }
                        learner.update_progress(unit.clone(), false);
                        learner.progress.get_mut(&unit).unwrap().print_progress();
                    }
                }
            }
        }
    }
}

pub fn select_next_unit(learner: &profile::Learner) -> Option<units::Module> {
    let modules = units::Module::iter();
    if learner.progress.get(&Module::Introduction).map_or(true, |p| p.get_probability() < 0.5) {
        return Some(crate::Module::Introduction);
    }
    for module in modules {
        if let Some(progress) = learner.progress.get(&module) {
            if progress.get_probability() < REQUIRED_FOR_MASTERY {
                if progress.get_probability() != 0.0 {
                    print_boxed(&format!("It looks like you haven't mastered {} yet. Let's work on that.", module), printers::StatementType::GeneralFeedback);
                }
                return Some(module);
            }
            // if progress.get_milestones_completed() == 0 {
            //     return Some(module);
            // }
        }
    }
    return None;
}