use std::path::Path;

use anyhow::{Context, Result};
use tera::Tera;

/// Renders `template_name` against `ctx` and writes the result to
/// `out_path`, creating parent directories as needed. Every generation step
/// (Stories 8-13) funnels through this one helper, so template/IO error
/// context is centralized in one place instead of repeated at each call site.
pub async fn render_and_write(
    tera: &Tera,
    template_name: &str,
    ctx: &tera::Context,
    out_path: &Path,
) -> Result<()> {
    let rendered = tera.render(template_name, ctx).with_context(|| {
        format!(
            "failed to render template '{template_name}' for '{}'",
            out_path.display()
        )
    })?;

    if let Some(parent) = out_path.parent() {
        tokio::fs::create_dir_all(parent)
            .await
            .with_context(|| format!("failed to create directory '{}'", parent.display()))?;
    }

    tokio::fs::write(out_path, rendered)
        .await
        .with_context(|| format!("failed to write '{}'", out_path.display()))
}

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

    use super::*;
    use crate::context::GeneratorContext;
    use crate::targets::typescript::context::TsTemplateContext;
    use crate::targets::typescript::render::render_engine;

    fn sample_ctx(output_dir: PathBuf, api_title: &str) -> GeneratorContext {
        GeneratorContext {
            publish_registry: false,
            openapi_input: "spec.yaml".to_string(),
            output_dir,
            force: false,
            output_dir_preexisted: false,
            auth_schemes: Vec::new(),
            normalized_operations: Vec::new(),
            api_title: api_title.to_string(),
            version_label: "default".to_string(),
        }
    }

    #[tokio::test]
    async fn renders_and_writes_package_json() {
        let tera = render_engine().unwrap();
        let view =
            TsTemplateContext::from_context(&sample_ctx(PathBuf::from("my-api-mcp"), "My API"));
        let ctx = tera::Context::from_serialize(&view).unwrap();

        let dir = tempfile::tempdir().unwrap();
        let out_path = dir.path().join("package.json");

        render_and_write(&tera, "package.json.tera", &ctx, &out_path)
            .await
            .unwrap();

        let contents = tokio::fs::read_to_string(&out_path).await.unwrap();
        let parsed: serde_json::Value =
            serde_json::from_str(&contents).expect("rendered output must be valid JSON");
        assert_eq!(parsed["name"], "my-api-mcp");
        assert_eq!(
            parsed["description"],
            "My API MCP server, generated by mcpify."
        );
    }

    #[tokio::test]
    async fn creates_parent_directories() {
        let tera = render_engine().unwrap();
        let view = TsTemplateContext::from_context(&sample_ctx(PathBuf::from("x"), "X"));
        let ctx = tera::Context::from_serialize(&view).unwrap();

        let dir = tempfile::tempdir().unwrap();
        let out_path = dir.path().join("nested").join("dir").join("package.json");

        render_and_write(&tera, "package.json.tera", &ctx, &out_path)
            .await
            .unwrap();

        assert!(out_path.exists());
    }
}
