use std::collections::HashMap;

use crate::cents::Cents;
use serde_json::Value as JsonValue;

#[derive(Debug, PartialEq, Clone, sqlx::FromRow)]
pub struct LineItemOption {
    pub id: i64,
    pub line_item_id: i64,
    pub sku: String,
    pub name: String,
    pub value: JsonValue,
    pub price: Cents,
}

pub async fn fetch<'e>(
    id: i64,
    conn: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Option<LineItemOption>, sqlx::Error> {
    sqlx::query_as("SELECT * FROM line_item_options WHERE id = $1")
        .bind(id)
        .fetch_optional(conn)
        .await
}

pub async fn by_line_item_ids<'e>(
    line_item_ids: Vec<i64>,
    conn: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<HashMap<i64, Vec<LineItemOption>>, sqlx::Error> {
    let options =
        sqlx::query_as("SELECT * FROM line_item_options WHERE line_item_id = ANY($1) ORDER BY id")
            .bind(line_item_ids)
            .fetch_all(conn)
            .await?
            .into_iter()
            .fold(HashMap::new(), |mut map, o: LineItemOption| {
                map.entry(o.line_item_id).or_insert_with(Vec::new).push(o);
                map
            });
    Ok(options)
}

#[derive(Debug)]
pub struct NewLineItemOption<'a> {
    pub line_item_id: i64,
    pub sku: &'a str,
    pub name: &'a str,
    pub value: JsonValue,
    pub price: Cents,
}

pub async fn insert<'e>(
    line_item_option: &NewLineItemOption<'_>,
    conn: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<LineItemOption, sqlx::Error> {
    // deconstruct to not miss new properties in the future
    let NewLineItemOption {
        line_item_id,
        sku,
        name,
        value,
        price,
    } = line_item_option;

    sqlx::query_as(
        "
            INSERT INTO line_item_options (
                line_item_id,
                sku,
                name,
                value,
                price
            ) VALUES (
                $1, $2, $3, $4, $5
            )
            RETURNING *
        ",
    )
    .bind(line_item_id)
    .bind(sku)
    .bind(name)
    .bind(value)
    .bind(price)
    .fetch_one(conn)
    .await
}
