use anyhow::{Context, Result};
use console::style;
use dialoguer::{theme::ColorfulTheme, Confirm, Input};
use std::path::{Path, PathBuf};

/// JSON schema version for the `templates create` envelope. See lanes.rs for
/// the per-command rationale.
const CREATE_TEMPLATE_SCHEMA: u32 = 1;

pub struct CreateTemplateOptions {
    pub name: String,
    pub output: PathBuf,
    pub description: Option<String>,
    pub render_patterns: Option<String>,
    pub hooks: Option<bool>,
    pub prompts: Option<bool>,
    pub yes: bool,
    pub json: bool,
}

struct TemplateAnswers {
    name: String,
    description: String,
    render_globs: Vec<String>,
    include_hooks: bool,
    include_prompts: bool,
}

pub fn run(mut options: CreateTemplateOptions) -> Result<()> {
    if crate::utils::is_non_interactive() || options.json {
        options.yes = true;
    }
    let target = options.output.join(&options.name);

    if target.exists() {
        anyhow::bail!("Directory '{}' already exists", target.display());
    }

    let all_provided = options.description.is_some()
        && options.render_patterns.is_some()
        && options.hooks.is_some()
        && options.prompts.is_some();

    let answers = if options.yes || all_provided || !crate::utils::is_interactive() {
        build_answers_from_flags(&options)
    } else {
        gather_answers(&options)?
    };
    scaffold(&target, &answers)?;

    if options.json {
        let result = serde_json::json!({
            "schema_version": CREATE_TEMPLATE_SCHEMA,
            "action": "create",
            "path": target.display().to_string(),
            "name": answers.name,
            "description": answers.description,
            "render_patterns": answers.render_globs,
            "include_hooks": answers.include_hooks,
            "include_prompts": answers.include_prompts,
            "files_created": [
                "template.toml",
                "README.md",
                "README.md.tera",
                ".gitignore",
            ],
        });
        println!("{}", serde_json::to_string_pretty(&result)?);
    } else {
        println!(
            "\n{} Created template at {}",
            style("✅").green().bold(),
            style(target.display()).cyan()
        );
        println!(
            "\n  {} Edit files in {}/",
            style("1.").dim(),
            style(&answers.name).green()
        );
        println!(
            "  {} Add .tera extension to files that need variable substitution",
            style("2.").dim()
        );
        println!(
            "  {} Test locally with: {}",
            style("3.").dim(),
            style(format!("fledge init my-project -t ./{}", answers.name)).cyan()
        );
    }

    Ok(())
}

fn build_answers_from_flags(options: &CreateTemplateOptions) -> TemplateAnswers {
    let name = options.name.clone();
    let description = options
        .description
        .clone()
        .unwrap_or_else(|| format!("A {} project template", name));
    let render_input = options
        .render_patterns
        .clone()
        .unwrap_or_else(|| "**/*.md, **/*.toml, **/*.json, **/*.yml".to_string());
    let render_globs: Vec<String> = render_input
        .split(',')
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty())
        .collect();

    TemplateAnswers {
        name,
        description,
        render_globs,
        include_hooks: options.hooks.unwrap_or(false),
        include_prompts: options.prompts.unwrap_or(true),
    }
}

fn gather_answers(options: &CreateTemplateOptions) -> Result<TemplateAnswers> {
    let theme = ColorfulTheme::default();

    let name: String = Input::with_theme(&theme)
        .with_prompt("Template name")
        .default(options.name.clone())
        .interact_text()?;

    let desc_default = options
        .description
        .clone()
        .unwrap_or_else(|| format!("A {} project template", name));
    let description: String = Input::with_theme(&theme)
        .with_prompt("Description")
        .default(desc_default)
        .interact_text()?;

    let render_default = options
        .render_patterns
        .clone()
        .unwrap_or_else(|| "**/*.md, **/*.toml, **/*.json, **/*.yml".to_string());
    let render_input: String = Input::with_theme(&theme)
        .with_prompt("File patterns to render through Tera (comma-separated)")
        .default(render_default)
        .interact_text()?;

    let render_globs: Vec<String> = render_input
        .split(',')
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty())
        .collect();

    let include_hooks = Confirm::with_theme(&theme)
        .with_prompt("Include post-create hooks?")
        .default(options.hooks.unwrap_or(false))
        .interact()?;

    let include_prompts = Confirm::with_theme(&theme)
        .with_prompt("Include custom prompts?")
        .default(options.prompts.unwrap_or(true))
        .interact()?;

    Ok(TemplateAnswers {
        name,
        description,
        render_globs,
        include_hooks,
        include_prompts,
    })
}

