//! Non-interactive `lucid-train exec "task"` runner — the harness mode used
//! for benchmark-style runs. Streams Codex-style output to stdout and exits 0
//! on success.

use crate::agent::{Agent, AgentEvent, ApprovalDecision, ApprovalRegistry};
use crate::config::{ApprovalPolicy, Config};
use std::io::{IsTerminal, Write};
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;

struct Paint {
    on: bool,
}

impl Paint {
    fn dim(&self, s: &str) -> String {
        if self.on { format!("\x1b[2m{s}\x1b[0m") } else { s.into() }
    }
    fn bold(&self, s: &str) -> String {
        if self.on { format!("\x1b[1m{s}\x1b[0m") } else { s.into() }
    }
    fn green(&self, s: &str) -> String {
        if self.on { format!("\x1b[32m{s}\x1b[0m") } else { s.into() }
    }
    fn red(&self, s: &str) -> String {
        if self.on { format!("\x1b[31m{s}\x1b[0m") } else { s.into() }
    }
    fn magenta(&self, s: &str) -> String {
        if self.on { format!("\x1b[35m{s}\x1b[0m") } else { s.into() }
    }
    fn cyan(&self, s: &str) -> String {
        if self.on { format!("\x1b[36m{s}\x1b[0m") } else { s.into() }
    }
}

pub async fn run_exec(mut cfg: Config, task: String) -> i32 {
    // Non-interactive runs cannot prompt; default to full-auto unless the user
    // explicitly chose plan (which then denies everything non-safe).
    if cfg.approval == ApprovalPolicy::Auto {
        cfg.approval = ApprovalPolicy::FullAuto;
    }
    let p = Paint { on: std::io::stdout().is_terminal() };

    println!("{}", p.dim("╭──────────────────────────────────────────────╮"));
    println!(
        "{} {} {}",
        p.dim("│"),
        p.bold(&format!(">_ Lucid Train v{} (exec)", env!("CARGO_PKG_VERSION"))),
        p.dim("│")
    );
    println!("{}", p.dim("╰──────────────────────────────────────────────╯"));
    println!("{} {}", p.dim("model:"), cfg.model);
    println!("{} {}", p.dim("cwd:  "), cfg.cwd.display());
    println!();
    println!("{} {}", p.cyan("›"), task);
    println!();

    let mut agent = Agent::new(cfg);
    let approvals = agent.approvals();
    let (tx, mut rx) = mpsc::unbounded_channel::<AgentEvent>();
    let cancel = CancellationToken::new();

    let cancel_for_ctrlc = cancel.clone();
    tokio::spawn(async move {
        let _ = tokio::signal::ctrl_c().await;
        cancel_for_ctrlc.cancel();
    });

    let turn = {
        let cancel = cancel.clone();
        async move {
            agent.run_turn(task, &tx, &cancel).await;
            agent.bg.shutdown().await;
        }
    };
    tokio::pin!(turn);

    let mut failed = false;
    let mut streamed_anything = false;
    let mut tokens = (0u64, 0u64);
    loop {
        tokio::select! {
            _ = &mut turn => break,
            ev = rx.recv() => {
                let Some(ev) = ev else { break };
                streamed_anything |= handle_event(ev, &p, &approvals, &mut failed, &mut tokens);
            }
        }
    }
    // Drain remaining events.
    while let Ok(ev) = rx.try_recv() {
        streamed_anything |= handle_event(ev, &p, &approvals, &mut failed, &mut tokens);
    }
    let _ = streamed_anything;
    if tokens.0 > 0 || tokens.1 > 0 {
        println!(
            "\n{}",
            p.dim(&format!("tokens: {} in • {} out", tokens.0, tokens.1))
        );
    }
    if failed { 1 } else { 0 }
}

