use actix_web::{web, HttpResponse};

use crate::pages::templates::ATOM_TURNS;
use crate::{DataBaseRo, WebData};

#[derive(serde_derive::Serialize)]
struct FeedData {
    gameuid: String,
    gameuidenc: String,
    upddatetime: String,
    entries: Vec<EntriesData>,
    selflink: String,
}

#[derive(serde_derive::Serialize)]
struct EntriesData {
    new: bool,
    turn: i32,
    turn_ts_str: String,
    turn_ts: i64,
}

#[actix_web::get("/atoms/{path}.xml")]
pub async fn atom_turns(
    path: web::Path<String>,
    data: web::Data<WebData<'_>>,
    data_ro: web::Data<DataBaseRo>,
) -> HttpResponse {
    let gameuid = path.into_inner();
    let gameuid = match gameuid.char_indices().nth(128) {
        None => gameuid,
        Some((idx, _)) => gameuid[..idx].to_string(),
    };

    let dbclient = match data_ro.0.get().await {
        Ok(client) => client,
        Err(e) => {
            log::error!("{}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };

    let stmt = match dbclient.prepare("select g.game_uid, t.turn, t.turn_ts from games.games g left join games.turns t on t.game_uid = g.game_uid where g.game_uid = $1 order by t.turn_ts desc limit 50;").await {
        Ok(stmt) => stmt,
        Err(e) => {
            log::error!("{}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };

    let rows = match dbclient.query(&stmt, &[&gameuid]).await {
        Ok(rows) => rows,
        Err(e) => {
            log::error!("{}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };

    if rows.is_empty() {
        log::info!("Empty data for {}", gameuid);
        return HttpResponse::NotFound().body(actix_web::body::None::new());
    }

    let mut upd_date_time = None;

    let mut entries = Vec::with_capacity(rows.len());

    for row in rows {
        if let Some(turn) = row.get::<_, Option<i32>>(1) {
            if let Some(turn_ts) = row.get::<_, Option<chrono::NaiveDateTime>>(2) {
                use chrono::TimeZone;
                let entry_ts = chrono::Utc::from_utc_datetime(&chrono::Utc, &turn_ts);

                entries.insert(
                    0,
                    EntriesData {
                        new: turn <= 0,
                        turn,
                        turn_ts_str: entry_ts.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
                        turn_ts: entry_ts.timestamp(),
                    },
                );

                upd_date_time.get_or_insert(entry_ts);
            }
        }
    }

    let upd_date_time = upd_date_time.unwrap_or_else(chrono::Utc::now);

    let body = match data.handlebars_xml.render(
        ATOM_TURNS,
        &FeedData {
            gameuid: gameuid.to_string(),
            gameuidenc: pct_str::PctString::encode(gameuid.chars(), pct_str::URIReserved)
                .into_string(),
            entries,
            upddatetime: upd_date_time.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
            selflink: format!("{}://{}/", data.base_proto, data.base_domain),
        },
    ) {
        Ok(b) => b,
        Err(e) => {
            log::error!("{}", e);
            return HttpResponse::ServiceUnavailable().body(actix_web::body::None::new());
        }
    };
    HttpResponse::Ok()
        .insert_header(("Content-Type", "application/atom+xml"))
        .insert_header((
            "Date",
            upd_date_time
                .format("%a, %d %b %Y %H:%M:%S GMT")
                .to_string(),
        ))
        .body(body)
}