use super::{AppState, PendingClone};
use hk_core::{HkError, deployer, manager, marketplace, models::*, scanner, service};
use tauri::State;

// --- Multi-skill git install flow ---

#[derive(serde::Serialize)]
#[serde(tag = "type")]
pub enum ScanResult {
    Installed {
        result: manager::InstallResult,
    },
    MultipleSkills {
        clone_id: String,
        skills: Vec<manager::DiscoveredSkill>,
    },
    NoSkills,
}

/// Names of agents that are both detected on disk and enabled in settings —
/// the targets an "install to all detected agents" fallback should use when the
/// caller passes no explicit list. Mirrors the frontend, which only offers
/// detected + enabled agents as install targets.
fn enabled_detected_agents(
    store: &hk_core::store::Store,
    adapters: &[Box<dyn hk_core::adapter::AgentAdapter>],
) -> Vec<String> {
    adapters
        .iter()
        .filter(|a| {
            a.detect()
                && store
                    .get_agent_setting(a.name())
                    .map(|(_, enabled)| enabled)
                    .unwrap_or(true)
        })
        .map(|a| a.name().to_string())
        .collect()
}

#[tauri::command]
pub async fn list_hermes_categories(
    state: State<'_, AppState>,
) -> Result<Vec<String>, HkError> {
    let adapters = state.adapters.clone();
    tauri::async_runtime::spawn_blocking(move || {
        Ok(adapters
            .iter()
            .find(|a| a.name() == "hermes")
            .map(|a| a.list_skill_categories())
            .unwrap_or_default())
    })
    .await
    .map_err(|e| HkError::Internal(e.to_string()))?
}

#[tauri::command]
pub async fn install_from_local(
    state: State<'_, AppState>,
    path: String,
    target_agents: Vec<String>,
    target_scope: ConfigScope,
    hermes_category: Option<String>,
) -> Result<manager::InstallResult, HkError> {
    let store = state.store.clone();
    let adapters = state.adapters.clone();
    tauri::async_runtime::spawn_blocking(move || {
        let source_path = std::path::Path::new(&path);
        if !source_path.is_dir() {
            return Err(HkError::Validation(
                "Selected path is not a directory".into(),
            ));
        }
        // Must contain SKILL.md at root or be a parent of skill subdirectories
        let skill_md = source_path.join("SKILL.md");
        if !skill_md.exists() {
            return Err(HkError::Validation(
                "Selected directory does not contain a SKILL.md file".into(),
            ));
        }

        let skill_name = scanner::parse_skill_name(&skill_md).unwrap_or_else(|| {
            source_path
                .file_name()
                .unwrap_or_default()
                .to_string_lossy()
                .to_string()
        });

        let agents: Vec<String> = if target_agents.is_empty() {
            enabled_detected_agents(&store.lock(), &adapters)
        } else {
            target_agents
        };

        for agent_name in &agents {
            let a = adapters
                .iter()
                .find(|a| a.name() == agent_name.as_str())
                .ok_or_else(|| HkError::NotFound(format!("Agent '{}' not found", agent_name)))?;
            // A category only resolves a dir for category-aware agents (Hermes);
            // everything else returns None here and falls back to skill_dir_for.
            let target_dir = hermes_category
                .as_deref()
                .and_then(|cat| a.skill_dir_for_category(&target_scope, cat))
                .or_else(|| a.skill_dir_for(&target_scope))
                .ok_or_else(|| {
                    HkError::Internal(format!(
                        "Agent '{}' has no skill directory for scope {:?}",
                        agent_name, target_scope
                    ))
                })?;
            std::fs::create_dir_all(&target_dir)?;
            deployer::deploy_skill(source_path, &target_dir)?;
        }

        let result = manager::InstallResult {
            name: skill_name.clone(),
            was_update: false,
            revision: None,
            ..Default::default()
        };

        // Post-install: scan, sync, set meta, audit
        let git_source = scanner::detect_source_for(source_path);
        let meta = InstallMeta {
            install_type: "local".into(),
            url: git_source.url.clone().or_else(|| Some(path.clone())),
            url_resolved: None,
            branch: None,
            subpath: None,
            revision: git_source.commit_hash.clone(),
            remote_revision: None,
            checked_at: None,
            check_error: None,
        };
        let pack = git_source
            .url
            .as_deref()
            .and_then(hk_core::scanner::extract_pack_from_url);
        {
            let store = store.lock();
            service::post_install_sync(
                &store,
                &adapters,
                &agents,
                &skill_name,
                Some(meta),
                pack.as_deref(),
                &target_scope,
            )?;
        }

        Ok(result)
    })
    .await
    .map_err(|e| HkError::Internal(e.to_string()))?
}