fn scaffold(target: &Path, answers: &TemplateAnswers) -> Result<()> {
    std::fs::create_dir_all(target).with_context(|| format!("creating {}", target.display()))?;

    write_manifest(target, answers)?;
    write_example_files(target)?;
    write_readme(target, &answers.name)?;

    Ok(())
}

fn write_manifest(target: &Path, answers: &TemplateAnswers) -> Result<()> {
    let mut manifest = String::new();

    manifest.push_str("[template]\n");
    manifest.push_str(&format!("name = {:?}\n", answers.name));
    manifest.push_str(&format!("description = {:?}\n", answers.description));
    manifest.push_str("# min_fledge_version = \"0.2.0\"\n");

    if answers.include_prompts {
        manifest.push_str("\n[prompts.description]\n");
        manifest.push_str("message = \"Project description\"\n");
        manifest.push_str(&format!("default = \"A new {} project\"\n", answers.name));
        manifest.push_str("\n# Add more prompts:\n");
        manifest.push_str("# [prompts.database]\n");
        manifest.push_str("# message = \"Database engine\"\n");
        manifest.push_str("# default = \"sqlite\"\n");
    }

    manifest.push_str("\n[files]\n");

    let render_arr: Vec<String> = answers
        .render_globs
        .iter()
        .map(|g| format!("{:?}", g))
        .collect();
    manifest.push_str(&format!("render = [{}]\n", render_arr.join(", ")));
    manifest.push_str("copy = [\"**/*.png\", \"**/*.ico\", \"**/*.woff2\"]\n");
    manifest.push_str("ignore = [\"template.toml\"]\n");

    if answers.include_hooks {
        manifest.push_str("\n[hooks]\n");
        manifest.push_str("post_create = [\n");
        manifest.push_str("    # \"git init\",\n");
        manifest.push_str("    # \"npm install\",   # Node\n");
        manifest.push_str("    # \"pip install -e .\",  # Python\n");
        manifest.push_str("    # \"go mod tidy\",   # Go\n");
        manifest.push_str("]\n");
    }

    std::fs::write(target.join("template.toml"), manifest).context("writing template.toml")?;

    Ok(())
}

fn write_example_files(target: &Path) -> Result<()> {
    std::fs::create_dir_all(target.join("src"))?;

    std::fs::write(
        target.join("README.md.tera"),
        r#"# {{ project_name }}

{{ description }}

## Getting Started

TODO: Add setup instructions here.

## License

{{ license }}
"#,
    )?;

    std::fs::write(
        target.join(".gitignore"),
        r#"# Build artifacts
/target/
/dist/
/build/
/out/

# Dependencies
node_modules/
vendor/
__pycache__/
*.pyc

# IDE
.idea/
.vscode/
*.swp

# OS
.DS_Store
Thumbs.db
"#,
    )?;

    Ok(())
}

