use std::collections::HashMap;
use std::fs;
use std::io::IsTerminal;
use std::io::Write;
use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use chrono::Utc;
use dialoguer::{theme::ColorfulTheme, Confirm, Input};

use crate::auth;
use crate::config::{AuthMethod, ConfigStore, CredentialBackend, ProfileMeta};
use crate::output;
use crate::profile::{validate_profile_name, ProfileStore};
use crate::runtime;
use crate::tool_detection::{self, DetectedTool};
use crate::types::Tool;

// Marker written by shell_hook.rs — must match.
pub(crate) const HOOK_MARKER: &str = "# Added by aisw";

pub(crate) fn run_inner(
    aisw_home: &Path,
    user_home: &Path,
    shell_env: Option<&str>,
    confirmed: bool,
) -> Result<()> {
    // Ensure ~/.aisw/ exists with a default config.json.
    fs::create_dir_all(aisw_home)
        .with_context(|| format!("could not create {}", aisw_home.display()))?;
    let config_store = ConfigStore::new(aisw_home);
    config_store.load()?; // creates config.json with defaults if absent
    output::print_title("Initialize aisw");
    output::print_kv("Home", aisw_home.display().to_string());
    output::print_blank_line();

    let detected_tools = detect_supported_tools();

    // Shell hook installation.
    let shell_name = shell_env
        .and_then(|s| Path::new(s).file_name())
        .and_then(|n| n.to_str());
    output::print_section("Shell integration");
    output::print_kv("Shell", shell_name.unwrap_or("unknown"));
    match shell_name {
        Some(s @ ("bash" | "zsh" | "fish")) => {
            install_shell_hook(user_home, s, confirmed)?;
        }
        Some(name) => {
            output::print_warning(format!(
                "Shell not recognized ({}). Install the hook manually: \
                 aisw shell-hook bash >> ~/.bashrc",
                name
            ));
        }
        None => {
            output::print_warning(
                "Could not detect shell. Install the hook manually: \
                 aisw shell-hook bash >> ~/.bashrc",
            );
        }
    }
    output::print_blank_line();

    print_detected_tools(&detected_tools);

    // Credential import.
    import_credentials(aisw_home, user_home, &detected_tools, confirmed)?;

    output::print_title("Setup complete");
    output::print_next_step(output::next_step_after_init());
    Ok(())
}

fn detect_supported_tools() -> HashMap<Tool, Option<DetectedTool>> {
    Tool::ALL
        .into_iter()
        .map(|tool| (tool, tool_detection::detect(tool)))
        .collect()
}

fn print_detected_tools(detected: &HashMap<Tool, Option<DetectedTool>>) {
    output::print_section("Detected tools");
    output::print_info("aisw checked your PATH for supported coding agent CLIs.");
    output::print_blank_line();

    for tool in Tool::ALL {
        output::print_tool_section(tool);
        print_detection_metadata(detected.get(&tool).and_then(|entry| entry.as_ref()));
        output::print_blank_line();
    }
}

pub(crate) fn rc_file(user_home: &Path, shell: &str) -> PathBuf {
    match shell {
        "bash" => {
            if cfg!(target_os = "macos") {
                user_home.join(".bash_profile")
            } else {
                user_home.join(".bashrc")
            }
        }
        "zsh" => user_home.join(".zshrc"),
        "fish" => user_home.join(".config").join("fish").join("config.fish"),
        _ => unreachable!(),
    }
}

fn install_shell_hook(user_home: &Path, shell: &str, confirmed: bool) -> Result<()> {
    let rc = rc_file(user_home, shell);

    if rc.exists() {
        let contents =
            fs::read_to_string(&rc).with_context(|| format!("could not read {}", rc.display()))?;
        if contents.contains(HOOK_MARKER) {
            output::print_info(format!("Shell hook already installed in {}.", rc.display()));
            return Ok(());
        }
    }

    let should_install = confirmed
        || prompt_yes_no(&format!(
            "Shell: {}\nAdd shell integration to {}? [Y/n] ",
            shell,
            rc.display()
        ));

    if !should_install {
        output::print_info("Skipping shell hook installation.");
        return Ok(());
    }

    if let Some(parent) = rc.parent() {
        fs::create_dir_all(parent)
            .with_context(|| format!("could not create {}", parent.display()))?;
    }

    let hook_line = match shell {
        "bash" | "zsh" => format!("\n{}\neval \"$(aisw shell-hook {})\"\n", HOOK_MARKER, shell),
        "fish" => format!("\n{}\naisw shell-hook fish | source\n", HOOK_MARKER),
        _ => unreachable!(),
    };

    let mut file = fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open(&rc)
        .with_context(|| format!("could not open {}", rc.display()))?;
    file.write_all(hook_line.as_bytes())
        .with_context(|| format!("could not write to {}", rc.display()))?;

    output::print_info(format!(
        "Appended to {}. Restart your shell or run: source {}",
        rc.display(),
        rc.display()
    ));
    Ok(())
}

