// synops-summarize — AI-oppsummering av kommunikasjonsnode.
//
// Input: --communication-id <uuid>. Henter meldinger og deltakere fra PG,
// sender til LiteLLM for oppsummering, returnerer sammendrag som JSON.
// Med --write: oppretter sammendrag-node og edges i PG.
//
// Miljøvariabler:
//   DATABASE_URL       — PostgreSQL-tilkobling (påkrevd)
//   AI_GATEWAY_URL     — LiteLLM gateway (default: http://localhost:4000)
//   LITELLM_MASTER_KEY — API-nøkkel for LiteLLM
//   AI_SUMMARY_MODEL   — Modellalias (default: synops/low)
//
// Erstatter: maskinrommet/src/summarize.rs
// Ref: docs/retninger/unix_filosofi.md, docs/infra/ai_gateway.md

use clap::Parser;
use serde::{Deserialize, Serialize};
use std::process;
use uuid::Uuid;

/// AI-oppsummering av kommunikasjonsnode via LiteLLM.
#[derive(Parser)]
#[command(name = "synops-summarize", about = "Generer AI-sammendrag av en kommunikasjonsnode")]
struct Cli {
    /// Kommunikasjonsnode-ID som skal oppsummeres
    #[arg(long)]
    communication_id: Uuid,

    /// Bruker-ID som utløste oppsummeringen
    #[arg(long)]
    requested_by: Option<Uuid>,

    /// Skriv sammendrag-node og edges til database (uten: kun generering + stdout)
    #[arg(long)]
    write: bool,
}

// --- Database-rader ---

#[derive(sqlx::FromRow)]
struct MessageRow {
    content: Option<String>,
    created_by: Uuid,
    #[allow(dead_code)]
    created_at: chrono::DateTime<chrono::Utc>,
}

#[derive(sqlx::FromRow)]
struct ParticipantRow {
    id: Uuid,
    title: Option<String>,
}

// --- LLM request/response (OpenAI-kompatibel) ---

#[derive(Serialize)]
struct ChatRequest {
    model: String,
    messages: Vec<ChatMessage>,
    temperature: f32,
}

#[derive(Serialize)]
struct ChatMessage {
    role: String,
    content: String,
}

#[derive(Deserialize)]
struct ChatResponse {
    choices: Vec<Choice>,
    #[serde(default)]
    usage: Option<UsageInfo>,
    #[serde(default)]
    model: Option<String>,
}

#[derive(Deserialize, Clone)]
struct UsageInfo {
    #[serde(default)]
    prompt_tokens: i64,
    #[serde(default)]
    completion_tokens: i64,
}

#[derive(Deserialize)]
struct Choice {
    message: MessageContent,
}

#[derive(Deserialize)]
struct MessageContent {
    content: Option<String>,
}

const SYSTEM_PROMPT: &str = r#"Du er en oppsummeringsassistent for en norsk redaksjonsplattform.

Du mottar en samtalelogg fra en kommunikasjonsnode (chat/møte/diskusjon). Lag et konsist sammendrag som fanger:

1. **Hovedtema:** Hva handlet samtalen om?
2. **Nøkkelpunkter:** De viktigste poengene, beslutningene eller konklusjonene.
3. **Handlingspunkter:** Eventuelle oppgaver, avtaler eller neste steg som ble nevnt.

Regler:
- Skriv på norsk, i prosa (ikke punktlister med mindre det passer naturlig for handlingspunkter).
- Vær konsis — maks 3-4 avsnitt.
- Referer til deltakere ved navn der det er relevant.
- Ikke inkluder metadata, tidsstempler eller systeminfo.
- Hvis samtalen er svært kort eller innholdsløs, skriv en kort setning som oppsummerer det."#;

#[tokio::main]
async fn main() {
    synops_common::logging::init("synops_summarize");

    let cli = Cli::parse();

    if cli.write && cli.requested_by.is_none() {
        eprintln!("Feil: --requested-by er påkrevd sammen med --write");
        process::exit(1);
    }

    if let Err(e) = run(cli).await {
        eprintln!("Feil: {e}");
        process::exit(1);
    }
}

