use std::{fs, path::Path};

use lol_html::{element, html_content::Element, rewrite_str, RewriteStrSettings};

pub struct Html {
    pub contents: String,
    pub input_path: String,
    pub output_path: String,
}

impl Html {
    /// Sets `self.contents` to result of querying elements
    pub async fn mutate_elements(
        &mut self,
        selector: &str,
        mut call: impl FnMut(&mut Element<'_, '_>),
    ) {
        self.contents = self
            .query_elements(selector, |el| {
                call(el);
            })
            .await;
    }

    /// Returns the output path file
    pub fn output_path_file(&self) -> String {
        format!(
            "{}/{}",
            &self.output_path,
            Path::new(&self.input_path)
                .file_name()
                .unwrap()
                .to_str()
                .unwrap()
        )
    }

    /// Queries elements and returns entire HTML contents
    pub async fn query_elements(
        &self,
        selector: &str,
        mut call: impl FnMut(&mut Element<'_, '_>),
    ) -> String {
        let element_content_handlers = vec![element!(selector, |el| {
            call(el);
            Ok(())
        })];

        // run handler on contents
        rewrite_str(
            &self.contents,
            RewriteStrSettings {
                element_content_handlers,
                ..RewriteStrSettings::new()
            },
        )
        .unwrap_or_default()
    }

    /// Saves the HTML contents to the output path
    pub fn save(&self) {
        fs::write(self.output_path_file(), &self.contents).unwrap();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use lol_html::html_content::ContentType;

    #[tokio::test]
    async fn test_mutate_elements() {
        let mut html = Html {
            contents: fs::read_to_string("tests/data/index.html").unwrap(),
            input_path: String::from("tests/data/index.html"),
            output_path: String::from("tests/output"),
        };
        html.mutate_elements("body", |el| {
            el.set_inner_content(
                "<div id=\"different-div\">Mutated successfully!</div>",
                ContentType::Html,
            );
        })
        .await;
        assert_eq!(
            html.contents,
            "<body><div id=\"different-div\">Mutated successfully!</div></body>\n"
        );
    }

    #[test]
    fn test_output_path_file() {
        let html = Html {
            contents: String::from(""),
            input_path: String::from("tests/data/index.html"),
            output_path: String::from("tests/output"),
        };
        assert_eq!(html.output_path_file(), "tests/output/index.html");
    }

    #[tokio::test]
    async fn test_query_elements() {
        let html = Html {
            contents: fs::read_to_string("tests/data/index.html").unwrap(),
            input_path: String::from("tests/data/index.html"),
            output_path: String::from("tests/output"),
        };
        assert_eq!(
            html.query_elements("body", |el| {
                el.remove_and_keep_content();
            })
            .await,
            "<div id=\"test-div\">Found me!</div>\n"
        );
    }

    #[test]
    fn test_save() {
        use tempfile::tempdir;

        let tmp_dir = tempdir().unwrap();
        let tmp_path = tmp_dir.path().to_str().unwrap();

        let html = Html {
            contents: String::from(""),
            input_path: String::from("tests/data/index.html"),
            output_path: tmp_path.to_string(),
        };
        html.save();
        let tmp_out_file = format!("{}/index.html", tmp_path);
        assert!(Path::new(&tmp_out_file).exists());
    }
}
