//! Native executor CLI entry point.
//!
//! `wg native-exec` runs the Rust-native LLM agent loop for a task.
//! It is called by the spawn wrapper script when the executor type is "native".
//!
//! This command:
//! 1. Reads the prompt from a file
//! 2. Resolves the bundle for the exec_mode (tool filtering)
//! 3. Initializes the appropriate LLM client (Anthropic or OpenAI-compatible)
//! 4. Runs the agent loop to completion
//! 5. Exits with 0 on success, non-zero on failure

use std::path::Path;

use anyhow::{Context, Result};

use workgraph::config::{Config, DispatchRole};
use workgraph::executor::native::agent::AgentLoop;
use workgraph::executor::native::bundle::resolve_bundle;
use workgraph::executor::native::journal;
use workgraph::executor::native::provider::create_provider_ext;
use workgraph::executor::native::tools::ToolRegistry;
use workgraph::executor::native::tools::helper_routing::HelperRouting;
use workgraph::models::ModelRegistry;

/// Run the native executor agent loop.
#[allow(clippy::too_many_arguments)]
pub fn run(
    workgraph_dir: &Path,
    prompt_file: &str,
    exec_mode: &str,
    task_id: &str,
    model: Option<&str>,
    provider: Option<&str>,
    endpoint_name: Option<&str>,
    endpoint_url: Option<&str>,
    api_key: Option<&str>,
    max_turns: usize,
    no_resume: bool,
) -> Result<()> {
    let prompt = std::fs::read_to_string(prompt_file)
        .with_context(|| format!("Failed to read prompt file: {}", prompt_file))?;

    let effective_model = model
        .map(String::from)
        .or_else(|| std::env::var("WG_MODEL").ok())
        .unwrap_or_else(|| {
            Config::load(workgraph_dir)
                .ok()
                .map(|c| c.resolve_model_for_role(DispatchRole::TaskAgent).model)
                .unwrap_or_else(|| "sonnet".to_string())
        });

    // Resolve the working directory (parent of .wg/)
    let working_dir = workgraph_dir
        .canonicalize()
        .ok()
        .and_then(|p| p.parent().map(|pp| pp.to_path_buf()))
        .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());

    // Load config for native executor settings
    let config = Config::load(workgraph_dir).unwrap_or_default();

    // Resolve provider/endpoint/key once and thread the same route into
    // helper tools, so summarize/delegate inherit the parent native session.
    let effective_provider = provider
        .map(String::from)
        .or_else(|| std::env::var("WG_LLM_PROVIDER").ok());
    let effective_endpoint = endpoint_name
        .map(String::from)
        .or_else(|| std::env::var("WG_ENDPOINT").ok())
        .or_else(|| endpoint_url.map(String::from))
        .or_else(|| std::env::var("WG_ENDPOINT_URL").ok());
    let effective_api_key = api_key
        .map(String::from)
        .or_else(|| std::env::var("WG_API_KEY").ok());

    // Build the tool registry with config
    let mut registry = ToolRegistry::default_all_with_config_and_routing(
        workgraph_dir,
        &working_dir,
        &config.native_executor,
        HelperRouting::new(
            Some(&effective_model),
            effective_provider.as_deref(),
            effective_endpoint.as_deref(),
            effective_api_key.as_deref(),
        ),
    );

    // Resolve bundle and filter tools
    let system_suffix = if let Some(bundle) = resolve_bundle(exec_mode, workgraph_dir) {
        let suffix = bundle.system_prompt_suffix.clone();
        registry = bundle.filter_registry(registry);
        suffix
    } else {
        String::new()
    };

    // Build full system prompt
    let system_prompt = if system_suffix.is_empty() {
        prompt
    } else {
        format!("{}\n\n{}", prompt, system_suffix)
    };

    // Build output log path
    let output_log = if let Ok(agent_id) = std::env::var("WG_AGENT_ID") {
        workgraph_dir
            .join("agents")
            .join(&agent_id)
            .join("agent.ndjson")
    } else {
        workgraph_dir.join("native-exec.ndjson")
    };

    eprintln!(
        "[native-exec] Starting agent loop for task '{}' with model '{}', exec_mode '{}', max_turns {}",
        task_id, effective_model, exec_mode, max_turns
    );

    // Create the LLM provider (auto-selects by model name).
    // If endpoint_url was passed explicitly, keep it in the environment for
    // any subprocesses the session might spawn.
    if let Some(url) = endpoint_url {
        // SAFETY: native-exec is single-threaded at this point (before tokio runtime creation).
        unsafe { std::env::set_var("WG_ENDPOINT_URL", url) };
    }
    let client = create_provider_ext(
        workgraph_dir,
        &effective_model,
        effective_provider.as_deref(),
        effective_endpoint.as_deref(),
        effective_api_key.as_deref(),
    )?;

    // Check if the model supports tool use
    let model_registry = ModelRegistry::load(workgraph_dir).unwrap_or_default();
    let supports_tools = model_registry.supports_tool_use(&effective_model);
    if !supports_tools {
        eprintln!(
            "[native-exec] Model '{}' does not support tool use, sending requests without tools",
            effective_model
        );
    }

    // Create and run the agent loop
    let journal_path = journal::journal_path(workgraph_dir, task_id);

    // Resolve session summary path: .wg/agents/<agent-id>/session-summary.md
    let session_summary_path = std::env::var("WG_AGENT_ID").ok().map(|agent_id| {
        workgraph_dir
            .join("agents")
            .join(&agent_id)
            .join("session-summary.md")
    });

    // Register this task-agent session in the chat-sessions registry
    // so it's discoverable via `wg chat list`, `wg chat attach
    // task-<id>`, etc. The journal + inbox/outbox live in the
    // existing `output/<task_id>/` dir (keeps legacy tests + readers
    // happy); we expose it under `chat/task-<id>/` via a symlink so
    // the chat surface addresses it uniformly with coordinators and
    // interactive sessions.
    let session_alias = format!("task-{}", task_id);
    let output_dir = workgraph_dir.join("output").join(task_id);
    let _ = std::fs::create_dir_all(&output_dir);
    let chat_link = workgraph_dir.join("chat").join(&session_alias);
    let _ = std::fs::create_dir_all(workgraph_dir.join("chat"));
    if !chat_link.exists() && !chat_link.is_symlink() {
        #[cfg(unix)]
        {
            let target = format!("../output/{}", task_id);
            let _ = std::os::unix::fs::symlink(&target, &chat_link);
        }
    }
    // Register by task-id — task-ids are already unique within a
    // WG, so we use them as the session key directly instead
    // of a fresh UUID.
    let mut reg = workgraph::chat_sessions::load(workgraph_dir).unwrap_or_default();
    reg.sessions.entry(task_id.to_string()).or_insert_with(|| {
        workgraph::chat_sessions::SessionMeta {
            kind: workgraph::chat_sessions::SessionKind::TaskAgent,
            created: chrono::Utc::now().to_rfc3339(),
            aliases: vec![session_alias.clone()],
            label: Some(format!("task {}", task_id)),
            forked_from: None,
            archived_at: None,
        }
    });
    let _ = workgraph::chat_sessions::save(workgraph_dir, &reg);

    let mut agent = AgentLoop::with_tool_support(
        client,
        registry,
        system_prompt,
        max_turns,
        output_log,
        supports_tools,
    )
    .with_journal(journal_path, task_id.to_string())
    .with_resume(!no_resume)
    .with_working_dir(working_dir.clone())
    // Mount the chat surface so `wg chat attach task-<id>` works: a
    // watcher sees tokens land in `.streaming` and final turns in
    // `outbox.jsonl`. `resume_existing=true` here because for an
    // autonomous task agent, any pre-existing inbox messages (from
    // the user interjecting while the task was in flight) SHOULD be
    // consumed, not skipped.
    .with_chat_ref(workgraph_dir.to_path_buf(), session_alias, true)
    .with_workgraph_dir(workgraph_dir.to_path_buf());

    // Add registry entry for cost tracking if available
    let config = Config::load_or_default(workgraph_dir);
    if let Some(entry) = config.registry_lookup(&effective_model) {
        agent = agent.with_registry_entry(entry);
    }

    if let Some(path) = session_summary_path {
        agent = agent.with_session_summary_path(path);
    }

    // Enable mid-turn state injection when we have an agent ID
    if let Ok(agent_id) = std::env::var("WG_AGENT_ID") {
        agent =
            agent.with_state_injection(workgraph_dir.to_path_buf(), task_id.to_string(), agent_id);
    }

    let mut agent = agent;

    // Run the async agent loop
    let rt = tokio::runtime::Runtime::new().context("Failed to create tokio runtime")?;
    let result = rt.block_on(agent.run(&format!(
        "You are working on task '{}'. Complete the task as described in your system prompt. \
         When done, run `wg done {}` with the bash tool. If you genuinely cannot complete the \
         task, run `wg fail {} --reason <what you tried and what blocked you>` with bash.",
        task_id, task_id, task_id
    )))?;

    eprintln!(
        "[native-exec] Agent completed: {} turns, {}+{} tokens",
        result.turns, result.total_usage.input_tokens, result.total_usage.output_tokens
    );

    Ok(())
}