fn prompt_yes_no(prompt: &str) -> bool {
    if runtime::is_non_interactive() {
        return false;
    }
    if !std::io::stdin().is_terminal() {
        eprint!("{}", prompt);
        let mut line = String::new();
        std::io::stdin().read_line(&mut line).unwrap_or(0);
        return matches!(line.trim(), "" | "y" | "Y");
    }
    Confirm::with_theme(&ColorfulTheme::default())
        .with_prompt(prompt.trim())
        .default(true)
        .interact_opt()
        .unwrap_or(None)
        .unwrap_or(false)
}

fn prompt_line(prompt: &str) -> String {
    if runtime::is_non_interactive() {
        return String::new();
    }
    if !std::io::stdin().is_terminal() {
        eprint!("{}", prompt);
        let mut line = String::new();
        std::io::stdin().read_line(&mut line).unwrap_or(0);
        return line.trim().to_owned();
    }
    let (message, default) = parse_prompt(prompt);
    let theme = ColorfulTheme::default();
    let input = Input::<String>::with_theme(&theme).with_prompt(message);
    let input = if let Some(default) = default {
        input.default(default.to_owned())
    } else {
        input
    };
    input.interact_text().unwrap_or_default()
}

fn parse_prompt(prompt: &str) -> (&str, Option<&str>) {
    let trimmed = prompt.trim();
    let Some(start) = trimmed.rfind('[') else {
        return (trimmed, None);
    };
    let Some(end) = trimmed[start..].find(']') else {
        return (trimmed, None);
    };
    let end = start + end;
    let message = trimmed[..start].trim_end_matches(':').trim();
    let default = trimmed[start + 1..end].trim();
    if message.is_empty() || default.is_empty() {
        (trimmed, None)
    } else {
        (message, Some(default))
    }
}

fn import_name_and_label(
    tool: Tool,
    profile_store: &ProfileStore,
    confirmed: bool,
) -> Result<Option<(String, Option<String>)>> {
    if confirmed {
        return Ok(Some(("default".to_owned(), Some("imported".to_owned()))));
    }

    if !prompt_yes_no("  Import these credentials into aisw? [Y/n] ") {
        return Ok(None);
    }

    loop {
        let profile_name = prompt_line("  Profile name [default]: ");
        let profile_name = if profile_name.is_empty() {
            "default".to_owned()
        } else {
            profile_name
        };

        if profile_name.eq_ignore_ascii_case("skip") {
            output::print_info("Skipping import and keeping existing aisw profiles unchanged.");
            return Ok(None);
        }

        if let Err(err) = validate_profile_name(&profile_name) {
            output::print_warning_stderr(format!("Invalid profile name: {}", err));
            continue;
        }
        if profile_store.exists(tool, &profile_name) {
            let existing = profile_store.list_profiles(tool)?;
            if !existing.is_empty() {
                output::print_info_stderr(format!(
                    "Existing {} profiles: {}",
                    tool,
                    existing.join(", ")
                ));
            }
            output::print_warning_stderr(format!(
                "Profile '{}' already exists for {}. Choose a different name or type 'skip' to cancel import.",
                profile_name, tool
            ));
            continue;
        }

        let label = prompt_line("  Label [imported]: ");
        let label = if label.is_empty() {
            Some("imported".to_owned())
        } else {
            Some(label)
        };

        return Ok(Some((profile_name, label)));
    }
}

fn should_mark_import_active(config_store: &ConfigStore, tool: Tool) -> Result<bool> {
    let config = config_store.load()?;
    Ok(config_store.get_active(&config, tool).is_none())
}

fn activate_imported_profile(
    tool: Tool,
    config_store: &ConfigStore,
    profile_name: &str,
) -> Result<()> {
    config_store.set_active(tool, profile_name)?;
    Ok(())
}