async fn run(cli: Cli) -> Result<(), String> {
    let db = synops_common::db::connect().await?;

    let communication_id = cli.communication_id;

    // 1. Verifiser at kommunikasjonsnoden finnes
    let comm_title: String = sqlx::query_scalar::<_, Option<String>>(
        "SELECT title FROM nodes WHERE id = $1 AND node_kind = 'communication'",
    )
    .bind(communication_id)
    .fetch_optional(&db)
    .await
    .map_err(|e| format!("PG-feil: {e}"))?
    .ok_or_else(|| format!("Kommunikasjonsnode {communication_id} ikke funnet"))?
    .unwrap_or_else(|| "Samtale".to_string());

    tracing::info!(
        communication_id = %communication_id,
        title = %comm_title,
        "Henter meldinger for oppsummering"
    );

    // 2. Hent alle meldinger i samtalen
    let messages = sqlx::query_as::<_, MessageRow>(
        r#"
        SELECT n.content, n.created_by, n.created_at
        FROM nodes n
        JOIN edges e ON e.source_id = n.id
        WHERE e.target_id = $1
          AND e.edge_type = 'belongs_to'
          AND n.node_kind = 'content'
        ORDER BY n.created_at ASC
        "#,
    )
    .bind(communication_id)
    .fetch_all(&db)
    .await
    .map_err(|e| format!("PG-feil ved henting av meldinger: {e}"))?;

    if messages.is_empty() {
        let result = serde_json::json!({
            "status": "skipped",
            "reason": "no_messages",
            "communication_id": communication_id.to_string()
        });
        println!("{}", serde_json::to_string_pretty(&result).unwrap());
        return Ok(());
    }

    // 3. Hent deltakere
    let participants = sqlx::query_as::<_, ParticipantRow>(
        r#"
        SELECT n.id, n.title
        FROM nodes n
        JOIN edges e ON e.source_id = n.id
        WHERE e.target_id = $1
          AND e.edge_type IN ('owner', 'member_of')
        "#,
    )
    .bind(communication_id)
    .fetch_all(&db)
    .await
    .map_err(|e| format!("PG-feil ved henting av deltakere: {e}"))?;

    let name_map: std::collections::HashMap<Uuid, String> = participants
        .iter()
        .map(|p| {
            (
                p.id,
                p.title.clone().unwrap_or_else(|| "Ukjent".to_string()),
            )
        })
        .collect();

    // 4. Bygg samtalelogg for LLM
    let mut conversation = String::new();
    for m in &messages {
        let name = name_map
            .get(&m.created_by)
            .map(|s| s.as_str())
            .unwrap_or("Ukjent");
        let content = m.content.as_deref().unwrap_or("");
        if !content.is_empty() {
            conversation.push_str(&format!("{name}: {content}\n"));
        }
    }

    if conversation.trim().is_empty() {
        let result = serde_json::json!({
            "status": "skipped",
            "reason": "empty_conversation",
            "communication_id": communication_id.to_string()
        });
        println!("{}", serde_json::to_string_pretty(&result).unwrap());
        return Ok(());
    }

    let participant_names: String = participants
        .iter()
        .filter_map(|p| p.title.as_deref())
        .collect::<Vec<_>>()
        .join(", ");

    let user_content = format!(
        "Samtale: \"{comm_title}\"\nDeltakere: {participant_names}\nAntall meldinger: {}\n\n--- Samtalelogg ---\n{conversation}",
        messages.len()
    );

    // 5. Kall LiteLLM
    tracing::info!(message_count = messages.len(), "Sender til LLM for oppsummering");
    let (summary_text, llm_usage, llm_model) = call_llm_summary(&user_content).await?;

    tracing::info!(summary_len = summary_text.len(), "Sammendrag generert");

    // 6. Bygg resultat
    let tokens_in = llm_usage.as_ref().map(|u| u.prompt_tokens).unwrap_or(0);
    let tokens_out = llm_usage.as_ref().map(|u| u.completion_tokens).unwrap_or(0);
    let model_id = llm_model.unwrap_or_else(|| "unknown".to_string());

    let mut result = serde_json::json!({
        "status": "completed",
        "communication_id": communication_id.to_string(),
        "summary": summary_text,
        "message_count": messages.len(),
        "model": model_id,
        "tokens_in": tokens_in,
        "tokens_out": tokens_out,
    });

    // 7. Skriv til database hvis --write
    if cli.write {
        let requested_by = cli.requested_by.unwrap(); // Allerede validert
        let summary_node_id = write_to_db(
            &db,
            communication_id,
            &comm_title,
            &summary_text,
            messages.len(),
            requested_by,
            &model_id,
            tokens_in,
            tokens_out,
        )
        .await?;

        result["summary_node_id"] = serde_json::Value::String(summary_node_id.to_string());
    }

    // 8. Output JSON til stdout
    println!(
        "{}",
        serde_json::to_string_pretty(&result)
            .map_err(|e| format!("JSON-serialisering feilet: {e}"))?
    );

    Ok(())
}

