use serde::{Deserialize, Serialize};
use sqlx::{FromRow, PgPool};
use crate::models::origin::Origin;
use crate::models::variant::{load_article_variants, Variant};
use sqlx::{Arguments, Pool, Row, Postgres, Error, AssertSqlSafe};
use sqlx::postgres::{PgArguments, PgQueryResult, PgRow};
use log::{debug, error, info};
use crate::models::unit::Unit;

#[derive(Debug, Deserialize, Serialize, Clone, FromRow)]
pub struct Article {
    pub id: i64,
    pub name: String,
    pub bio: bool,
    pub origin: Origin,
    pub description: Option<String>,
    pub ingredients: Option<String>,
    pub default_unit: Unit,
    pub variants: Vec<Variant>,
    pub mhd: Option<String>,
    pub bio_origin: Option<String>,
    pub allergens_text: Option<String>,
    pub bio_info: Option<String>,
    pub topm_id: Option<String>,
    pub additional_name: Option<String>,
}

fn from_postgres_row_small(row: PgRow) -> Article {
    Article {
        id: row.get(0),
        name: row.get(1),
        bio: row.get(2),
        origin: row.get(3),
        description: row.get(4),
        ingredients: row.get(5),
        default_unit: row.get(6),
        variants: vec![],
        mhd: row.get(7),
        bio_origin: row.get(8),
        allergens_text: row.get(9),
        bio_info: row.get(10),
        topm_id: row.get(11),
        additional_name: row.get(12),
    }
}

async fn from_postgres_row(row: PgRow, pool: PgPool) -> Article {
    let variants_option = load_article_variants(row.get(0), &pool).await;

    let variants = match variants_option {
        Some(variants_inner) => {
            info!("Found Variants");
            variants_inner
        }
        None => {
            info!("No Variants");
            vec![]
        }
    };

    Article {
        id: row.get(0),
        name: row.get(1),
        bio: row.get(2),
        origin: row.get(3),
        description: row.get(4),
        ingredients: row.get(5),
        default_unit: row.get(6),
        variants: variants,
        mhd: row.get(7),
        bio_origin: row.get(8),
        allergens_text: row.get(9),
        bio_info: row.get(10),
        topm_id: row.get(11),
        additional_name: row.get(12),
    }
}

pub async fn load_articles(pool: PgPool) -> Option<Vec<Article>> {
    let statement = format!(
        "SELECT * FROM {}",
        "article",
    );

    let res = sqlx::query(
        AssertSqlSafe(statement.as_str()))
        .fetch_all(&pool).await;

    let mut articles: Vec<Article> = Vec::new();

    match res {
        Ok(records) => {
            for record in records {
                articles.push(
                    from_postgres_row_small(record)
                );
            }

            Some(articles)
        }
        Err(err) => {
            error!("Error while loading all articles");
            None
        }
    }
}

pub async fn load_article(id: i64, pool: PgPool) -> Option<Article> {
    let statement = format!(
        "SELECT * FROM {} WHERE id = $1",
        "article"
    );

    let mut args = PgArguments::default();
    args.add(id);

    let res = sqlx::query_with(
        AssertSqlSafe(statement.as_str()), args)
        .fetch_one(&pool).await;

    match res {
        Ok(record) => {
            let article = from_postgres_row(record, pool).await;

            Some(article)
        }
        Err(err) => {
            error!("Error while loading all article id: {}", id);
            None
        }
    }
}

pub async fn insert_article(article: &Article, pool: PgPool) -> Option<Article> {
    let mut args = PgArguments::default();
    args.add(&article.name);
    args.add(&article.bio);
    args.add(&article.origin);
    args.add(&article.description);
    args.add(&article.ingredients);
    args.add(&article.default_unit);
    args.add(&article.mhd);
    args.add(&article.bio_origin);
    args.add(&article.allergens_text);
    args.add(&article.bio_info);
    args.add(&article.topm_id);
    args.add(&article.additional_name);

    let statement = format!(
        "INSERT INTO {} (name, bio, origin, description, ingredients, default_unit_id, mhd, bio_origin, allergens_text, bio_info, topm_id, additional_name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *",
        "article",
    );

    let res = sqlx::query_with(
        AssertSqlSafe(statement.as_str()),
        args)
        .fetch_one(&pool).await;

    info!("{:#?}", res);

    match res {
        Ok(record) => {
            let article = from_postgres_row(record, pool).await;

            Some(article)
        }
        Err(err) => {
            error!("Error while loading newly created article: {}", article.name);
            None
        }
    }
}

pub async fn update_article(article_id: i64, article: &Article, pool: PgPool) -> Option<Article> {
    let mut args = PgArguments::default();
    args.add(&article.id);
    args.add(&article.name);
    args.add(&article.bio);
    args.add(&article.origin);
    args.add(&article.description);
    args.add(&article.ingredients);
    args.add(&article.default_unit);
    args.add(&article.mhd);
    args.add(&article.bio_origin);
    args.add(&article.allergens_text);
    args.add(&article.bio_info);
    args.add(&article.topm_id);
    args.add(&article.additional_name);

    let statement = format!(
        "UPDATE {} SET name = $2, bio = $3, origin = $4, description = $5, ingredients = $6, default_unit_id = $7, mhd = $8, bio_origin = $9, allergens_text = $10, bio_info = $11, topm_id = $12, additional_name = $13 WHERE id = $1 RETURNING *",
        "article",
    );

    let res = sqlx::query_with(
        AssertSqlSafe(statement.as_str()),
        args)
        .fetch_one(&pool).await;

    info!("{:#?}", res);

    match res {
        Ok(record) => {
            let variant = from_postgres_row_small(record);
            Some(variant)
        }
        Err(err) => {
            error!("Error while updating variant: {}", article_id);
            None
        }
    }
}

pub async fn delete_article(article_id: i64, pool: PgPool) -> Option<()> {
    let result = sqlx::query(
        AssertSqlSafe(format!("DELETE FROM {} WHERE id = {}",
                "article",
                article_id
        ).as_str())
    ).execute(&pool).await;

    match result {
        Ok(_) => {
            Some(())
        }
        Err(err) => {
            println!("Error deleting article: {:#?}", err);

            None
        }
    }
}
