use std::collections::HashMap;
use serde_json::Value;
use crate::error::Result;
use crate::sql_builder::SqlBuilder;
#[derive(Debug, Clone, Default)]
pub struct SearchSetsParams {
pub name: Option<String>,
pub set_type: Option<String>,
pub block: Option<String>,
pub release_year: Option<i32>,
pub limit: Option<usize>,
pub offset: Option<usize>,
}
pub struct SetQuery<'a> {
conn: &'a crate::connection::Connection,
}
impl<'a> SetQuery<'a> {
pub fn new(conn: &'a crate::connection::Connection) -> Self {
Self { conn }
}
pub fn get(&self, code: &str) -> Result<Option<Value>> {
self.conn.ensure_views(&["sets"])?;
let upper = code.to_uppercase();
let (sql, params) = SqlBuilder::new("sets")
.where_eq("code", &upper)
.limit(1)
.build();
let rows = self.conn.execute(&sql, ¶ms)?;
Ok(rows.into_iter().next().map(|r| serde_json::to_value(r).unwrap_or(Value::Null)))
}
pub fn list(
&self,
set_type: Option<&str>,
name: Option<&str>,
limit: Option<usize>,
offset: Option<usize>,
) -> Result<Vec<Value>> {
self.conn.ensure_views(&["sets"])?;
let mut qb = SqlBuilder::new("sets");
qb.order_by(&["releaseDate DESC"]);
if let Some(st) = set_type {
qb.where_eq("type", st);
}
if let Some(n) = name {
qb.where_like("name", &format!("%{}%", n));
}
if let Some(l) = limit {
qb.limit(l);
}
if let Some(o) = offset {
qb.offset(o);
}
let (sql, params) = qb.build();
let rows = self.conn.execute(&sql, ¶ms)?;
Ok(rows_to_values(rows))
}
pub fn search(&self, params: &SearchSetsParams) -> Result<Vec<Value>> {
self.conn.ensure_views(&["sets"])?;
let mut qb = SqlBuilder::new("sets");
qb.order_by(&["releaseDate DESC"]);
if let Some(ref name) = params.name {
qb.where_like("name", &format!("%{}%", name));
}
if let Some(ref st) = params.set_type {
qb.where_eq("type", st);
}
if let Some(ref block) = params.block {
qb.where_eq("block", block);
}
if let Some(year) = params.release_year {
qb.where_clause(
"EXTRACT(YEAR FROM CAST(releaseDate AS DATE)) = ?",
&[&year.to_string()],
);
}
let limit = params.limit.unwrap_or(100);
let offset = params.offset.unwrap_or(0);
qb.limit(limit);
qb.offset(offset);
let (sql, sql_params) = qb.build();
let rows = self.conn.execute(&sql, &sql_params)?;
Ok(rows_to_values(rows))
}
pub fn get_financial_summary(
&self,
set_code: &str,
provider: Option<&str>,
currency: Option<&str>,
finish: Option<&str>,
price_type: Option<&str>,
) -> Result<HashMap<String, Value>> {
self.conn.ensure_views(&["cards", "all_prices_today"])?;
let upper = set_code.to_uppercase();
let provider = provider.unwrap_or("tcgplayer");
let currency = currency.unwrap_or("USD");
let finish = finish.unwrap_or("normal");
let price_type = price_type.unwrap_or("retail");
let sql = r#"
SELECT
COUNT(DISTINCT c.uuid) AS card_count,
COALESCE(SUM(p.price), 0) AS total_value,
COALESCE(AVG(p.price), 0) AS avg_value,
COALESCE(MIN(p.price), 0) AS min_value,
COALESCE(MAX(p.price), 0) AS max_value,
MAX(p.date) AS date
FROM cards c
JOIN all_prices_today p ON c.uuid = p.uuid
WHERE c.setCode = ?
AND p.provider = ?
AND p.currency = ?
AND p.finish = ?
AND p.price_type = ?
"#;
let rows = self.conn.execute(
sql,
&[
upper,
provider.to_string(),
currency.to_string(),
finish.to_string(),
price_type.to_string(),
],
)?;
if let Some(row) = rows.into_iter().next() {
Ok(row)
} else {
Ok(HashMap::new())
}
}
pub fn count(&self, set_type: Option<&str>) -> Result<i64> {
self.conn.ensure_views(&["sets"])?;
let mut qb = SqlBuilder::new("sets");
qb.select(&["COUNT(*) AS cnt"]);
if let Some(st) = set_type {
qb.where_eq("type", st);
}
let (sql, params) = qb.build();
let rows = self.conn.execute(&sql, ¶ms)?;
let cnt = rows
.first()
.and_then(|r| r.get("cnt"))
.and_then(|v| v.as_i64())
.unwrap_or(0);
Ok(cnt)
}
}
fn rows_to_values(rows: Vec<HashMap<String, Value>>) -> Vec<Value> {
rows.into_iter()
.map(|r| serde_json::to_value(r).unwrap_or(Value::Null))
.collect()
}