#[tauri::command]
pub async fn install_from_git(
    state: State<'_, AppState>,
    url: String,
    target_agent: Option<String>,
    skill_id: Option<String>,
    target_scope: ConfigScope,
) -> Result<manager::InstallResult, HkError> {
    let store = state.store.clone();
    let adapters = state.adapters.clone();
    tauri::async_runtime::spawn_blocking(move || {
        hk_core::sanitize::validate_git_url(&url)
            .map_err(|e| HkError::Validation(e.to_string()))?;
        let (target_dir, agent_name) = if let Some(ref agent) = target_agent {
            let a = adapters
                .iter()
                .find(|a| a.name() == agent.as_str())
                .ok_or_else(|| HkError::NotFound(format!("Agent '{}' not found", agent)))?;
            let dir = a.skill_dir_for(&target_scope).ok_or_else(|| {
                HkError::Internal(format!(
                    "Agent '{}' has no skill directory for scope {:?}",
                    agent, target_scope
                ))
            })?;
            (dir, agent.clone())
        } else {
            // Fallback: first detected agent
            let a = adapters
                .iter()
                .find(|a| a.detect())
                .ok_or_else(|| HkError::Internal("No detected agent found".into()))?;
            let name = a.name().to_string();
            let dir = a.skill_dir_for(&target_scope).ok_or_else(|| {
                HkError::Internal(format!(
                    "Agent '{}' has no skill directory for scope {:?}",
                    name, target_scope
                ))
            })?;
            (dir, name)
        };

        std::fs::create_dir_all(&target_dir)?;
        let sid = skill_id.as_deref().filter(|s| !s.is_empty());
        let result = manager::install_from_git_with_id(&url, &target_dir, sid)?;

        // Post-install: scan, sync, set meta, audit
        let meta = InstallMeta {
            install_type: "git".into(),
            url: Some(url.clone()),
            url_resolved: None,
            branch: None,
            subpath: sid.map(|s| s.to_string()),
            revision: result.revision.clone(),
            remote_revision: None,
            checked_at: None,
            check_error: None,
        };
        let pack = meta
            .url
            .as_deref()
            .and_then(hk_core::scanner::extract_pack_from_url);
        let agents = vec![agent_name];
        {
            let store = store.lock();
            service::post_install_sync(
                &store,
                &adapters,
                &agents,
                &result.name,
                Some(meta),
                pack.as_deref(),
                &target_scope,
            )?;
        }

        Ok(result)
    })
    .await
    .map_err(|e| HkError::Internal(e.to_string()))?
}

