use crate::adapter::{AlacrittyAdapter, BatAdapter, GhosttyAdapter, StarshipAdapter};
use crate::cli::theme_apply;
use crate::config::ConfigManager;
use crate::detection;
use crate::env::SlateEnv;
use crate::error::{Result, SlateError};
use crate::theme::{ThemeRegistry, ThemeVariant};
use std::fs;
use std::path::Path;

pub(crate) fn resolve_selected_theme(theme: Option<&str>, env: &SlateEnv) -> Result<ThemeVariant> {
    let config_mgr = ConfigManager::with_env(env)?;
    let theme_id = if let Some(theme_name) = theme {
        theme_name.to_string()
    } else if let Some(current_theme) = config_mgr.get_current_theme()? {
        current_theme
    } else {
        "catppuccin-mocha".to_string()
    };

    let registry = ThemeRegistry::new()?;
    registry.get(&theme_id).cloned().ok_or_else(|| {
        crate::error::SlateError::InvalidThemeData(format!("Theme '{}' not found", theme_id))
    })
}

fn shell_source_block(path: &std::path::Path) -> String {
    let quoted_path = detection::shell_quote_path(path);

    format!(
        "{}\nif [ -f {path} ]; then\n  source {path}\nfi\n{}\n",
        crate::adapter::marker_block::START,
        crate::adapter::marker_block::END,
        path = quoted_path
    )
}

fn setup_fish_loader(env: &SlateEnv) -> Result<()> {
    let env_fish_path = env.config_dir().join("managed/shell/env.fish");
    let env_fish_shell_path = detection::shell_quote_path(&env_fish_path);
    let loader_path = env.fish_loader_path();

    if let Some(parent) = loader_path.parent() {
        fs::create_dir_all(parent)?;
    }

    fs::write(
        loader_path,
        format!(
            "# Generated by slate - managed loader\nif test -f {path}\n  source {path}\nend\n",
            path = env_fish_shell_path
        ),
    )?;

    Ok(())
}

/// Setup shell integration: generate env files, then wire the detected shell loader.
pub(crate) fn setup_shell_integration_with_env(
    theme: Option<&str>,
    env: &SlateEnv,
    tools_to_configure: &[String],
) -> Result<(ThemeVariant, theme_apply::ThemeApplyReport)> {
    use crate::adapter::marker_block;

    let selected_theme = resolve_selected_theme(theme, env)?;

    // Write the shared shell env files BEFORE running the tool-apply pass. On a bare host
    // (CI, fresh box) the tool-apply may Err out or skip entirely, and the marker block
    // we're about to upsert below references these files — missing them breaks every
    // future shell startup. Do this early so even a downstream failure can't leave the
    // loader pointing at nothing.
    let config = crate::config::ConfigManager::with_env(env)?;
    config.write_shell_integration_file(&selected_theme)?;

    let report = theme_apply::apply_theme_selection_for_tools_with_env(
        &selected_theme,
        env,
        Some(tools_to_configure),
    )?;

    match crate::platform::shell::detect_backend() {
        crate::platform::shell::ShellBackend::Zsh => {
            marker_block::upsert_managed_block_file(
                &env.zshrc_path(),
                &shell_source_block(&env.config_dir().join("managed/shell/env.zsh")),
            )?;
        }
        crate::platform::shell::ShellBackend::Bash => {
            marker_block::upsert_managed_block_file(
                &env.bash_integration_path(),
                &shell_source_block(&env.config_dir().join("managed/shell/env.bash")),
            )?;
        }
        crate::platform::shell::ShellBackend::Fish => {
            setup_fish_loader(env)?;
        }
        crate::platform::shell::ShellBackend::Unsupported => {
            return Err(SlateError::PlatformError(
                "Slate shell integration currently targets zsh, bash, and fish only.".to_string(),
            ));
        }
    }

    Ok((selected_theme, report))
}

#[allow(dead_code)]
pub(crate) fn setup_shell_integration(
    theme: Option<&str>,
) -> Result<(ThemeVariant, theme_apply::ThemeApplyReport)> {
    let env = SlateEnv::from_process()?;
    setup_shell_integration_with_env(theme, &env, &[])
}

