use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
use std::{io, process};

use serde::{Deserialize, Serialize};

use crate::types::class_type::ClassTypeName;
use crate::types::grade_type::GradeTypeGroupName;

use super::grade_type::GradeTypeGroup;
use super::{class::Class, class_type::ClassType, grade_type::GradeType};

#[derive(Debug, Deserialize, Serialize)]
pub struct GradesConfigFile {
    pub unit: String,
    pub grade_groups: Vec<GradeTypeGroup>,
    pub grade_types: Vec<GradeType>,
    pub class_types: Vec<ClassType>,
    pub classes: Vec<Class>,
}
impl GradesConfigFile {
    pub fn config_check(file: &PathBuf) {
        let config_file: GradesConfigFile = match GradesConfigFile::from_yaml_file(file) {
            Ok(output) => output,
            Err(e) => {
                eprintln!("Error while reading config file: {e}");
                process::exit(1);
            }
        };

        config_file
            .grade_groups
            .iter()
            .map(|group| &group.weight)
            .for_each(|weight| {
                if let Err(e) = fraction::Fraction::from_str(&weight.0) {
                    eprintln!("Error: {e}");
                    eprintln!(
                        "Your weight \"{}\" can not be turned into a fraction!",
                        weight.0
                    );
                    process::exit(1);
                }
            });

        let possible_grade_groups: Vec<GradeTypeGroupName> = config_file
            .grade_groups
            .iter()
            .map(|group| group.name.to_owned())
            .collect();

        config_file.grade_types.iter().for_each(|grade_type| {
            if !possible_grade_groups.contains(&grade_type.group) {
                eprintln!(
                    "The Grade Type name {} has not been declared!",
                    grade_type.group.0
                );
                process::exit(1);
            }
        });

        let possible_class_types: Vec<ClassTypeName> = config_file
            .class_types
            .iter()
            .map(|class_type| class_type.name.to_owned())
            .collect();

        config_file.classes.iter().for_each(|class| {
            if !possible_class_types.contains(&class.class_type) {
                eprintln!(
                    "The Class Type name {} has not been declared!",
                    class.class_type
                );
                process::exit(1);
            }
        });

        let classes_with_wrong_class_type: Vec<Class> = config_file
            .classes
            .into_iter()
            .filter(|class| !possible_class_types.contains(&class.class_type))
            .collect();
        if !classes_with_wrong_class_type.is_empty() {
            for class in classes_with_wrong_class_type {
                eprintln!("You tried to declare a class with a not already existing class_type!");
                eprintln!("class: {}; class_type: {}", class.name, class.class_type);
            }
            process::exit(1);
        }
    }
    pub fn to_yaml_file(&self, file: &PathBuf) -> io::Result<()> {
        match serde_yaml::to_string(&self) {
            Err(e) => {
                eprintln!("Error: {e}");
            }
            Ok(output) => {
                fs::write(file, [])?;
                let mut file = OpenOptions::new().append(true).open(file).unwrap();
                file.write_all(output.as_bytes())?;
            }
        }
        Ok(())
    }

    pub fn from_yaml_file(file: &PathBuf) -> io::Result<GradesConfigFile> {
        let file_string = fs::read_to_string(file)?;
        match serde_yaml::from_str::<GradesConfigFile>(&file_string) {
            Err(e) => {
                eprintln!("Error: {e}");
                if let Some(location) = e.location() {
                    eprintln!(
                        "Error is at line {}, column {} and byteindex {}",
                        location.line(),
                        location.column(),
                        location.index()
                    )
                }
                process::exit(1);
            }
            Ok(output) => Ok(output),
        }
    }
}

#[cfg(test)]
mod test {
    use std::env;

    use crate::types::grade_type::GradeTypeName;

    use super::*;
    //#[test]
    pub fn config_init(/*file: &PathBuf*/) -> io::Result<()> {
        let file = &PathBuf::from(env::var("HOME").expect("home should ALWAYS be set"))
            .join(".config/grades/config.yaml");
        let output = GradesConfigFile {
            unit: "Punkte".into(),
            grade_groups: vec![
                GradeTypeGroup {
                    name: "Zeugnis".into(),
                    weight: "2".into(),
                },
                GradeTypeGroup {
                    name: "Sonstige".into(),
                    weight: "1".into(),
                },
            ],
            class_types: vec![
                ClassType {
                    name: ClassTypeName("NebenFach".into()),
                },
                ClassType {
                    name: ClassTypeName("LeistungsKurs".into()),
                },
            ],
            grade_types: vec![
                GradeType {
                    name: GradeTypeName("Zeugnis".into()),
                    group: "Zeugnis".into(),
                },
                GradeType {
                    name: GradeTypeName("Sonstige".into()),
                    group: "Sonstige".into(),
                },
                GradeType {
                    name: GradeTypeName("Epo".into()),
                    group: "Sonstige".into(),
                },
            ],
            classes: vec![
                Class::new("Mathe", "LeistungsKurs".into()),
                Class::new("Deutsch", "NebenFach".into()),
            ],
        };
        output.to_yaml_file(file)?;
        Ok(())
    }
}
