//! synops-agent — modell-agnostisk agent-runtime.
//!
//! Erstatter Claude Code. Støtter OpenRouter, Anthropic, Gemini,
//! xAI, OpenAI og Ollama. Tool-loop med fil-operasjoner og shell.
//!
//! Bruk:
//!   synops-agent --model openrouter/anthropic/claude-sonnet-4 --task "fiks buggen"
//!   synops-agent --model gemini/gemini-2.5-flash --task "oppsummer denne filen"
//!   synops-agent --model ollama/llama3 --task "skriv en test"

mod provider;
mod tools;

use clap::Parser;
use provider::{ApiKeys, CompletionResponse, Message, TokenUsage, create_provider};
use std::collections::HashMap;
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "synops-agent", about = "Modell-agnostisk agent-runtime for Synops")]
struct Cli {
    /// Modell: "provider/model" (f.eks. "openrouter/anthropic/claude-sonnet-4")
    #[arg(short, long, default_value = "openrouter/anthropic/claude-sonnet-4")]
    model: String,

    /// Oppgave å utføre
    #[arg(short, long)]
    task: String,

    /// System-prompt (valgfritt, legges til før oppgaven)
    #[arg(short, long)]
    system: Option<String>,

    /// Arbeidsmappe (default: gjeldende)
    #[arg(short = 'd', long, default_value = ".")]
    working_dir: PathBuf,

    /// Maks antall tool-loop iterasjoner
    #[arg(long, default_value = "50")]
    max_iterations: usize,

    /// Vis token-forbruk underveis
    #[arg(long)]
    verbose: bool,

    /// Spawn Claude Code i stedet (bruker abonnement)
    #[arg(long)]
    claude: bool,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::from_default_env()
                .add_directive("synops_agent=info".parse().unwrap()),
        )
        .init();

    let cli = Cli::parse();

    // Spawn Claude Code direkte hvis --claude
    if cli.claude {
        return spawn_claude_code(&cli.task, &cli.working_dir).await;
    }

    let api_keys = ApiKeys::from_env();
    let provider = create_provider(&cli.model, &api_keys)?;

    tracing::info!(
        model = provider.model_id(),
        provider = provider.name(),
        "Starter agent"
    );

    // Build system prompt
    let system_prompt = build_system_prompt(cli.system.as_deref(), &cli.working_dir);

    // Initialize messages
    let mut messages = vec![
        Message {
            role: "system".into(),
            content: Some(system_prompt),
            tool_calls: None,
            tool_call_id: None,
        },
        Message {
            role: "user".into(),
            content: Some(cli.task.clone()),
            tool_calls: None,
            tool_call_id: None,
        },
    ];

    let tool_defs = tools::tool_definitions();

    // Token accounting
    let mut total_usage: HashMap<String, TokenUsage> = HashMap::new();
    let mut iteration = 0;

    // === Agent loop ===
    loop {
        iteration += 1;
        if iteration > cli.max_iterations {
            tracing::warn!("Nådde maks iterasjoner ({})", cli.max_iterations);
            break;
        }

        // Call LLM
        let response: CompletionResponse = provider.complete(&messages, &tool_defs).await?;

        // Accumulate token usage
        let entry = total_usage
            .entry(response.model.clone())
            .or_insert_with(TokenUsage::default);
        entry.input_tokens += response.usage.input_tokens;
        entry.output_tokens += response.usage.output_tokens;

        if cli.verbose {
            tracing::info!(
                iteration,
                input = response.usage.input_tokens,
                output = response.usage.output_tokens,
                "LLM-kall"
            );
        }

        // Check for tool calls
        let has_tool_calls = response
            .message
            .tool_calls
            .as_ref()
            .map(|tc| !tc.is_empty())
            .unwrap_or(false);

        // Print any text response
        if let Some(ref text) = response.message.content {
            if !text.is_empty() {
                println!("{}", text);
            }
        }

        // Add assistant message to history
        messages.push(response.message.clone());

        if !has_tool_calls {
            // No tool calls — agent is done
            break;
        }

        // Execute tool calls
        let tool_calls = response.message.tool_calls.unwrap();
        for tc in &tool_calls {
            let args: serde_json::Value =
                serde_json::from_str(&tc.function.arguments).unwrap_or_default();

            tracing::info!(
                tool = tc.function.name,
                "Kjører verktøy"
            );

            let result = tools::execute_tool(&tc.function.name, &args, &cli.working_dir).await;

            if cli.verbose {
                let preview = if result.len() > 200 {
                    format!("{}...", &result[..200])
                } else {
                    result.clone()
                };
                tracing::info!(tool = tc.function.name, result = preview, "Resultat");
            }

            // Add tool result to messages
            messages.push(Message {
                role: "tool".into(),
                content: Some(result),
                tool_calls: None,
                tool_call_id: Some(tc.id.clone()),
            });
        }
    }

    // Print token summary
    eprintln!("\n--- Token-forbruk ---");
    let mut total_in = 0u64;
    let mut total_out = 0u64;
    for (model, usage) in &total_usage {
        eprintln!(
            "  {}: {} inn / {} ut",
            model, usage.input_tokens, usage.output_tokens
        );
        total_in += usage.input_tokens;
        total_out += usage.output_tokens;
    }
    eprintln!("  Totalt: {} inn / {} ut", total_in, total_out);
    eprintln!("  Iterasjoner: {}", iteration);

    Ok(())
}

