use std::{
    io::{Read, Write},
    path::Path,
};

use crate::{
    logger::Logger, md_generator::generate_markdown_from_paths, renderer::RenderOptions,
    util::fence::generate_outer_backticks,
};

pub fn generate_fenced_markdown<R: Read, W: Write>(
    input: R,
    mut output: W,
    render_options: RenderOptions,
    root: &Path,
    origin_base: &Path,
    project_title: Option<&str>,
    logger: Logger,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut md_output = Vec::new();
    generate_markdown_from_paths(
        input,
        &mut md_output,
        render_options,
        root,
        origin_base,
        project_title,
        logger,
    )?;
    let fence = generate_outer_backticks(&md_output);
    writeln!(output, "{}markdown", fence)?;
    output.write_all(&md_output)?;
    writeln!(output, "{}", fence)?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use std::fs;
    use std::io::{Cursor, Read, Write};
    use tempfile::tempdir;

    use crate::logger::Logger;
    use crate::renderer::RenderOptions;

    use super::generate_fenced_markdown;

    fn wrap_with_defaults<R: Read, W: Write>(
        input: R,
        output: W,
        root: &std::path::Path,
        origin_base: &std::path::Path,
        project_title: Option<&str>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let logger = Logger::default();
        let render_options = RenderOptions::default();
        generate_fenced_markdown(
            input,
            output,
            render_options,
            root,
            origin_base,
            project_title,
            logger,
        )
    }

    #[test]
    fn empty_input_produces_wrapped_header() {
        let temp_dir = tempdir().unwrap();
        let input = Cursor::new(b"");
        let mut output = Vec::new();
        let root = temp_dir.path();
        let origin_base = temp_dir.path();

        wrap_with_defaults(input, &mut output, root, origin_base, Some("Project name")).unwrap();

        assert_eq!(
            String::from_utf8(output).unwrap(),
            "```markdown\n# Project name\n```\n"
        );
    }

    #[test]
    fn single_file_is_wrapped() {
        let temp_dir = tempdir().unwrap();
        let origin_base = temp_dir.path();
        let input = Cursor::new(b"test_main.rs\0");
        let mut output = Vec::new();
        let root = temp_dir.path();

        fs::write(origin_base.join("test_main.rs"), "fn main() {}").unwrap();

        wrap_with_defaults(input, &mut output, root, origin_base, None).unwrap();

        let output_str = String::from_utf8(output).unwrap();
        // Must contain outer fence
        assert!(output_str.starts_with("````markdown\n"));
        assert!(output_str.ends_with("\n````\n"));
        // Must contain file content
        assert!(output_str.contains("## File: test_main.rs"));
        assert!(output_str.contains("fn main() {}"));
    }

    #[test]
    fn multiple_files_are_enveloped_using_only_one_outer_fence_while_each_having_its_own_fence() {
        let temp_dir = tempdir().unwrap();
        let origin_base = temp_dir.path();
        let input = Cursor::new(b"a.rs\0b.rs\0");
        let mut output = Vec::new();
        let root = temp_dir.path();

        fs::write(origin_base.join("a.rs"), "A").unwrap();
        fs::write(origin_base.join("b.rs"), "B").unwrap();

        wrap_with_defaults(input, &mut output, root, origin_base, None).unwrap();

        let output_str = String::from_utf8(output).unwrap();
        // Each inner file must have its own language‑specific fence
        assert!(output_str.contains("```rust\nA\n```"));
        assert!(output_str.contains("```rust\nB\n```"));
        // Only one outer fence at start and end
        assert_eq!(output_str.matches("````markdown").count(), 1);
        assert_eq!(output_str.matches("\n````\n").count(), 1);
    }

    #[test]
    fn inner_file_with_four_backticks_causes_outer_fence_of_six_backticks() {
        let temp_dir = tempdir().unwrap();
        let origin_base = temp_dir.path();
        let input = Cursor::new(b"backticks.rs\0");
        let mut output = Vec::new();
        let root = temp_dir.path();

        let content = "````"; // 4 backticks
        fs::write(origin_base.join("backticks.rs"), content).unwrap();

        wrap_with_defaults(input, &mut output, root, origin_base, None).unwrap();

        let output_str = String::from_utf8(output).unwrap();
        // Inner fence should be 5 backticks because inner content contains 4 backticks
        assert!(output_str.contains("`````rust\n````\n`````"));
        // Outer fence must then be 6 backticks
        assert!(output_str.starts_with("``````markdown\n"));
        assert!(output_str.ends_with("\n``````\n"));
    }

    #[test]
    fn project_title_with_three_backticks_causes_outer_fence_of_four_backticks() {
        let temp_dir = tempdir().unwrap();
        let input = Cursor::new(b"");
        let mut output = Vec::new();
        let root = temp_dir.path();
        let origin_base = temp_dir.path();

        let project_title = "Project ``` name";
        wrap_with_defaults(input, &mut output, root, origin_base, Some(project_title)).unwrap();

        let output_str = String::from_utf8(output).unwrap();
        // Outer fence must be 4 backticks because the header contains 3 backticks
        assert!(output_str.starts_with("````markdown\n"));
        assert!(output_str.ends_with("\n````\n"));
        // The header line itself should appear unchanged
        assert!(output_str.contains("# Project ``` name"));
    }
}