fn import_credentials(
    aisw_home: &Path,
    user_home: &Path,
    detected: &HashMap<Tool, Option<DetectedTool>>,
    confirmed: bool,
) -> Result<()> {
    output::print_section("Credential onboarding");
    output::print_info(
        "Each tool is checked for the current live upstream credentials that can be imported into aisw.",
    );
    output::print_info(
        "The live account shown here is whatever the upstream tool is using right now and may have changed outside aisw.",
    );
    output::print_blank_line();
    import_claude(
        aisw_home,
        user_home,
        detected.get(&Tool::Claude).and_then(|entry| entry.as_ref()),
        confirmed,
    )?;
    import_codex(
        aisw_home,
        user_home,
        detected.get(&Tool::Codex).and_then(|entry| entry.as_ref()),
        confirmed,
    )?;
    import_gemini(
        aisw_home,
        user_home,
        detected.get(&Tool::Gemini).and_then(|entry| entry.as_ref()),
        confirmed,
    )?;
    Ok(())
}

fn print_import_outcome(tool: Tool, profile_name: &str, marked_active: bool) {
    output::print_kv("Import", format!("profile '{}'", profile_name));
    output::print_kv(
        "Activation",
        if marked_active { "active" } else { "stored" },
    );
    output::print_blank_line();
    output::print_effects_header();
    if marked_active {
        output::print_effect(format!(
            "Imported {} credentials as profile '{}' and marked it active.",
            tool.display_name(),
            profile_name
        ));
        output::print_effect("Active profile updated.");
    } else {
        output::print_effect(format!(
            "Imported {} credentials as profile '{}'.",
            tool.display_name(),
            profile_name
        ));
    }
    output::print_blank_line();
}

fn print_already_managed_live_match(
    tool: Tool,
    config_store: &ConfigStore,
    auth: &str,
    existing_name: &str,
) -> Result<()> {
    output::print_kv("Auth", auth);
    output::print_kv("Import", "already managed");
    output::print_info(format!(
        "Current live credentials match stored profile '{}'.",
        existing_name
    ));

    match config_store.load()?.active_for(tool) {
        Some(active_name) if active_name == existing_name => {
            output::print_info(format!(
                "aisw also records '{}' as the active profile for {}.",
                existing_name, tool
            ));
        }
        Some(active_name) => {
            output::print_warning(format!(
                "aisw records '{}' as the active profile for {}, but the current live upstream credentials match '{}'.",
                active_name, tool, existing_name
            ));
        }
        None => {
            output::print_info(format!(
                "aisw does not currently record an active profile for {}.",
                tool
            ));
        }
    }

    output::print_blank_line();
    Ok(())
}

fn print_detection_metadata(detected: Option<&DetectedTool>) {
    match detected {
        Some(tool) => {
            output::print_kv("Status", "detected");
            if let Some(version) = tool.version.as_deref() {
                output::print_kv("Version", version);
            }
            output::print_kv("Path", tool.binary_path.display().to_string());
        }
        None => {
            output::print_kv("Status", "not detected");
        }
    }
}

fn print_import_header(tool: Tool, detected: Option<&DetectedTool>) {
    output::print_tool_section(tool);
    output::print_kv("Detected", if detected.is_some() { "yes" } else { "no" });
}

fn extract_json_string_field(bytes: &[u8], field: &str) -> Option<String> {
    let value: serde_json::Value = serde_json::from_slice(bytes).ok()?;
    value.get(field)?.as_str().map(ToOwned::to_owned)
}

fn extract_gemini_api_key(bytes: &[u8]) -> Option<String> {
    std::str::from_utf8(bytes)
        .ok()?
        .lines()
        .find_map(|line| line.strip_prefix("GEMINI_API_KEY=").map(ToOwned::to_owned))
}

