use chrono::NaiveDateTime;
use diesel::prelude::*;

use crate::{
    api::EmptyResult,
    db::{DbConn, schema::archives},
    error::MapResult,
};

use super::{CipherId, User, UserId};

#[derive(Identifiable, Queryable, Insertable)]
#[diesel(table_name = archives)]
#[diesel(primary_key(user_uuid, cipher_uuid))]
pub struct Archive {
    pub user_uuid: UserId,
    pub cipher_uuid: CipherId,
    pub archived_at: NaiveDateTime,
}

impl Archive {
    // Returns the date the specified cipher was archived
    pub async fn get_archived_at(cipher_uuid: &CipherId, user_uuid: &UserId, conn: &DbConn) -> Option<NaiveDateTime> {
        conn.run(move |conn| {
            archives::table
                .filter(archives::cipher_uuid.eq(cipher_uuid))
                .filter(archives::user_uuid.eq(user_uuid))
                .select(archives::archived_at)
                .first::<NaiveDateTime>(conn)
                .ok()
        })
        .await
    }

    // Saves (inserts or updates) an archive record with the provided timestamp
    pub async fn save(
        user_uuid: &UserId,
        cipher_uuid: &CipherId,
        archived_at: NaiveDateTime,
        conn: &DbConn,
    ) -> EmptyResult {
        User::update_uuid_revision(user_uuid, conn).await;
        db_run! { conn:
            sqlite, mysql {
                diesel::replace_into(archives::table)
                    .values((
                        archives::user_uuid.eq(user_uuid),
                        archives::cipher_uuid.eq(cipher_uuid),
                        archives::archived_at.eq(archived_at),
                    ))
                    .execute(conn)
                    .map_res("Error saving archive")
            }
            postgresql {
                diesel::insert_into(archives::table)
                    .values((
                        archives::user_uuid.eq(user_uuid),
                        archives::cipher_uuid.eq(cipher_uuid),
                        archives::archived_at.eq(archived_at),
                    ))
                    .on_conflict((archives::user_uuid, archives::cipher_uuid))
                    .do_update()
                    .set(archives::archived_at.eq(archived_at))
                    .execute(conn)
                    .map_res("Error saving archive")
            }
        }
    }

    // Deletes an archive record for a specific cipher
    pub async fn delete_by_cipher(user_uuid: &UserId, cipher_uuid: &CipherId, conn: &DbConn) -> EmptyResult {
        User::update_uuid_revision(user_uuid, conn).await;
        conn.run(move |conn| {
            diesel::delete(
                archives::table.filter(archives::user_uuid.eq(user_uuid)).filter(archives::cipher_uuid.eq(cipher_uuid)),
            )
            .execute(conn)
            .map_res("Error deleting archive")
        })
        .await
    }

    /// Return a vec with (cipher_uuid, archived_at)
    /// This is used during a full sync so we only need one query for all archive matches
    pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<(CipherId, NaiveDateTime)> {
        conn.run(move |conn| {
            archives::table
                .filter(archives::user_uuid.eq(user_uuid))
                .select((archives::cipher_uuid, archives::archived_at))
                .load::<(CipherId, NaiveDateTime)>(conn)
                .unwrap_or_default()
        })
        .await
    }
}