fn write_readme(target: &Path, name: &str) -> Result<()> {
    std::fs::write(
        target.join("README.md"),
        format!(
            r#"# {name} — fledge template

A project template for [fledge](https://github.com/CorvidLabs/fledge).

## Usage

Test this template locally:

```bash
fledge init my-project -t ./{name}
```

## Template structure

- `template.toml` — Template manifest (name, prompts, file rules, hooks)
- Files with `.tera` extension are rendered through Tera and the extension is stripped
- Files matching `render` globs in template.toml are also rendered through Tera
- Files matching `ignore` globs are not included in generated projects

## Template variables

These variables are available in all rendered files:

| Variable | Description |
|----------|-------------|
| `{{{{ project_name }}}}` | Project name as provided by the user |
| `{{{{ project_name_snake }}}}` | snake_case version |
| `{{{{ project_name_kebab }}}}` | kebab-case version |
| `{{{{ project_name_pascal }}}}` | PascalCase version |
| `{{{{ project_name_camel }}}}` | camelCase version |
| `{{{{ author }}}}` | Author name |
| `{{{{ github_org }}}}` | GitHub organization |
| `{{{{ license }}}}` | License identifier |
| `{{{{ year }}}}` | Current year |
| `{{{{ date }}}}` | Current date (YYYY-MM-DD) |
| `{{{{ description }}}}` | Project description (if prompt defined) |

Custom prompts defined in `template.toml` also become available as variables.
"#,
        ),
    )
    .context("writing README.md")?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn scaffold_creates_expected_files() {
        let tmp = TempDir::new().unwrap();
        let target = tmp.path().join("my-template");

        let answers = TemplateAnswers {
            name: "my-template".to_string(),
            description: "A test template".to_string(),
            render_globs: vec!["**/*.md".to_string(), "**/*.toml".to_string()],
            include_hooks: false,
            include_prompts: true,
        };

        scaffold(&target, &answers).unwrap();

        assert!(target.join("template.toml").exists());
        assert!(target.join("README.md").exists());
        assert!(target.join("README.md.tera").exists());
        assert!(target.join(".gitignore").exists());
    }

    #[test]
    fn scaffold_manifest_is_valid_toml() {
        let tmp = TempDir::new().unwrap();
        let target = tmp.path().join("test-tpl");

        let answers = TemplateAnswers {
            name: "test-tpl".to_string(),
            description: "Test".to_string(),
            render_globs: vec!["**/*.rs".to_string()],
            include_hooks: true,
            include_prompts: true,
        };

        scaffold(&target, &answers).unwrap();

        let content = std::fs::read_to_string(target.join("template.toml")).unwrap();
        let manifest: Result<crate::templates::TemplateManifest, _> = toml::from_str(&content);
        assert!(
            manifest.is_ok(),
            "Generated template.toml should be valid: {:?}",
            manifest.err()
        );
    }

    #[test]
    fn scaffold_manifest_without_hooks_or_prompts() {
        let tmp = TempDir::new().unwrap();
        let target = tmp.path().join("bare-tpl");

        let answers = TemplateAnswers {
            name: "bare-tpl".to_string(),
            description: "Bare template".to_string(),
            render_globs: vec!["**/*.txt".to_string()],
            include_hooks: false,
            include_prompts: false,
        };

        scaffold(&target, &answers).unwrap();

        let content = std::fs::read_to_string(target.join("template.toml")).unwrap();
        assert!(!content.contains("[hooks]"));
        assert!(!content.contains("[prompts"));

        let manifest: Result<crate::templates::TemplateManifest, _> = toml::from_str(&content);
        assert!(manifest.is_ok());
    }

    #[test]
    fn scaffold_fails_if_target_exists() {
        let tmp = TempDir::new().unwrap();
        let target = tmp.path().join("existing");
        std::fs::create_dir(&target).unwrap();

        let options = CreateTemplateOptions {
            name: "existing".to_string(),
            output: tmp.path().to_path_buf(),
            description: None,
            render_patterns: None,
            hooks: None,
            prompts: None,
            yes: false,
            json: false,
        };

        let result = run(options);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("already exists"));
    }

    #[test]
    fn manifest_render_globs_are_correct() {
        let tmp = TempDir::new().unwrap();
        let target = tmp.path().join("glob-tpl");

        let answers = TemplateAnswers {
            name: "glob-tpl".to_string(),
            description: "Test".to_string(),
            render_globs: vec![
                "**/*.rs".to_string(),
                "**/*.toml".to_string(),
                "**/*.md".to_string(),
            ],
            include_hooks: false,
            include_prompts: false,
        };

        scaffold(&target, &answers).unwrap();

        let content = std::fs::read_to_string(target.join("template.toml")).unwrap();
        let manifest: crate::templates::TemplateManifest = toml::from_str(&content).unwrap();
        assert_eq!(
            manifest.files.render,
            vec!["**/*.rs", "**/*.toml", "**/*.md"]
        );
    }
}