#[tauri::command]
pub async fn scan_git_repo(
    state: State<'_, AppState>,
    url: String,
    target_agents: Vec<String>,
    target_scope: ConfigScope,
) -> Result<ScanResult, HkError> {
    // Clean up stale pending clones (older than 10 minutes)
    {
        let mut clones = state.pending_clones.lock();
        clones.retain(|_, v| v.created_at.elapsed().as_secs() < 600);
    }

    let store_clone = state.store.clone();
    let adapters = state.adapters.clone();
    let pending_clones = state.pending_clones.clone();

    tauri::async_runtime::spawn_blocking(move || -> Result<ScanResult, HkError> {
        let temp = tempfile::tempdir()?;
        let clone_dir = temp.path().join("repo");

        let output = std::process::Command::new("git")
            .args([
                "clone",
                "--depth",
                "1",
                "--",
                &url,
                &clone_dir.to_string_lossy(),
            ])
            .output()
            .map_err(|e| HkError::CommandFailed(format!("Failed to run git clone: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(HkError::CommandFailed(format!(
                "git clone failed: {}",
                stderr.trim()
            )));
        }

        let skills = manager::scan_repo_skills(&clone_dir);

        match skills.len() {
            0 => Ok(ScanResult::NoSkills),
            1 => {
                // Auto-install single skill
                let agents = if target_agents.is_empty() {
                    vec![
                        enabled_detected_agents(&store_clone.lock(), &adapters)
                            .into_iter()
                            .next()
                            .ok_or_else(|| HkError::NotFound("No detected agent found".into()))?,
                    ]
                } else {
                    target_agents
                };

                let skill_id = if skills[0].skill_id.is_empty() {
                    None
                } else {
                    Some(skills[0].skill_id.as_str())
                };
                let mut last_result = None;
                let mut installed_agents = Vec::new();
                for agent_name in &agents {
                    let a = adapters
                        .iter()
                        .find(|a| a.name() == agent_name.as_str())
                        .ok_or_else(|| {
                            HkError::NotFound(format!("Agent '{}' not found", agent_name))
                        })?;
                    let target_dir = a.skill_dir_for(&target_scope).ok_or_else(|| {
                        HkError::Internal(format!(
                            "Agent '{}' has no skill directory for scope {:?}",
                            agent_name, target_scope
                        ))
                    })?;
                    std::fs::create_dir_all(&target_dir)?;
                    let result = manager::install_from_git_with_id(&url, &target_dir, skill_id)?;
                    installed_agents.push(agent_name.clone());
                    last_result = Some(result);
                }

                // Post-install: scan, sync, set meta, audit
                if let Some(ref result) = last_result {
                    let meta = InstallMeta {
                        install_type: "git".into(),
                        url: Some(url.clone()),
                        url_resolved: None,
                        branch: None,
                        subpath: skill_id.map(|s| s.to_string()),
                        revision: result.revision.clone(),
                        remote_revision: None,
                        checked_at: None,
                        check_error: None,
                    };
                    let pack = meta
                        .url
                        .as_deref()
                        .and_then(hk_core::scanner::extract_pack_from_url);
                    let store = store_clone.lock();
                    service::post_install_sync(
                        &store,
                        &adapters,
                        &installed_agents,
                        &result.name,
                        Some(meta),
                        pack.as_deref(),
                        &target_scope,
                    )?;
                }

                Ok(ScanResult::Installed {
                    result: last_result
                        .ok_or_else(|| HkError::Internal("No install results produced".into()))?,
                })
            }
            _ => {
                // Multiple skills -- cache the clone and return the list
                let clone_id = uuid::Uuid::new_v4().to_string();
                let mut clones = pending_clones.lock();
                clones.insert(
                    clone_id.clone(),
                    PendingClone {
                        _temp_dir: temp,
                        clone_dir,
                        url,
                        created_at: std::time::Instant::now(),
                    },
                );
                Ok(ScanResult::MultipleSkills { clone_id, skills })
            }
        }
    })
    .await
    .map_err(|e| HkError::Internal(e.to_string()))?
}

