use std::io;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

use crate::agent::{Agent, ConversationTurnControlChannels};
use crate::model::ResolvedCodexPermissions;
use crate::storage::{AppResult, Storage};

pub fn direct_answer(
    storage: &Storage,
    cwd: &Path,
    prompt: &str,
    raw_event_stream_path: &Path,
    cancel_requested: Option<Arc<AtomicBool>>,
) -> AppResult<String> {
    direct_answer_with_control(
        storage,
        cwd,
        prompt,
        raw_event_stream_path,
        cancel_requested,
        None,
    )
}

pub fn direct_answer_with_control(
    storage: &Storage,
    cwd: &Path,
    prompt: &str,
    raw_event_stream_path: &Path,
    cancel_requested: Option<Arc<AtomicBool>>,
    control: Option<ConversationTurnControlChannels>,
) -> AppResult<String> {
    let config = storage.load_config()?;
    let permissions = resolved_permissions(storage)?;
    let model = config
        .codex
        .as_ref()
        .and_then(|codex| codex.model.as_deref());
    let mut agent = Agent::for_conversation(cwd, model, permissions, raw_event_stream_path)?;
    agent.ask_text_with_cancel_and_control(cwd, prompt, cancel_requested, control)
}

pub fn direct_reflection(
    storage: &Storage,
    cwd: &Path,
    prompt: &str,
    raw_event_stream_path: &Path,
    cancel_requested: Option<Arc<AtomicBool>>,
) -> AppResult<String> {
    let config = storage.load_config()?;
    let permissions = resolved_permissions(storage)?;
    let model = config
        .codex
        .as_ref()
        .and_then(|codex| codex.model.as_deref());
    let mut agent = Agent::for_mentat_reflection(cwd, model, permissions, raw_event_stream_path)?;
    agent.ask_text_with_cancel(cwd, prompt, cancel_requested)
}

pub fn direct_self_reflection(
    storage: &Storage,
    cwd: &Path,
    prompt: &str,
    raw_event_stream_path: &Path,
    cancel_requested: Option<Arc<AtomicBool>>,
) -> AppResult<String> {
    let config = storage.load_config()?;
    let permissions = resolved_permissions(storage)?;
    let model = config
        .codex
        .as_ref()
        .and_then(|codex| codex.model.as_deref());
    let mut agent =
        Agent::for_mentat_self_reflection(cwd, model, permissions, raw_event_stream_path)?;
    agent.ask_text_with_cancel(cwd, prompt, cancel_requested)
}

pub fn direct_improvement_adoption(
    storage: &Storage,
    cwd: &Path,
    prompt: &str,
    raw_event_stream_path: &Path,
    cancel_requested: Option<Arc<AtomicBool>>,
) -> AppResult<String> {
    let config = storage.load_config()?;
    let permissions = resolved_permissions(storage)?;
    let model = config
        .codex
        .as_ref()
        .and_then(|codex| codex.model.as_deref());
    let mut agent = Agent::for_mentat_adoption(cwd, model, permissions, raw_event_stream_path)?;
    agent.ask_text_with_cancel(cwd, prompt, cancel_requested)
}

pub fn open_path_in_editor(path: &Path) -> AppResult<()> {
    let editor = resolve_editor_command();
    let mut parts = editor.split_whitespace();
    let program = parts
        .next()
        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "editor command is empty"))?;
    let args = parts.collect::<Vec<_>>();

    let status = Command::new(program)
        .args(args)
        .arg(path)
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .status()
        .map_err(|error| {
            io::Error::new(
                error.kind(),
                format!("failed to start editor {program}: {error}"),
            )
        })?;

    if !status.success() {
        return Err(io::Error::other(format!(
            "editor exited unsuccessfully for {}",
            path.display()
        ))
        .into());
    }

    Ok(())
}

fn resolved_permissions(storage: &Storage) -> AppResult<ResolvedCodexPermissions> {
    let config = storage.load_config()?;
    Ok(ResolvedCodexPermissions {
        network_access: config
            .codex
            .as_ref()
            .and_then(|codex| codex.permissions.as_ref())
            .and_then(|permissions| permissions.network_access)
            .unwrap_or(false),
    })
}

fn resolve_editor_command() -> String {
    std::env::var("VISUAL")
        .ok()
        .filter(|value| !value.trim().is_empty())
        .or_else(|| {
            std::env::var("EDITOR")
                .ok()
                .filter(|value| !value.trim().is_empty())
        })
        .unwrap_or_else(|| "vi".to_string())
}
