rssume 0.2.0

RSS middleware with AI-powered translation and summarization
use crate::monitor::{LogStatus, Monitor};
use axum::{Extension, Json, Router, routing::get};
use serde::Serialize;
use std::sync::Arc;

#[derive(Clone)]
pub struct AppState {
    pub config: Arc<tokio::sync::RwLock<crate::config::Config>>,
    pub monitor: Arc<tokio::sync::RwLock<Monitor>>,
}

pub fn router(state: Arc<AppState>) -> Router {
    Router::new()
        .route("/api/stats", get(get_stats))
        .route("/api/feeds", get(list_feeds))
        .route("/api/monitor/status", get(monitor_status))
        .route("/api/monitor/translating", get(monitor_translating))
        .route("/api/monitor/logs/{name}", get(monitor_logs))
        .route("/api/token-usage", get(token_usage))
        .layer(Extension(state))
}

#[derive(Serialize)]
struct ApiStats {
    feeds: Vec<crate::storage::FeedStats>,
    total_articles: usize,
    total_translated: usize,
    total_with_summary: usize,
    total_prompt_tokens: u64,
    total_completion_tokens: u64,
}

async fn get_stats(
    Extension(s): Extension<Arc<AppState>>,
) -> Result<Json<ApiStats>, crate::error::AppError> {
    let stats = crate::storage::all_feed_stats()?;
    let tu = &s.monitor.read().await.token_usage;
    Ok(Json(ApiStats {
        total_articles: stats.iter().map(|x| x.article_count).sum(),
        total_translated: stats.iter().map(|x| x.translated_count).sum(),
        total_with_summary: stats.iter().map(|x| x.with_summary_count).sum(),
        total_prompt_tokens: tu.total_prompt_tokens,
        total_completion_tokens: tu.total_completion_tokens,
        feeds: stats,
    }))
}

#[derive(Serialize)]
struct FeedInfo {
    name: String,
    url: String,
    enabled: bool,
    interval_secs: u64,
}

async fn list_feeds(Extension(s): Extension<Arc<AppState>>) -> Json<Vec<FeedInfo>> {
    let c = s.config.read().await;
    Json(
        c.feeds
            .iter()
            .map(|f| FeedInfo {
                name: f.name.clone(),
                url: f.url.clone(),
                enabled: f.enabled,
                interval_secs: f.interval_secs,
            })
            .collect(),
    )
}

async fn monitor_status(Extension(s): Extension<Arc<AppState>>) -> Json<Vec<serde_json::Value>> {
    let mon = s.monitor.read().await;
    let cfg = s.config.read().await;
    Json(
        cfg.feeds
            .iter()
            .map(|f| {
                let rt = mon.feeds.get(&f.name);
                let d = crate::storage::FeedData::load(&f.name).ok();
                serde_json::json!({
                    "name": f.name,
                    "url": f.url,
                    "enabled": f.enabled,
                    "status": rt.map(|r| &r.status),
                    "last_fetch_at": rt.and_then(|r| r.last_fetch_at.as_ref()),
                    "last_fetch_error": rt.and_then(|r| r.last_fetch_error.as_ref()),
                    "last_poll_duration_ms": rt.map(|r| r.last_poll_duration_ms).unwrap_or(0),
                    "articles": d.as_ref().map(|d| d.article_count()).unwrap_or(0),
                    "translated": d.as_ref().map(|d| d.translated_count()).unwrap_or(0),
                    "summarized": d.as_ref().map(|d| d.with_summary_count()).unwrap_or(0),
                })
            })
            .collect(),
    )
}

async fn monitor_translating(Extension(s): Extension<Arc<AppState>>) -> Json<serde_json::Value> {
    let mon = s.monitor.read().await;
    let cfg = s.config.read().await;
    let feeds_status: Vec<_> = cfg
        .feeds
        .iter()
        .filter_map(|f| {
            mon.feeds
                .get(&f.name)
                .map(|s| (f.name.clone(), s.status.clone()))
        })
        .collect();
    let active = mon.active_translations();
    let recent_count: usize = mon
        .translation_logs
        .values()
        .map(|l| {
            l.iter()
                .filter(|l| matches!(l.status, LogStatus::Completed | LogStatus::Failed(_)))
                .count()
        })
        .sum();
    Json(serde_json::json!({
        "feeds_status": feeds_status,
        "active": active.iter().map(|(f, l)| serde_json::json!({
            "feed_name": f,
            "log": l,
        })).collect::<Vec<_>>(),
        "recent_count": recent_count,
    }))
}

async fn monitor_logs(
    Extension(s): Extension<Arc<AppState>>,
    axum::extract::Path(name): axum::extract::Path<String>,
) -> Json<Vec<crate::monitor::TranslationLog>> {
    Json(
        s.monitor
            .read()
            .await
            .get_logs(&name)
            .into_iter()
            .cloned()
            .collect(),
    )
}

async fn token_usage(Extension(s): Extension<Arc<AppState>>) -> Json<crate::monitor::TokenUsage> {
    Json(s.monitor.read().await.token_usage.clone())
}