#[tauri::command]
pub async fn install_scanned_skills(
    state: State<'_, AppState>,
    clone_id: String,
    skill_ids: Vec<String>,
    target_agents: Vec<String>,
    target_scope: ConfigScope,
) -> Result<Vec<manager::InstallResult>, HkError> {
    let pending = {
        let mut clones = state.pending_clones.lock();
        clones
            .remove(&clone_id)
            .ok_or_else(|| HkError::Internal("Clone session expired. Please try again.".into()))?
    };

    let store_clone = state.store.clone();
    let adapters = state.adapters.clone();

    tauri::async_runtime::spawn_blocking(move || -> Result<Vec<manager::InstallResult>, HkError> {
        let mut results = Vec::new();

        for agent_name in &target_agents {
            let a = adapters
                .iter()
                .find(|a| a.name() == agent_name.as_str())
                .ok_or_else(|| HkError::NotFound(format!("Agent '{}' not found", agent_name)))?;
            let target_dir = a.skill_dir_for(&target_scope).ok_or_else(|| {
                HkError::Internal(format!(
                    "Agent '{}' has no skill directory for scope {:?}",
                    agent_name, target_scope
                ))
            })?;
            std::fs::create_dir_all(&target_dir)?;

            for sid in &skill_ids {
                let skill_id_opt = if sid.is_empty() {
                    None
                } else {
                    Some(sid.as_str())
                };
                let result = manager::install_from_clone(
                    &pending.clone_dir,
                    &target_dir,
                    skill_id_opt,
                    &pending.url,
                )?;
                results.push((agent_name.clone(), sid.clone(), result));
            }
        }

        // Post-install: scan, sync, set meta, audit — once per unique skill name
        {
            let store = store_clone.lock();
            let install_pack = hk_core::scanner::extract_pack_from_url(&pending.url);
            let mut synced_skills = std::collections::HashSet::new();
            for (_agent_name, sid, result) in &results {
                if !synced_skills.insert(result.name.clone()) {
                    // Already synced this skill name — just set meta for remaining agents
                    let meta = InstallMeta {
                        install_type: "git".into(),
                        url: Some(pending.url.clone()),
                        url_resolved: None,
                        branch: None,
                        subpath: if sid.is_empty() {
                            None
                        } else {
                            Some(sid.clone())
                        },
                        revision: result.revision.clone(),
                        remote_revision: None,
                        checked_at: None,
                        check_error: None,
                    };
                    let ext_id = scanner::stable_id_with_scope_for(
                        &result.name,
                        "skill",
                        _agent_name,
                        &target_scope,
                    );
                    let _ = store.set_install_meta(&ext_id, &meta);
                    if let Some(ref p) = install_pack {
                        let _ = store.update_pack(&ext_id, Some(p));
                    }
                    continue;
                }
                let meta = InstallMeta {
                    install_type: "git".into(),
                    url: Some(pending.url.clone()),
                    url_resolved: None,
                    branch: None,
                    subpath: if sid.is_empty() {
                        None
                    } else {
                        Some(sid.clone())
                    },
                    revision: result.revision.clone(),
                    remote_revision: None,
                    checked_at: None,
                    check_error: None,
                };
                service::post_install_sync(
                    &store,
                    &adapters,
                    &target_agents,
                    &result.name,
                    Some(meta),
                    install_pack.as_deref(),
                    &target_scope,
                )?;
            }
        }

        // pending._temp_dir is dropped here, cleaning up the clone
        let results = results.into_iter().map(|(_, _, r)| r).collect();
        Ok(results)
    })
    .await
    .map_err(|e| HkError::Internal(e.to_string()))?
}

// --- Install new skills discovered in existing repos ---

