use log::{error, info};
use serde::{Deserialize, Serialize};
use sqlx::{Arguments, AssertSqlSafe, Decode, Encode, FromRow, PgPool, Row};
use sqlx::postgres::{PgArguments, PgRow};

#[derive(Debug, Deserialize, Serialize, Clone, FromRow, Decode, Encode)]
pub struct Label {
    pub id: i64,
    pub name: String,
}

fn from_postgres_row_small(row: PgRow) -> Label {
    Label {
        id: row.get(0),
        name: row.get(1),
    }
}

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

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

    let mut labels: Vec<Label> = Vec::new();

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

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

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

    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) => {
            Some(from_postgres_row_small(record))
        }
        Err(err) => {
            error!("Error while loading label: {}", id);
            None
        }
    }
}

pub async fn insert_label(label: &Label, pool: PgPool) -> Option<Label> {
    let mut args = PgArguments::default();
    args.add(&label.name);

    let statement = format!(
        "INSERT INTO {} (name) VALUES ($1) RETURNING *",
        "label",
    );

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

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

    match res {
        Ok(record) => {
            let label = from_postgres_row_small(record);

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

pub async fn update_label(label_id: i64, label: &Label, pool: PgPool) -> Option<Label> {
    let mut args = PgArguments::default();
    args.add(&label.id);
    args.add(&label.name);

    let statement = format!(
        "UPDATE {} SET name = $2 WHERE id = $1 RETURNING *",
        "label",
    );

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

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

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

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

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

            None
        }
    }
}