/// Kall LiteLLM for oppsummering. Returnerer (tekst, usage, model).
async fn call_llm_summary(
    user_content: &str,
) -> Result<(String, Option<UsageInfo>, Option<String>), String> {
    let gateway_url =
        std::env::var("AI_GATEWAY_URL").unwrap_or_else(|_| "http://localhost:4000".to_string());
    let api_key = std::env::var("LITELLM_MASTER_KEY").unwrap_or_default();

    let model =
        std::env::var("AI_SUMMARY_MODEL").unwrap_or_else(|_| "synops/low".to_string());

    let request = ChatRequest {
        model,
        messages: vec![
            ChatMessage {
                role: "system".to_string(),
                content: SYSTEM_PROMPT.to_string(),
            },
            ChatMessage {
                role: "user".to_string(),
                content: user_content.to_string(),
            },
        ],
        temperature: 0.3,
    };

    let client = reqwest::Client::new();
    let url = format!("{gateway_url}/v1/chat/completions");

    let resp = client
        .post(&url)
        .header("Authorization", format!("Bearer {api_key}"))
        .header("Content-Type", "application/json")
        .json(&request)
        .timeout(std::time::Duration::from_secs(60))
        .send()
        .await
        .map_err(|e| format!("LiteLLM-kall feilet: {e}"))?;

    if !resp.status().is_success() {
        let status = resp.status();
        let body = resp.text().await.unwrap_or_default();
        return Err(format!("LiteLLM returnerte {status}: {body}"));
    }

    let chat_resp: ChatResponse = resp
        .json()
        .await
        .map_err(|e| format!("Kunne ikke parse LiteLLM-respons: {e}"))?;

    let content = chat_resp
        .choices
        .first()
        .and_then(|c| c.message.content.as_deref())
        .ok_or("LiteLLM returnerte ingen content")?;

    Ok((content.to_string(), chat_resp.usage, chat_resp.model))
}

/// Opprett sammendrag-node, belongs_to-edge, summary-edge og logg ressursforbruk.
/// Returnerer ID-en til den nye sammendrag-noden.
async fn write_to_db(
    db: &sqlx::PgPool,
    communication_id: Uuid,
    comm_title: &str,
    summary_text: &str,
    message_count: usize,
    requested_by: Uuid,
    model_id: &str,
    tokens_in: i64,
    tokens_out: i64,
) -> Result<Uuid, String> {
    let summary_node_id = Uuid::now_v7();
    let summary_title = format!("Sammendrag: {comm_title}");
    let metadata = serde_json::json!({
        "ai_generated": true,
        "source_type": "communication_summary",
        "message_count": message_count,
        "communication_id": communication_id.to_string()
    });

    // Opprett sammendrag-node
    sqlx::query(
        r#"
        INSERT INTO nodes (id, node_kind, title, content, visibility, metadata, created_by)
        VALUES ($1, 'content', $2, $3, 'hidden'::visibility, $4, $5)
        "#,
    )
    .bind(summary_node_id)
    .bind(&summary_title)
    .bind(summary_text)
    .bind(&metadata)
    .bind(requested_by)
    .execute(db)
    .await
    .map_err(|e| format!("PG insert summary-node feilet: {e}"))?;

    tracing::info!(summary_node_id = %summary_node_id, "Sammendrag-node opprettet");

    // belongs_to-edge: sammendrag → kommunikasjonsnode
    let belongs_edge_id = Uuid::now_v7();
    sqlx::query(
        r#"
        INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by)
        VALUES ($1, $2, $3, 'belongs_to', '{}', false, $4)
        "#,
    )
    .bind(belongs_edge_id)
    .bind(summary_node_id)
    .bind(communication_id)
    .bind(requested_by)
    .execute(db)
    .await
    .map_err(|e| format!("PG insert belongs_to-edge feilet: {e}"))?;

    // summary-edge: kommunikasjonsnode → sammendrag
    let summary_edge_id = Uuid::now_v7();
    let summary_edge_meta = serde_json::json!({
        "generated_at": chrono::Utc::now().to_rfc3339()
    });

    sqlx::query(
        r#"
        INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by)
        VALUES ($1, $2, $3, 'summary', $4, false, $5)
        "#,
    )
    .bind(summary_edge_id)
    .bind(communication_id)
    .bind(summary_node_id)
    .bind(&summary_edge_meta)
    .bind(requested_by)
    .execute(db)
    .await
    .map_err(|e| format!("PG insert summary-edge feilet: {e}"))?;

    tracing::info!(
        summary_node_id = %summary_node_id,
        communication_id = %communication_id,
        "Sammendrag knyttet til kommunikasjonsnode"
    );

    // Logg AI-ressursforbruk
    let collection_id: Option<Uuid> = sqlx::query_scalar(
        "SELECT e.target_id FROM edges e
         JOIN nodes n ON n.id = e.target_id
         WHERE e.source_id = $1 AND e.edge_type = 'belongs_to' AND n.node_kind = 'collection'
         LIMIT 1",
    )
    .bind(communication_id)
    .fetch_optional(db)
    .await
    .ok()
    .flatten();

    if let Err(e) = sqlx::query(
        "INSERT INTO resource_usage_log (target_node_id, triggered_by, collection_id, resource_type, detail)
         VALUES ($1, $2, $3, $4, $5)",
    )
    .bind(communication_id)
    .bind(Some(requested_by))
    .bind(collection_id)
    .bind("ai")
    .bind(serde_json::json!({
        "model_level": "smart",
        "model_id": model_id,
        "tokens_in": tokens_in,
        "tokens_out": tokens_out,
        "job_type": "summarize_communication"
    }))
    .execute(db)
    .await
    {
        tracing::warn!(error = %e, "Kunne ikke logge AI-ressursforbruk");
    }

    Ok(summary_node_id)
}