/// Ensure integration config files exist for detected tools so adapters can write to them.
pub(crate) fn ensure_tool_configs(
    env: &SlateEnv,
    user_selected: &[String],
    just_installed: &[String],
) -> Vec<String> {
    use std::fs;

    fn touch_config(tool_id: &str, path: &Path, issues: &mut Vec<String>) {
        if path.exists() {
            return;
        }

        if let Some(parent) = path.parent() {
            if let Err(err) = fs::create_dir_all(parent) {
                issues.push(format!(
                    "Could not create {} config directory at {}: {}",
                    tool_id,
                    parent.display(),
                    err
                ));
                return;
            }
        }

        let comment = match tool_id {
            "ghostty" => "# Ghostty configuration — managed by ~/.config/slate/managed/ghostty/\n",
            "alacritty" => "# Alacritty configuration — managed imports in general.import\n",
            "kitty" => "# Kitty configuration — managed imports\n",
            "bat" => "# bat configuration — managed imports\n",
            "delta" => "# git configuration — managed imports\n",
            _ => "# Slate configuration\n",
        };

        if let Err(err) = fs::write(path, comment) {
            issues.push(format!(
                "Could not initialize {} config file at {}: {}",
                tool_id,
                path.display(),
                err
            ));
        }
    }

    fn seed_starship_config(path: &Path, issues: &mut Vec<String>) {
        let needs_seed = match fs::read(path) {
            Ok(content) => match String::from_utf8(content) {
                Ok(content) => {
                    crate::config::shell_integration::should_upgrade_seeded_starship_content(
                        &content,
                    )
                }
                Err(_) => {
                    issues.push(format!(
                        "Could not inspect starship config at {}: file is not valid UTF-8",
                        path.display()
                    ));
                    false
                }
            },
            Err(err) => {
                issues.push(format!(
                    "Could not inspect starship config at {}: {}",
                    path.display(),
                    err
                ));
                false
            }
        };

        if needs_seed {
            if let Err(err) = fs::write(
                path,
                crate::config::shell_integration::starter_starship_content(),
            ) {
                issues.push(format!(
                    "Could not seed starship config at {}: {}",
                    path.display(),
                    err
                ));
            }
        }
    }

    let mut installed = crate::cli::tool_selection::detect_installed_tools_with_env(env);
    for tool_id in just_installed {
        installed.insert(
            tool_id.clone(),
            crate::detection::ToolPresence {
                installed: true,
                in_path: true,
                evidence: None,
            },
        );
    }
    let mut issues = Vec::new();

    let user_set: std::collections::HashSet<&str> = user_selected
        .iter()
        .chain(just_installed.iter())
        .map(|selection| selection.as_str())
        .collect();
    // Policy: terminal adapters that are already installed as the user's primary terminal
    // (tier1) are configured automatically even when the user didn't explicitly tick them in
    // the wizard — the starter kit's value proposition is a one-shot "my terminal looks good",
    // and asking users to re-select their current terminal every setup is friction that fails
    // the 30-second promise. Non-terminal tools still require explicit user selection.
    let should_configure = |id: &str| -> bool {
        let presence = installed.get(id);
        let is_detected = presence
            .map(|tool_presence| tool_presence.installed)
            .unwrap_or(false);
        if !is_detected {
            return false;
        }
        if id == "ghostty" || id == "alacritty" || id == "kitty" {
            return presence
                .map(|tool_presence| tool_presence.is_tier1())
                .unwrap_or(false)
                || user_set.contains(id);
        }
        user_set.contains(id)
    };

    if should_configure("ghostty") {
        let adapter = GhosttyAdapter;
        match adapter.integration_config_path_with_env(env) {
            Ok(path) => touch_config("ghostty", &path, &mut issues),
            Err(err) => issues.push(format!("Could not resolve ghostty config path: {}", err)),
        }
    }
    if should_configure("starship") {
        let path = StarshipAdapter::integration_config_path_with_env(env);
        touch_config("starship", &path, &mut issues);
        if path.exists() {
            seed_starship_config(&path, &mut issues);
        }
    }
    if should_configure("alacritty") {
        touch_config(
            "alacritty",
            &AlacrittyAdapter::integration_config_path_with_env(env),
            &mut issues,
        );
    }
    if should_configure("kitty") {
        touch_config(
            "kitty",
            &crate::adapter::kitty::KittyAdapter::resolve_config_path_with_env(env),
            &mut issues,
        );
    }
    if should_configure("bat") {
        let adapter = BatAdapter;
        match adapter.integration_config_path_with_env(env) {
            Ok(path) => touch_config("bat", &path, &mut issues),
            Err(err) => issues.push(format!("Could not resolve bat config path: {}", err)),
        }
    }
    if should_configure("delta") {
        touch_config("delta", &env.home().join(".gitconfig"), &mut issues);
    }

    issues
}

pub(crate) fn theme_apply_issues(results: &[crate::adapter::ToolApplyResult]) -> Vec<String> {
    results
        .iter()
        .filter_map(|result| match &result.status {
            crate::adapter::ToolApplyStatus::Skipped(
                crate::adapter::SkipReason::MissingIntegrationConfig,
            ) => Some(format!(
                "{} is installed, but slate could not initialize its integration file.",
                result.tool_name
            )),
            crate::adapter::ToolApplyStatus::Failed(err) => Some(format!(
                "{} failed during theme apply: {}",
                result.tool_name, err
            )),
            crate::adapter::ToolApplyStatus::Skipped(crate::adapter::SkipReason::NotInstalled)
            | crate::adapter::ToolApplyStatus::Applied => None,
        })
        .collect()
}
