//! Helpers and types for dealing with the edit lifecycle.
//!
//! Does not contain the core code for creating/updating/reading/deleting the Editor, Annotation,
//! and Changelog objects, which lives under `editing_crud`.

use crate::database_models::*;
use crate::database_schema::*;
use crate::editing_crud::EditgroupCrud;
use crate::entity_crud::EntityCrud;
use crate::errors::{FatcatError, Result};
use crate::identifiers::FatcatId;
use crate::server::DbConn;
use diesel;
use diesel::prelude::*;
use fatcat_api_spec::models::*;

pub struct EditContext {
    pub editor_id: FatcatId,
    pub editgroup_id: FatcatId,
    pub extra_json: Option<serde_json::Value>,
    pub autoaccept: bool,
}

impl EditContext {
    /// This function should always be run within a transaction
    pub fn check(&self, conn: &DbConn) -> Result<()> {
        let count: i64 = changelog::table
            .filter(changelog::editgroup_id.eq(&self.editgroup_id.to_uuid()))
            .count()
            .get_result(conn)?;
        if count > 0 {
            Err(FatcatError::EditgroupAlreadyAccepted(
                self.editgroup_id.to_string(),
            ))?;
        }
        Ok(())
    }
}

pub fn make_edit_context(
    conn: &DbConn,
    editor_id: FatcatId,
    editgroup_id: Option<FatcatId>,
    autoaccept: bool,
) -> Result<EditContext> {
    // *either* autoaccept is false and editgroup_id is Some, *or* autoaccept is true and
    // editgroup_id is None
    let editgroup_id: FatcatId = match (editgroup_id, autoaccept) {
        (Some(eg), false) => eg,
        (None, true) => {
            let eg = Editgroup {
                editgroup_id: None,
                editor_id: Some(editor_id.to_string()),
                editor: None,
                changelog_index: None,
                submitted: None,
                description: None,
                extra: None,
                annotations: None,
                edits: None,
            };
            let row = eg.db_create(conn, autoaccept)?;
            FatcatId::from_uuid(&row.id)
        }
        _ => Err(FatcatError::BadRequest(
            "unsupported batch editgroup/accept combination".to_string(),
        ))?,
    };
    Ok(EditContext {
        editor_id,
        editgroup_id,
        extra_json: None,
        autoaccept,
    })
}

/// This function should always be run within a transaction
pub fn accept_editgroup(conn: &DbConn, editgroup_id: FatcatId) -> Result<ChangelogRow> {
    // check that we haven't accepted already (in changelog)
    // NB: could leave this to a UNIQUE constraint
    // TODO: redundant with check_edit_context
    let count: i64 = changelog::table
        .filter(changelog::editgroup_id.eq(editgroup_id.to_uuid()))
        .count()
        .get_result(conn)?;
    if count > 0 {
        return Err(FatcatError::EditgroupAlreadyAccepted(editgroup_id.to_string()).into());
    }

    // copy edit columns to ident table
    ContainerEntity::db_accept_edits(conn, editgroup_id)?;
    CreatorEntity::db_accept_edits(conn, editgroup_id)?;
    FileEntity::db_accept_edits(conn, editgroup_id)?;
    FilesetEntity::db_accept_edits(conn, editgroup_id)?;
    WebcaptureEntity::db_accept_edits(conn, editgroup_id)?;
    ReleaseEntity::db_accept_edits(conn, editgroup_id)?;
    WorkEntity::db_accept_edits(conn, editgroup_id)?;

    // append log/changelog row
    let entry: ChangelogRow = diesel::insert_into(changelog::table)
        .values((changelog::editgroup_id.eq(editgroup_id.to_uuid()),))
        .get_result(conn)?;

    // update editgroup row with is_accepted
    diesel::update(editgroup::table.find(editgroup_id.to_uuid()))
        .set(editgroup::is_accepted.eq(true))
        .execute(conn)?;

    Ok(entry)
}