/// Build system prompt with context about the project.
fn build_system_prompt(custom: Option<&str>, working_dir: &Path) -> String {
    let mut prompt = String::new();

    prompt.push_str("Du er synops-agent, en autonom utviklerassistent for Synops-plattformen.\n");
    prompt.push_str("Du har tilgang til verktøy for å lese, skrive og redigere filer, ");
    prompt.push_str("kjøre shell-kommandoer, og søke i koden.\n\n");
    prompt.push_str("Arbeidsmappe: ");
    prompt.push_str(&working_dir.display().to_string());
    prompt.push_str("\n\n");

    // Try to read CLAUDE.md for project context
    let claude_md = working_dir.join("CLAUDE.md");
    if claude_md.exists() {
        if let Ok(content) = std::fs::read_to_string(&claude_md) {
            // Truncate if very long
            let truncated = if content.len() > 8000 {
                format!("{}...\n(truncated)", &content[..8000])
            } else {
                content
            };
            prompt.push_str("# Prosjektkontekst (fra CLAUDE.md)\n\n");
            prompt.push_str(&truncated);
            prompt.push_str("\n\n");
        }
    }

    if let Some(custom) = custom {
        prompt.push_str("# Tilleggsinstruksjoner\n\n");
        prompt.push_str(custom);
        prompt.push_str("\n\n");
    }

    prompt.push_str("Regler:\n");
    prompt.push_str("- Les relevante filer før du endrer dem\n");
    prompt.push_str("- Gjør minimale, fokuserte endringer\n");
    prompt.push_str("- Test at kode kompilerer (cargo check, npm run build)\n");
    prompt.push_str("- Commit og push når oppgaven er ferdig\n");

    prompt
}

/// Spawn Claude Code for heavy tasks (uses paid subscription).
async fn spawn_claude_code(
    task: &str,
    working_dir: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
    tracing::info!("Spawner Claude Code for oppgaven");

    let output = tokio::process::Command::new("claude")
        .arg("-p")
        .arg(task)
        .arg("--dangerously-skip-permissions")
        .current_dir(working_dir)
        .output()
        .await?;

    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    if !stdout.is_empty() {
        println!("{}", stdout);
    }
    if !stderr.is_empty() {
        eprintln!("{}", stderr);
    }

    if !output.status.success() {
        eprintln!("Claude Code avsluttet med kode: {}", output.status.code().unwrap_or(-1));
    }

    Ok(())
}

use std::path::Path;