fn import_claude(
    aisw_home: &Path,
    user_home: &Path,
    detected: Option<&DetectedTool>,
    confirmed: bool,
) -> Result<()> {
    print_import_header(Tool::Claude, detected);
    let local_state = auth::claude::live_local_state_dir(user_home);
    output::print_kv(
        "Local state",
        local_state
            .as_ref()
            .map(|path| format!("found {}", path.display()))
            .unwrap_or_else(|| "not found".to_owned()),
    );
    let may_prompt_for_claude_keychain_import = cfg!(target_os = "macos")
        && local_state.is_some()
        && auth::claude::keychain_import_supported();
    if may_prompt_for_claude_keychain_import {
        output::print_info(
            "Claude on macOS stores live auth in Keychain. Import may trigger a macOS Keychain prompt so aisw can read the current Claude credentials.",
        );
    }

    let Some(snapshot) = auth::claude::live_credentials_snapshot_for_import(user_home)? else {
        if auth::claude::keychain_import_supported() && local_state.is_some() {
            output::print_kv("Credentials", "not found in file or Keychain");
            output::print_info(
                "Claude local state exists, but aisw could not find importable auth in \
                 ~/.claude/.credentials.json or the system keyring.",
            );
        } else {
            output::print_kv("Credentials", "not found");
        }
        output::print_blank_line();
        return Ok(());
    };

    let profile_store = ProfileStore::new(aisw_home);
    let config_store = ConfigStore::new(aisw_home);
    let mark_active = should_mark_import_active(&config_store, Tool::Claude)?;

    let imported_backend = auth::claude::imported_profile_backend(&snapshot.source);
    if let Some(note) = auth::claude::storage_fallback_note(CredentialBackend::SystemKeyring) {
        if matches!(
            snapshot.source,
            auth::claude::LiveCredentialSource::Keychain
        ) {
            output::print_warning(note);
        }
    }
    let (source_desc, source_bytes) = match snapshot.source {
        auth::claude::LiveCredentialSource::File(path) => {
            (format!("found {}", path.display()), snapshot.bytes)
        }
        auth::claude::LiveCredentialSource::Keychain => (
            format!("found {}", auth::system_keyring::display_name()),
            snapshot.bytes,
        ),
    };
    let imported_method = if extract_json_string_field(&source_bytes, "apiKey").is_some() {
        AuthMethod::ApiKey
    } else {
        AuthMethod::OAuth
    };
    let account_bytes = auth::claude::read_live_oauth_account_metadata_for_import(user_home)?;
    if imported_method == AuthMethod::OAuth {
        if let Some(existing_name) = existing_claude_profile_for_exact_credentials(
            &profile_store,
            &config_store,
            &source_bytes,
            account_bytes.as_deref(),
        )? {
            output::print_kv("Credentials", &source_desc);
            print_already_managed_live_match(Tool::Claude, &config_store, "oauth", &existing_name)?;
            return Ok(());
        }
    }
    if let Some(api_key) = extract_json_string_field(&source_bytes, "apiKey") {
        if let Some(existing_name) = auth::identity::existing_api_key_profile_for_secret(
            &profile_store,
            &config_store,
            Tool::Claude,
            &api_key,
        )? {
            output::print_kv("Credentials", &source_desc);
            print_already_managed_live_match(
                Tool::Claude,
                &config_store,
                "api_key",
                &existing_name,
            )?;
            return Ok(());
        }
    }
    if let Some(existing_name) = auth::identity::existing_claude_oauth_profile_for_live_state(
        &profile_store,
        &config_store,
        &source_bytes,
        account_bytes.as_deref(),
    )? {
        output::print_kv("Credentials", &source_desc);
        print_already_managed_live_match(Tool::Claude, &config_store, "oauth", &existing_name)?;
        return Ok(());
    }

    if confirmed && profile_store.exists(Tool::Claude, "default") {
        output::print_kv("Credentials", &source_desc);
        output::print_kv("Import", "skipped");
        output::print_info("Profile 'default' already exists.");
        output::print_blank_line();
        return Ok(());
    }

    output::print_kv("Credentials", &source_desc);
    output::print_kv(
        "Auth",
        if imported_method == AuthMethod::ApiKey {
            "api_key"
        } else {
            "oauth"
        },
    );
    let Some((profile_name, label)) =
        import_name_and_label(Tool::Claude, &profile_store, confirmed)?
    else {
        output::print_kv("Import", "skipped");
        output::print_blank_line();
        return Ok(());
    };

    profile_store.create(Tool::Claude, &profile_name)?;
    match imported_backend {
        CredentialBackend::File => profile_store.write_file(
            Tool::Claude,
            &profile_name,
            ".credentials.json",
            &source_bytes,
        )?,
        CredentialBackend::SystemKeyring => {
            auth::secure_store::write_profile_secret(Tool::Claude, &profile_name, &source_bytes)?
        }
    }
    if imported_method == AuthMethod::OAuth {
        auth::claude::capture_live_oauth_account_metadata(
            &profile_store,
            &profile_name,
            user_home,
        )?;
    }
    if imported_method == AuthMethod::OAuth {
        auth::identity::ensure_unique_oauth_identity(
            &profile_store,
            &config_store,
            Tool::Claude,
            &profile_name,
            imported_backend,
        )
        .inspect_err(|_| {
            if imported_backend == CredentialBackend::SystemKeyring {
                let _ = auth::secure_store::delete_profile_secret(Tool::Claude, &profile_name);
            }
            let _ = profile_store.delete(Tool::Claude, &profile_name);
        })?;
    }
    config_store.add_profile(
        Tool::Claude,
        &profile_name,
        ProfileMeta {
            added_at: Utc::now(),
            auth_method: imported_method,
            credential_backend: imported_backend,
            label,
        },
    )?;
    if mark_active {
        activate_imported_profile(Tool::Claude, &config_store, &profile_name)?;
        print_import_outcome(Tool::Claude, &profile_name, true);
    } else {
        print_import_outcome(Tool::Claude, &profile_name, false);
    }
    Ok(())
}