#[tauri::command]
pub async fn install_new_repo_skills(
    state: State<'_, AppState>,
    url: String,
    skill_ids: Vec<String>,
    target_agents: Vec<String>,
    target_scope: ConfigScope,
) -> Result<Vec<manager::InstallResult>, HkError> {
    let store_clone = state.store.clone();
    let adapters = state.adapters.clone();

    tauri::async_runtime::spawn_blocking(move || -> Result<Vec<manager::InstallResult>, HkError> {
        // Clone the repo once
        let temp = tempfile::tempdir()
            .map_err(|e| HkError::Internal(format!("Failed to create temp directory: {e}")))?;
        let clone_dir = temp.path().join("repo");
        let output = std::process::Command::new("git")
            .args([
                "clone",
                "--depth",
                "1",
                "--",
                &url,
                &clone_dir.to_string_lossy(),
            ])
            .output()
            .map_err(|e| HkError::CommandFailed(format!("Failed to run git clone: {e}")))?;
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(HkError::CommandFailed(format!(
                "git clone failed: {}",
                stderr.trim()
            )));
        }

        let mut results = Vec::new();
        for agent_name in &target_agents {
            let a = adapters
                .iter()
                .find(|a| a.name() == agent_name.as_str())
                .ok_or_else(|| HkError::NotFound(format!("Agent '{}' not found", agent_name)))?;
            let target_dir = a.skill_dir_for(&target_scope).ok_or_else(|| {
                HkError::Internal(format!(
                    "Agent '{}' has no skill directory for scope {:?}",
                    agent_name, target_scope
                ))
            })?;
            std::fs::create_dir_all(&target_dir)?;

            for sid in &skill_ids {
                let skill_id_opt = if sid.is_empty() {
                    None
                } else {
                    Some(sid.as_str())
                };
                let result =
                    manager::install_from_clone(&clone_dir, &target_dir, skill_id_opt, &url)?;
                results.push((agent_name.clone(), sid.clone(), result));
            }
        }

        // Post-install: scan, sync, set meta, audit
        {
            let store = store_clone.lock();
            let install_pack = hk_core::scanner::extract_pack_from_url(&url);
            let mut synced_skills = std::collections::HashSet::new();
            for (_agent_name, sid, result) in &results {
                if !synced_skills.insert(result.name.clone()) {
                    let meta = InstallMeta {
                        install_type: "git".into(),
                        url: Some(url.clone()),
                        url_resolved: None,
                        branch: None,
                        subpath: if sid.is_empty() {
                            None
                        } else {
                            Some(sid.clone())
                        },
                        revision: result.revision.clone(),
                        remote_revision: None,
                        checked_at: None,
                        check_error: None,
                    };
                    let ext_id = scanner::stable_id_with_scope_for(
                        &result.name,
                        "skill",
                        _agent_name,
                        &target_scope,
                    );
                    let _ = store.set_install_meta(&ext_id, &meta);
                    if let Some(ref p) = install_pack {
                        let _ = store.update_pack(&ext_id, Some(p));
                    }
                    continue;
                }
                let meta = InstallMeta {
                    install_type: "git".into(),
                    url: Some(url.clone()),
                    url_resolved: None,
                    branch: None,
                    subpath: if sid.is_empty() {
                        None
                    } else {
                        Some(sid.clone())
                    },
                    revision: result.revision.clone(),
                    remote_revision: None,
                    checked_at: None,
                    check_error: None,
                };
                service::post_install_sync(
                    &store,
                    &adapters,
                    &target_agents,
                    &result.name,
                    Some(meta),
                    install_pack.as_deref(),
                    &target_scope,
                )?;
            }
        }

        Ok(results.into_iter().map(|(_, _, r)| r).collect())
    })
    .await
    .map_err(|e| HkError::Internal(e.to_string()))?
}

// --- Cross-agent deploy command ---

#[tauri::command]
pub async fn install_to_agent(
    state: State<'_, AppState>,
    extension_id: String,
    target_agent: String,
    hermes_category: Option<String>,
) -> Result<String, HkError> {
    let store = state.store.clone();
    let adapters = state.adapters.clone();
    tauri::async_runtime::spawn_blocking(move || {
        service::install_to_agent(
            &store,
            &adapters,
            &extension_id,
            &target_agent,
            hermes_category.as_deref(),
        )
    })
    .await
    .map_err(|e| HkError::Internal(e.to_string()))?
}

// --- CLI commands ---

#[tauri::command]
pub fn get_cli_with_children(
    state: State<AppState>,
    cli_id: String,
) -> Result<(Extension, Vec<Extension>), HkError> {
    let store = state.store.lock();
    let cli = store
        .get_extension(&cli_id)?
        .ok_or_else(|| HkError::NotFound(format!("CLI not found: {}", cli_id)))?;
    let children = store.get_child_skills(&cli_id)?;
    Ok((cli, children))
}

#[tauri::command]
pub async fn list_cli_marketplace() -> Result<Vec<marketplace::MarketplaceItem>, HkError> {
    tauri::async_runtime::spawn_blocking(marketplace::list_cli_registry)
        .await
        .map_err(|e| HkError::Internal(e.to_string()))
}