fn handle_event(
    ev: AgentEvent,
    p: &Paint,
    approvals: &ApprovalRegistry,
    failed: &mut bool,
    tokens: &mut (u64, u64),
) -> bool {
    match ev {
        AgentEvent::AssistantDelta { text } => {
            print!("{text}");
            let _ = std::io::stdout().flush();
            true
        }
        AgentEvent::ReasoningDelta { .. } => false,
        AgentEvent::AssistantDone => {
            println!();
            false
        }
        AgentEvent::ToolBegin { command, background, .. } => {
            let label = if background { "Running (bg)" } else { "Running" };
            println!("\n{} {}", p.magenta("•"), p.bold(&format!("{label} {command}")));
            false
        }
        AgentEvent::ToolEnd { output, exit_code, duration_ms, success, .. } => {
            let bullet = if success { p.green("•") } else { p.red("•") };
            let dur = if duration_ms > 0 {
                format!(" {}", p.dim(&format!("• {:.1}s", duration_ms as f64 / 1000.0)))
            } else {
                String::new()
            };
            let status = if success {
                "done".to_string()
            } else {
                format!("exit {exit_code}")
            };
            println!("{bullet} {}{dur}", p.dim(&status));
            for line in output.lines().take(12) {
                println!("  {} {}", p.dim("└"), p.dim(line));
            }
            let n = output.lines().count();
            if n > 12 {
                println!("  {} {}", p.dim("└"), p.dim(&format!("… +{} lines", n - 12)));
            }
            false
        }
        AgentEvent::ApprovalRequest { request_id, command, .. } => {
            // exec mode is headless: deny anything that still requires approval
            // (only reachable in plan mode).
            println!("{} approval required for `{command}` — denied (headless run)", p.red("!"));
            let _ = approvals.respond(&request_id, ApprovalDecision::Deny);
            false
        }
        AgentEvent::Notice { text } => {
            println!("{}", p.dim(&format!("◦ {text}")));
            false
        }
        AgentEvent::ModelUnavailable { model, needs_key } => {
            eprintln!("{} model '{model}' not found at provider (404).", p.red("error:"));
            if needs_key {
                eprintln!("  set OPENROUTER_API_KEY (or --api-key), or use a local model: -p ollama -m <tag>");
            } else {
                eprintln!("  pick a valid model id (lucid-train models) or a local one.");
            }
            *failed = true;
            false
        }
        AgentEvent::CodexReauth { reason } => {
            eprintln!("{} {reason} — Codex session expired.", p.red("error:"));
            eprintln!("  refresh it interactively (run `lucid-train`, then /login) or via the Codex CLI.");
            *failed = true;
            false
        }
        AgentEvent::FileEdit { path, op, added, removed, lines } => {
            println!("\n{} {}{}", p.green("●"), p.bold(op), p.bold(&format!("({path})")));
            println!("  {} {}", p.dim("└"), p.dim(&format!("Added {added} lines, removed {removed}")));
            for dl in lines.iter().take(40) {
                use crate::tools::edit::DiffKind;
                let (sign, line) = match dl.kind {
                    DiffKind::Added => ("+", p.green(&format!("+ {}", dl.text))),
                    DiffKind::Removed => ("-", p.red(&format!("- {}", dl.text))),
                    DiffKind::Context => (" ", p.dim(&format!("  {}", dl.text))),
                };
                let _ = sign;
                println!("    {line}");
            }
            false
        }
        AgentEvent::Plan { steps } => {
            println!("{}", p.bold("plan:"));
            for (step, status) in steps {
                let mark = match status.as_str() {
                    "completed" => p.green("✓"),
                    "in_progress" => p.cyan("→"),
                    _ => p.dim("○"),
                };
                println!("  {mark} {step}");
            }
            false
        }
        AgentEvent::SubagentStatus { id, task, state } => {
            let icon = match state.as_str() {
                "finished" => p.green("●"),
                "failed" => p.red("✕"),
                _ => p.cyan("◐"),
            };
            println!("{} explorer {} {} — {}", icon, id + 1, state, task);
            false
        }
        AgentEvent::Usage { session_input, session_output, .. } => {
            *tokens = (session_input, session_output);
            false
        }
        AgentEvent::TurnComplete => false,
        AgentEvent::Error { message } => {
            eprintln!("{} {message}", p.red("error:"));
            *failed = true;
            false
        }
    }
}