fn existing_claude_profile_for_exact_credentials(
    profile_store: &ProfileStore,
    config_store: &ConfigStore,
    live_bytes: &[u8],
    live_account_bytes: Option<&[u8]>,
) -> Result<Option<String>> {
    let config = config_store.load()?;
    for (name, meta) in config.profiles_for(Tool::Claude) {
        if meta.auth_method != AuthMethod::OAuth {
            continue;
        }

        let stored = match meta.credential_backend {
            CredentialBackend::File => profile_store
                .read_file(Tool::Claude, name, ".credentials.json")
                .ok(),
            CredentialBackend::SystemKeyring => {
                auth::secure_store::read_profile_secret(Tool::Claude, name)?
            }
        };

        if stored.as_deref() == Some(live_bytes) {
            let account_match = auth::identity::existing_claude_oauth_profile_for_live_state(
                profile_store,
                config_store,
                live_bytes,
                live_account_bytes,
            )?;
            if account_match.as_deref() == Some(name.as_str()) || live_account_bytes.is_none() {
                return Ok(Some(name.clone()));
            }
        }
    }

    Ok(None)
}

fn import_codex(
    aisw_home: &Path,
    user_home: &Path,
    detected: Option<&DetectedTool>,
    confirmed: bool,
) -> Result<()> {
    print_import_header(Tool::Codex, detected);
    let local_state = auth::codex::live_local_state_dir(user_home);
    output::print_kv(
        "Local state",
        local_state
            .as_ref()
            .map(|path| format!("found {}", path.display()))
            .unwrap_or_else(|| "not found".to_owned()),
    );

    let Some(snapshot) = auth::codex::live_credentials_snapshot_for_import(user_home)? else {
        if local_state.is_some() {
            let storage = auth::codex::live_auth_storage(user_home)?
                .unwrap_or(auth::codex::LiveAuthStorage::Auto);
            output::print_kv("Credentials", "not found in auth.json");
            match storage {
                auth::codex::LiveAuthStorage::Keyring => output::print_info(
                    "Codex local state exists, but aisw could not find importable auth in \
                     ~/.codex/auth.json or the system keyring. This install appears to use \
                     keyring-backed auth, but aisw could not locate a readable credential there.",
                ),
                auth::codex::LiveAuthStorage::Auto => output::print_info(
                    "Codex local state exists, but aisw could not find importable auth in \
                     ~/.codex/auth.json. Codex defaults to its auto auth-storage mode here, \
                     which may be using the system keyring instead of a file backend.",
                ),
                auth::codex::LiveAuthStorage::File => output::print_info(
                    "Codex local state exists, but aisw could not find importable auth in \
                     ~/.codex/auth.json even though the configured backend is file.",
                ),
                auth::codex::LiveAuthStorage::Unknown => output::print_info(
                    "Codex local state exists, but aisw could not find importable auth in \
                     ~/.codex/auth.json. The configured auth backend is not recognized.",
                ),
            }
            output::print_kv("Auth storage", storage.description());
        } else {
            output::print_kv("Credentials", "not found");
        }
        output::print_blank_line();
        return Ok(());
    };

    let profile_store = ProfileStore::new(aisw_home);
    let config_store = ConfigStore::new(aisw_home);
    let mark_active = should_mark_import_active(&config_store, Tool::Codex)?;

    let src_path = snapshot.source_path;
    let (src_desc, source_bytes) = (format!("found {}", src_path.display()), snapshot.bytes);

    // Determine auth method from the credential content: a `token` field indicates an
    // API key; anything with an OAuth identity (email / subject) is an OAuth credential.
    let is_api_key = extract_json_string_field(&source_bytes, "token").is_some();

    if is_api_key {
        if let Some(secret) = extract_json_string_field(&source_bytes, "token") {
            if let Some(existing_name) = auth::identity::existing_api_key_profile_for_secret(
                &profile_store,
                &config_store,
                Tool::Codex,
                &secret,
            )? {
                output::print_kv("Credentials", &src_desc);
                print_already_managed_live_match(
                    Tool::Codex,
                    &config_store,
                    "api_key",
                    &existing_name,
                )?;
                return Ok(());
            }
        }
    } else if let Some(existing_name) = auth::identity::existing_oauth_profile_for_json_bytes(
        &profile_store,
        &config_store,
        Tool::Codex,
        &source_bytes,
    )? {
        output::print_kv("Credentials", &src_desc);
        print_already_managed_live_match(Tool::Codex, &config_store, "oauth", &existing_name)?;
        return Ok(());
    }

    if confirmed && profile_store.exists(Tool::Codex, "default") {
        output::print_kv("Credentials", &src_desc);
        output::print_kv("Import", "skipped");
        output::print_info("Profile 'default' already exists.");
        output::print_blank_line();
        return Ok(());
    }

    let auth_method = if is_api_key {
        AuthMethod::ApiKey
    } else {
        AuthMethod::OAuth
    };

    output::print_kv("Credentials", &src_desc);
    output::print_kv("Auth", if is_api_key { "api_key" } else { "oauth" });
    let Some((profile_name, label)) =
        import_name_and_label(Tool::Codex, &profile_store, confirmed)?
    else {
        output::print_kv("Import", "skipped");
        output::print_blank_line();
        return Ok(());
    };

    profile_store.create(Tool::Codex, &profile_name)?;
    auth::codex::write_file_store_config(&profile_store, &profile_name)?;
    profile_store.write_file(
        Tool::Codex,
        &profile_name,
        auth::codex::AUTH_FILE,
        &source_bytes,
    )?;
    if auth_method == AuthMethod::OAuth {
        auth::identity::ensure_unique_oauth_identity(
            &profile_store,
            &config_store,
            Tool::Codex,
            &profile_name,
            CredentialBackend::File,
        )
        .inspect_err(|_| {
            let _ = profile_store.delete(Tool::Codex, &profile_name);
        })?;
    }
    config_store.add_profile(
        Tool::Codex,
        &profile_name,
        ProfileMeta {
            added_at: Utc::now(),
            auth_method,
            credential_backend: CredentialBackend::File,
            label,
        },
    )?;
    if mark_active {
        activate_imported_profile(Tool::Codex, &config_store, &profile_name)?;
        print_import_outcome(Tool::Codex, &profile_name, true);
    } else {
        print_import_outcome(Tool::Codex, &profile_name, false);
    }
    Ok(())
}

