use anyhow::{Result, bail};
use std::fs;
use std::path::{Path, PathBuf};

use crate::cli::InitStrategy;
use crate::config::{GlobalConfig, ensure_safe_path_component};
use crate::parser::ConfigPaths;

pub struct InitResult {
    pub created: Vec<PathBuf>,
    pub skipped: Vec<PathBuf>,
}

pub fn run(
    paths: &ConfigPaths,
    create_global: bool,
    app_name: Option<&str>,
    strategy: InitStrategy,
    force: bool,
) -> Result<InitResult> {
    let should_create_global = create_global || app_name.is_none();
    let mut created = Vec::new();
    let mut skipped = Vec::new();

    if should_create_global {
        let global_path = paths.global_config_path();
        let serialized = serde_yaml::to_string(&GlobalConfig::default())?;
        let content = format!(
            "# yaml-language-server: $schema=https://fau.fpt.icu/schema-global.json\n\n{}",
            serialized
        );
        write_file(&global_path, &content, force, &mut created, &mut skipped)?;
    }

    if let Some(app_name) = app_name {
        ensure_safe_path_component(app_name, "app name")?;
        let app_path = paths.apps_dir().join(format!("{app_name}.yml"));
        let content = app_template(app_name, strategy);
        write_file(&app_path, &content, force, &mut created, &mut skipped)?;

        if matches!(strategy, InitStrategy::Script) {
            let script_dir = paths.apps_dir().join(app_name);
            let script_path = script_dir.join("resolver.sh");
            let script_content = script_template();
            write_file(
                &script_path,
                script_content,
                force,
                &mut created,
                &mut skipped,
            )?;
            #[cfg(unix)]
            {
                use std::os::unix::fs::PermissionsExt;
                let perms = fs::Permissions::from_mode(0o755);
                let _ = fs::set_permissions(script_path, perms);
            }
        }
    }

    Ok(InitResult { created, skipped })
}

fn write_file(
    path: &Path,
    content: &str,
    force: bool,
    created: &mut Vec<PathBuf>,
    skipped: &mut Vec<PathBuf>,
) -> Result<()> {
    if path.exists() && !force {
        skipped.push(path.to_path_buf());
        return Ok(());
    }

    let parent = path
        .parent()
        .ok_or_else(|| anyhow::anyhow!("Invalid path: {}", path.display()))?;
    fs::create_dir_all(parent)?;

    if path.exists() && force && path.is_dir() {
        bail!("Cannot overwrite directory with file: {}", path.display());
    }

    fs::write(path, content)?;
    created.push(path.to_path_buf());
    Ok(())
}

fn app_template(name: &str, strategy: InitStrategy) -> String {
    let header = "# yaml-language-server: $schema=https://fau.fpt.icu/schema-recipe.json\n\n";
    let body = match strategy {
        InitStrategy::Direct => format!(
            "name: {name}\nstrategy:\n  strategy: direct\n  url: \"https://example.org/{name}.AppImage\"\n  check_method: etag\n"
        ),
        InitStrategy::Forge => format!(
            "name: {name}\nstrategy:\n  strategy: forge\n  repository: \"https://github.com/OWNER/REPO\"\n  asset_match: \"*-x86_64.AppImage\"\n  # Optional: use a regex when release names are ambiguous.\n  # asset_match_regex: \"^Obsidian-[0-9.]+\\\\.AppImage$\"\n"
        ),
        InitStrategy::Script => format!(
            "name: {name}\nstrategy:\n  strategy: script\n  script_path: ./{name}/resolver.sh\n"
        ),
    };
    format!("{}{}", header, body)
}

fn script_template() -> &'static str {
    "#!/usr/bin/env bash\nset -euo pipefail\n\necho \"https://example.org/my-app.AppImage\"\necho \"1.0.0\"\n"
}
