use chrono::NaiveDate;
use serde_json::Value as JsonValue;

#[derive(Debug, PartialEq, Clone, sqlx::FromRow)]
pub struct Invoice {
    pub invoice_id: i64,
    pub order_id: i64,
    pub invoice_date: NaiveDate,
    pub payment_method_name: String,
    pub payment_transaction_id: String,
    pub payment_instructions: Option<String>,
    pub shipment: Option<JsonValue>,
}

#[derive(Debug)]
pub struct NewInvoice<'a> {
    pub order_id: i64,
    pub invoice_date: NaiveDate,
    pub payment_method_name: &'a str,
    pub payment_transaction_id: &'a str,
    pub payment_instructions: Option<&'a str>,
    pub shipment: Option<&'a JsonValue>,
}

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

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

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

pub async fn insert<'e>(
    invoice: &NewInvoice<'_>,
    conn: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Invoice, sqlx::Error> {
    // deconstruct to not miss new properties in the future
    let NewInvoice {
        order_id,
        invoice_date,
        payment_method_name,
        payment_transaction_id,
        payment_instructions,
        shipment,
    } = invoice;
    sqlx::query_as(
        "
            INSERT INTO invoices (
                order_id,
                invoice_date,
                payment_method_name,
                payment_transaction_id,
                payment_instructions,
                shipment
            ) VALUES (
                $1, $2, $3, $4, $5, $6
            ) RETURNING *
        ",
    )
    .bind(order_id)
    .bind(invoice_date)
    .bind(payment_method_name)
    .bind(payment_transaction_id)
    .bind(payment_instructions)
    .bind(shipment)
    .fetch_one(conn)
    .await
}

impl Invoice {
    pub async fn update_shipment(
        &mut self,
        new_shipment: JsonValue,
        conn: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
    ) -> Result<(), sqlx::Error> {
        sqlx::query("UPDATE invoices SET shipment = $2 WHERE invoice_id = $1")
            .bind(self.invoice_id)
            .bind(&new_shipment)
            .execute(conn)
            .await?;
        self.shipment = Some(new_shipment);
        Ok(())
    }
}