fn import_gemini(
    aisw_home: &Path,
    user_home: &Path,
    detected: Option<&DetectedTool>,
    confirmed: bool,
) -> Result<()> {
    print_import_header(Tool::Gemini, detected);
    let Some(selection) = auth::gemini::detect_live_import_selection(user_home)? else {
        output::print_kv("Credentials", "not found");
        output::print_blank_line();
        return Ok(());
    };
    let src_desc = selection.source_description;
    let method = selection.method;
    let env_file = selection.env_file;
    let oauth_files = selection.oauth_files;
    let has_both_sources = selection.has_both_sources;

    let profile_store = ProfileStore::new(aisw_home);
    let config_store = ConfigStore::new(aisw_home);
    let mark_active = should_mark_import_active(&config_store, Tool::Gemini)?;

    if method == AuthMethod::ApiKey {
        let source_bytes = fs::read(&env_file)
            .with_context(|| format!("could not read {}", env_file.display()))?;
        if let Some(api_key) = extract_gemini_api_key(&source_bytes) {
            if let Some(existing_name) = auth::identity::existing_api_key_profile_for_secret(
                &profile_store,
                &config_store,
                Tool::Gemini,
                &api_key,
            )? {
                output::print_kv("Credentials", &src_desc);
                print_already_managed_live_match(
                    Tool::Gemini,
                    &config_store,
                    "api_key",
                    &existing_name,
                )?;
                return Ok(());
            }
        }
    }

    if method == AuthMethod::OAuth {
        if let Some(existing_name) = auth::gemini::existing_oauth_profile_for_live_files(
            &profile_store,
            &config_store,
            &oauth_files,
        )? {
            output::print_kv("Credentials", &src_desc);
            print_already_managed_live_match(Tool::Gemini, &config_store, "oauth", &existing_name)?;
            return Ok(());
        }
    }

    if confirmed && profile_store.exists(Tool::Gemini, "default") {
        output::print_kv("Credentials", &src_desc);
        output::print_kv("Import", "skipped");
        output::print_info("Profile 'default' already exists.");
        output::print_blank_line();
        return Ok(());
    }

    output::print_kv("Credentials", &src_desc);
    if has_both_sources {
        output::print_info(
            "Both Gemini API key (.env) and OAuth cache were found. Import precedence is .env first.",
        );
    }
    output::print_kv(
        "Auth",
        if method == AuthMethod::ApiKey {
            "api_key"
        } else {
            "oauth"
        },
    );
    let Some((profile_name, label)) =
        import_name_and_label(Tool::Gemini, &profile_store, confirmed)?
    else {
        output::print_kv("Import", "skipped");
        output::print_blank_line();
        return Ok(());
    };

    profile_store.create(Tool::Gemini, &profile_name)?;
    if method == AuthMethod::OAuth {
        auth::gemini::copy_live_oauth_files_into_profile(
            &profile_store,
            &profile_name,
            &oauth_files,
        )?;
        auth::identity::ensure_unique_oauth_identity(
            &profile_store,
            &config_store,
            Tool::Gemini,
            &profile_name,
            CredentialBackend::File,
        )
        .inspect_err(|_| {
            let _ = profile_store.delete(Tool::Gemini, &profile_name);
        })?;
    } else {
        profile_store.copy_file_into(Tool::Gemini, &profile_name, &env_file, ".env")?;
    }
    config_store.add_profile(
        Tool::Gemini,
        &profile_name,
        ProfileMeta {
            added_at: Utc::now(),
            auth_method: method,
            credential_backend: CredentialBackend::File,
            label,
        },
    )?;
    if mark_active {
        activate_imported_profile(Tool::Gemini, &config_store, &profile_name)?;
        print_import_outcome(Tool::Gemini, &profile_name, true);
    } else {
        print_import_outcome(Tool::Gemini, &profile_name, false);
    }
    Ok(())
}

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

    use tempfile::tempdir;

    use super::*;

    fn run(aisw_home: &Path, user_home: &Path, shell: Option<&str>) -> Result<()> {
        run_inner(aisw_home, user_home, shell, true)
    }

    #[test]
    fn creates_aisw_home_and_config() {
        let tmp = tempdir().unwrap();
        let aisw_home = tmp.path().join("aisw");
        let user_home = tmp.path().join("home");
        fs::create_dir_all(&user_home).unwrap();

        run(&aisw_home, &user_home, None).unwrap();

        assert!(aisw_home.join("config.json").exists());
    }

    #[test]
    fn idempotent_does_not_duplicate_hook() {
        let tmp = tempdir().unwrap();
        let aisw_home = tmp.path().join("aisw");
        let user_home = tmp.path().join("home");
        fs::create_dir_all(&user_home).unwrap();
        let rc = user_home.join(".zshrc");

        run(&aisw_home, &user_home, Some("/bin/zsh")).unwrap();
        run(&aisw_home, &user_home, Some("/bin/zsh")).unwrap();

        let contents = fs::read_to_string(&rc).unwrap();
        assert_eq!(
            contents.matches("shell-hook").count(),
            1,
            "hook should appear exactly once"
        );
    }

    #[test]
    fn bash_rc_file_appended() {
        let tmp = tempdir().unwrap();
        let aisw_home = tmp.path().join("aisw");
        let user_home = tmp.path().join("home");
        fs::create_dir_all(&user_home).unwrap();

        run(&aisw_home, &user_home, Some("/bin/bash")).unwrap();

        let rc = if cfg!(target_os = "macos") {
            user_home.join(".bash_profile")
        } else {
            user_home.join(".bashrc")
        };
        let contents = fs::read_to_string(&rc).unwrap();
        assert!(contents.contains("shell-hook bash"));
    }

    #[test]
    fn fish_rc_file_appended() {
        let tmp = tempdir().unwrap();
        let aisw_home = tmp.path().join("aisw");
        let user_home = tmp.path().join("home");
        fs::create_dir_all(&user_home).unwrap();

        run(&aisw_home, &user_home, Some("/usr/bin/fish")).unwrap();

        let rc = user_home.join(".config").join("fish").join("config.fish");
        let contents = fs::read_to_string(&rc).unwrap();
        assert!(contents.contains("shell-hook fish | source"));
    }

    #[test]
    fn unknown_shell_does_not_error() {
        let tmp = tempdir().unwrap();
        let aisw_home = tmp.path().join("aisw");
        let user_home = tmp.path().join("home");
        fs::create_dir_all(&user_home).unwrap();

        assert!(run(&aisw_home, &user_home, Some("/usr/bin/nushell")).is_ok());
    }

    #[test]
    fn imports_claude_credentials() {
        let _g = crate::SPAWN_LOCK.lock().unwrap_or_else(|p| p.into_inner());
        let tmp = tempdir().unwrap();
        let aisw_home = tmp.path().join("aisw");
        let user_home = tmp.path().join("home");
        let claude_dir = user_home.join(".claude");
        fs::create_dir_all(&claude_dir).unwrap();
        fs::write(
            claude_dir.join(".credentials.json"),
            b"{\"token\":\"oauth\"}",
        )
        .unwrap();
        std::env::set_var("AISW_CLAUDE_AUTH_STORAGE", "file");

        let result = run(&aisw_home, &user_home, None);
        std::env::remove_var("AISW_CLAUDE_AUTH_STORAGE");
        result.unwrap();

        let ps = ProfileStore::new(&aisw_home);
        assert!(ps.exists(Tool::Claude, "default"));
        let config = ConfigStore::new(&aisw_home).load().unwrap();
        assert!(config.profiles_for(Tool::Claude).contains_key("default"));
        assert_eq!(
            config.profiles_for(Tool::Claude)["default"].auth_method,
            AuthMethod::OAuth
        );
        let contents = ps
            .read_file(Tool::Claude, "default", ".credentials.json")
            .unwrap();
        assert_eq!(contents, b"{\"token\":\"oauth\"}");
        assert_eq!(
            config.profiles_for(Tool::Claude)["default"].credential_backend,
            CredentialBackend::File
        );
    }

    #[test]
    fn imports_codex_credentials() {
        let tmp = tempdir().unwrap();
        let aisw_home = tmp.path().join("aisw");
        let user_home = tmp.path().join("home");
        let codex_dir = user_home.join(".codex");
        fs::create_dir_all(&codex_dir).unwrap();
        fs::write(codex_dir.join("auth.json"), b"{\"token\":\"tok\"}").unwrap();

        run(&aisw_home, &user_home, None).unwrap();

        let ps = ProfileStore::new(&aisw_home);
        assert!(ps.exists(Tool::Codex, "default"));
        let config = ConfigStore::new(&aisw_home).load().unwrap();
        assert_eq!(
            config.profiles_for(Tool::Codex)["default"].auth_method,
            AuthMethod::ApiKey
        );
    }

    #[test]
    fn imports_gemini_env_as_api_key() {
        let tmp = tempdir().unwrap();
        let aisw_home = tmp.path().join("aisw");
        let user_home = tmp.path().join("home");
        let gemini_dir = user_home.join(".gemini");
        fs::create_dir_all(&gemini_dir).unwrap();
        fs::write(gemini_dir.join(".env"), b"GEMINI_API_KEY=abc123\n").unwrap();

        run(&aisw_home, &user_home, None).unwrap();

        let ps = ProfileStore::new(&aisw_home);
        assert!(ps.exists(Tool::Gemini, "default"));
        let config = ConfigStore::new(&aisw_home).load().unwrap();
        assert_eq!(
            config.profiles_for(Tool::Gemini)["default"].auth_method,
            AuthMethod::ApiKey
        );
    }

    #[test]
    fn imports_gemini_prefers_env_when_both_sources_exist() {
        let tmp = tempdir().unwrap();
        let aisw_home = tmp.path().join("aisw");
        let user_home = tmp.path().join("home");
        let gemini_dir = user_home.join(".gemini");
        fs::create_dir_all(&gemini_dir).unwrap();
        fs::write(gemini_dir.join(".env"), b"GEMINI_API_KEY=abc123\n").unwrap();
        fs::write(
            gemini_dir.join("oauth_creds.json"),
            b"{\"token\":\"oauth\"}",
        )
        .unwrap();

        run(&aisw_home, &user_home, None).unwrap();

        let ps = ProfileStore::new(&aisw_home);
        assert!(ps.exists(Tool::Gemini, "default"));
        assert!(ps
            .profile_dir(Tool::Gemini, "default")
            .join(".env")
            .exists());
        assert!(!ps
            .profile_dir(Tool::Gemini, "default")
            .join("oauth_creds.json")
            .exists());
        let config = ConfigStore::new(&aisw_home).load().unwrap();
        assert_eq!(
            config.profiles_for(Tool::Gemini)["default"].auth_method,
            AuthMethod::ApiKey
        );
    }

    #[test]
    fn skip_import_if_default_profile_exists() {
        let _g = crate::SPAWN_LOCK.lock().unwrap_or_else(|p| p.into_inner());
        let tmp = tempdir().unwrap();
        let aisw_home = tmp.path().join("aisw");
        let user_home = tmp.path().join("home");
        let claude_dir = user_home.join(".claude");
        fs::create_dir_all(&claude_dir).unwrap();
        fs::write(claude_dir.join(".credentials.json"), b"{\"token\":\"v1\"}").unwrap();
        std::env::set_var("AISW_CLAUDE_AUTH_STORAGE", "file");

        // First run: import succeeds.
        run(&aisw_home, &user_home, None).unwrap();

        // Second run: skip without error.
        let result = run(&aisw_home, &user_home, None);
        std::env::remove_var("AISW_CLAUDE_AUTH_STORAGE");
        result.unwrap();

        // Profile still exists, credentials not overwritten.
        let ps = ProfileStore::new(&aisw_home);
        assert!(ps.exists(Tool::Claude, "default"));
    }
}
