use chrono::NaiveDateTime;
use serde::{Serialize, Deserialize};
use diesel::{
    self,
    Queryable,
    Insertable
};
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use uuid::Uuid;
use crate::ShopsterError;
use crate::schema::*;
use crate::aquire_pool;




#[derive(Debug, Serialize, Deserialize, Identifiable, PartialEq, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = products)]
pub struct DbProduct {
    pub id: i64,
    pub article_number: String,
    pub gtin: String,
    pub title: String,
    pub short_description: String,
    pub description: String,
    pub tags: String,
    pub title_image: String,
    pub additional_images: String,
    pub price: i64,
    pub currency: String,
    pub weight: i32,
    pub created_at: NaiveDateTime,
    pub updated_at: Option<NaiveDateTime>
}

#[derive(Debug, Serialize, Deserialize, Insertable)]
#[diesel(table_name = products)]
pub struct InsertableDbProduct {
    pub article_number: String,
    pub gtin: String,
    pub title: String,
    pub short_description: String,
    pub description: String,
    pub tags: String,
    pub title_image: String,
    pub additional_images: String,
    pub price: i64,
    pub currency: String,
    pub weight: i32,
    pub created_at: NaiveDateTime,
    pub updated_at: Option<NaiveDateTime>
}

impl From<&DbProduct> for InsertableDbProduct {
    fn from(product: &DbProduct) -> Self {
        InsertableDbProduct {
            title: product.title.clone(),
            gtin: product.gtin.clone(),
            article_number: product.article_number.clone(),
            short_description: product.short_description.clone(),
            description: product.description.clone(),
            price: product.price,
            currency: product.currency.clone(),
            tags: product.tags.clone(),
            title_image: product.title_image.clone(),
            additional_images: product.additional_images.clone(),
            weight: product.weight,
            created_at: product.created_at,
            updated_at: product.updated_at
        }
    }
}



impl DbProduct {

    pub async fn find(tenant_id: Uuid, id: i64) -> Result<Self, ShopsterError> {
        let pool = aquire_pool(tenant_id).await?;
        let mut conn = pool.get().await.map_err(|e| ShopsterError::DatabaseConnectionError(e.to_string()))?;

        let product = products::table
            .filter(products::id.eq(id))
            .first(&mut conn).await?;
        Ok(product)
    }

    pub async fn get_all(tenant_id: Uuid) -> Result<Vec<Self>, ShopsterError> {
        let pool = aquire_pool(tenant_id).await?;
        let mut conn = pool.get().await.map_err(|e| ShopsterError::DatabaseConnectionError(e.to_string()))?;

        let products = products::table.load(&mut conn).await?;
        Ok(products)
    }

    pub async fn create(tenant_id: Uuid, product: DbProduct) -> Result<Self, ShopsterError> {
        let pool = aquire_pool(tenant_id).await?;
        let mut conn = pool.get().await.map_err(|e| ShopsterError::DatabaseConnectionError(e.to_string()))?;

        let insertable = InsertableDbProduct::from(&product);
        let db_product = diesel::insert_into(products::table)
            .values(insertable)
            .get_result(&mut conn).await?;
        Ok(db_product)
    }

    pub async fn update(tenant_id: Uuid, id: i64, product: DbProduct) -> Result<Self, ShopsterError> {
        let pool = aquire_pool(tenant_id).await?;
        let mut conn = pool.get().await.map_err(|e| ShopsterError::DatabaseConnectionError(e.to_string()))?;

        let db_product = diesel::update(products::table)
            .filter(products::id.eq(id))
            .set(product)
            .get_result(&mut conn).await?;
        Ok(db_product)
    }

    pub async fn delete(tenant_id: Uuid, id: i64) -> Result<usize, ShopsterError> {
        let pool = aquire_pool(tenant_id).await?;
        let mut conn = pool.get().await.map_err(|e| ShopsterError::DatabaseConnectionError(e.to_string()))?;

        let res = diesel::delete(
                products::table
                    .filter(products::id.eq(id))
            )
            .execute(&mut conn).await?;
        Ok(res)
    }
}
