use miette::Diagnostic;
use thiserror::Error;

use super::{Sessions, path};

#[derive(Debug, Error, Diagnostic)]
pub enum Error {
    #[error("Error reading your sessions file")]
    #[diagnostic(
        code(muxi::sessions::io_error),
        help("Check that ~/.config/muxi/sessions.toml exists and is readable")
    )]
    IoError(#[from] std::io::Error),

    #[error("Error parsing your sessions file")]
    #[diagnostic(
        code(muxi::sessions::parse_error),
        help(
            "Check the TOML syntax in ~/.config/muxi/sessions.toml\nExample format:\nd = {{ name = \"dotfiles\", path = \"~/.dotfiles\" }}\nq = {{ name = \"qmk\", path = \"~/qmk_userspace\", on_create = [{{ new_window = {{ path = \"../qmk_firmware\" }} }}] }}"
        )
    )]
    ParseError(#[from] toml_edit::de::Error),
}

#[derive(Debug)]
pub struct Muxi {
    pub sessions: Sessions,
}

impl Muxi {
    pub fn new() -> Result<Self, Error> {
        let sessions_file = path::sessions_file();

        if std::fs::metadata(&sessions_file).is_err() {
            std::fs::create_dir_all(path::muxi_dir())?;
            std::fs::File::create(&sessions_file)?;
        }

        let sessions_data = std::fs::read_to_string(sessions_file)?;
        let sessions = Sessions(toml_edit::de::from_str(&sessions_data)?);

        Ok(Self { sessions })
    }
}

#[cfg(test)]
mod tests {
    use std::env::temp_dir;
    use std::io::Write;
    use std::path::PathBuf;

    use uuid::Uuid;

    use crate::muxi::{NewWindow, OnCreateAction, Session};

    use super::*;

    fn with_config<F>(config: &str, test: F)
    where
        F: Fn(Sessions) + std::panic::RefUnwindSafe,
    {
        // Create tmp folder
        let pwd = temp_dir().join(Uuid::new_v4().to_string());
        std::fs::create_dir_all(&pwd).unwrap();

        // Create sessions file
        let path = pwd.join("sessions.toml");
        let mut file = std::fs::File::create(path).unwrap();
        file.write_all(config.as_bytes()).unwrap();

        // Set $MUXI_CONFIG_PATH to current folder and load config
        temp_env::with_var("MUXI_CONFIG_PATH", Some(pwd.clone()), || {
            let muxi = Muxi::new();

            // Cleanup before test, in case of panic
            std::fs::remove_dir_all(&pwd).unwrap();

            test(muxi.unwrap().sessions);
        });
    }

    fn expected_sessions() -> Sessions {
        Sessions(
            vec![
                (
                    "d".into(),
                    Session {
                        name: "dotfiles".into(),
                        path: path::expand_tilde("~/.dotfiles".into()),
                        on_create: Vec::new(),
                    },
                ),
                (
                    "k".into(),
                    Session {
                        name: "muxi".into(),
                        path: path::expand_tilde("/home/user/muxi/".into()),
                        on_create: Vec::new(),
                    },
                ),
                (
                    "Space".into(),
                    Session {
                        name: "tmux".into(),
                        path: path::expand_tilde("~/Sites/tmux/".into()),
                        on_create: Vec::new(),
                    },
                ),
                (
                    "M-n".into(),
                    Session {
                        name: "notes".into(),
                        path: path::expand_tilde(
                            "~/Library/Mobile Documents/com~apple~CloudDocs/notes".into(),
                        ),
                        on_create: Vec::new(),
                    },
                ),
            ]
            .into_iter()
            .collect(),
        )
    }

    #[test]
    fn test_normal_sessions() {
        let config = r#"
            d = { name = "dotfiles", path = "~/.dotfiles" }
            k = { name = "muxi", path = "/home/user/muxi/" }
            Space = { name = "tmux", path = "~/Sites/tmux/" }
            M-n = { name = "notes", path = "~/Library/Mobile Documents/com~apple~CloudDocs/notes" }
        "#;

        let expected_sessions = expected_sessions();

        with_config(config, |sessions| {
            assert_eq!(sessions, expected_sessions);
        });
    }

    #[test]
    fn test_sessions_with_on_create() {
        let config = r#"
            q = { name = "qmk", path = "~/qmk_userspace", on_create = [
                { new_window = { path = "../qmk_firmware", name = "firmware" } }
            ] }
        "#;

        let expected_sessions = Sessions(
            vec![(
                "q".into(),
                Session {
                    name: "qmk".into(),
                    path: path::expand_tilde("~/qmk_userspace".into()),
                    on_create: vec![OnCreateAction::NewWindow(NewWindow {
                        path: Some(PathBuf::from("../qmk_firmware")),
                        name: Some("firmware".into()),
                        command: None,
                    })],
                },
            )]
            .into_iter()
            .collect(),
        );

        with_config(config, |sessions| {
            assert_eq!(sessions, expected_sessions);
        });
    }
}
