use std::sync::Arc;

use axum::extract::{Query, State};
use axum::http::header;
use axum::response::{IntoResponse, Response};

use crate::state::AppState;

#[derive(serde::Deserialize)]
pub struct FeedQuery {
    pub preset: Option<String>,
}

pub async fn atom_feed(
    State(state): State<Arc<AppState>>,
    Query(q): Query<FeedQuery>,
) -> Response {
    let entries = get_recent_dailies(&state, q.preset.as_deref()).await;
    let feed = build_atom(&entries);
    ([(header::CONTENT_TYPE, "application/atom+xml; charset=utf-8")], feed).into_response()
}

pub async fn rss_feed(
    State(state): State<Arc<AppState>>,
    Query(q): Query<FeedQuery>,
) -> Response {
    let entries = get_recent_dailies(&state, q.preset.as_deref()).await;
    let feed = build_rss(&entries);
    ([(header::CONTENT_TYPE, "application/rss+xml; charset=utf-8")], feed).into_response()
}

pub async fn json_feed(
    State(state): State<Arc<AppState>>,
    Query(q): Query<FeedQuery>,
) -> Response {
    let entries = get_recent_dailies(&state, q.preset.as_deref()).await;
    let feed = build_json_feed(&entries);
    ([(header::CONTENT_TYPE, "application/feed+json; charset=utf-8")], feed).into_response()
}

struct FeedEntry {
    date: String,
    preset: String,
    short_id: String,
    width: i64,
    height: i64,
}

async fn get_recent_dailies(state: &AppState, preset_filter: Option<&str>) -> Vec<FeedEntry> {
    let dbi = state.db.lock().await;
    let (query, param): (String, Option<String>) = if let Some(preset) = preset_filter {
        ("SELECT daily_date, preset, short_id, width, height FROM puzzles WHERE daily_date IS NOT NULL AND preset = ?1 ORDER BY daily_date DESC LIMIT 14".into(), Some(preset.to_string()))
    } else {
        ("SELECT daily_date, preset, short_id, width, height FROM puzzles WHERE daily_date IS NOT NULL ORDER BY daily_date DESC LIMIT 28".into(), None)
    };

    let mut stmt = dbi.db.prepare(&query).unwrap();
    let rows = if let Some(p) = &param {
        stmt.query_map(rusqlite::params![p], |row| {
            Ok(FeedEntry {
                date: row.get::<_, String>(0)?,
                preset: row.get::<_, String>(1)?,
                short_id: row.get::<_, String>(2)?,
                width: row.get::<_, i64>(3)?,
                height: row.get::<_, i64>(4)?,
            })
        }).unwrap().filter_map(|r| r.ok()).collect()
    } else {
        stmt.query_map([], |row| {
            Ok(FeedEntry {
                date: row.get::<_, String>(0)?,
                preset: row.get::<_, String>(1)?,
                short_id: row.get::<_, String>(2)?,
                width: row.get::<_, i64>(3)?,
                height: row.get::<_, i64>(4)?,
            })
        }).unwrap().filter_map(|r| r.ok()).collect()
    };
    rows
}

fn build_atom(entries: &[FeedEntry]) -> String {
    let mut xml = String::from(r#"<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>cruciverb daily puzzles</title>
  <id>urn:cruciverb:daily</id>
  <updated>"#);

    if let Some(e) = entries.first() {
        xml.push_str(&format!("{}T00:00:00Z", e.date));
    }
    xml.push_str("</updated>\n");

    for e in entries {
        xml.push_str(&format!(
            r#"  <entry>
    <title>cruciverb {} - {} - {}x{}</title>
    <id>urn:cruciverb:{}-{}</id>
    <link href="/p/{}" />
    <published>{}T00:00:00Z</published>
    <summary>{}x{} {} crossword puzzle</summary>
  </entry>
"#,
            e.preset, e.date, e.width, e.height,
            e.preset, e.date,
            e.short_id,
            e.date,
            e.width, e.height, e.preset,
        ));
    }
    xml.push_str("</feed>\n");
    xml
}

fn build_rss(entries: &[FeedEntry]) -> String {
    let mut xml = String::from(r#"<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
  <title>cruciverb daily puzzles</title>
  <description>daily crossword puzzles</description>
"#);

    for e in entries {
        xml.push_str(&format!(
            r#"  <item>
    <title>cruciverb {} - {} - {}x{}</title>
    <link>/p/{}</link>
    <guid>cruciverb-{}-{}</guid>
    <description>{}x{} {} crossword puzzle</description>
  </item>
"#,
            e.preset, e.date, e.width, e.height,
            e.short_id,
            e.preset, e.date,
            e.width, e.height, e.preset,
        ));
    }
    xml.push_str("</channel>\n</rss>\n");
    xml
}

fn build_json_feed(entries: &[FeedEntry]) -> String {
    let items: Vec<serde_json::Value> = entries.iter().map(|e| {
        serde_json::json!({
            "id": format!("cruciverb-{}-{}", e.preset, e.date),
            "title": format!("cruciverb {} - {} - {}x{}", e.preset, e.date, e.width, e.height),
            "url": format!("/p/{}", e.short_id),
            "content_text": format!("{}x{} {} crossword puzzle", e.width, e.height, e.preset),
            "date_published": format!("{}T00:00:00Z", e.date),
        })
    }).collect();

    serde_json::json!({
        "version": "https://jsonfeed.org/version/1.1",
        "title": "cruciverb daily puzzles",
        "items": items,
    }).to_string()
}
