use crate::db::schema::DollProfile;
use uuid::{uuid, Uuid};

use super::schema::{CreateDollProfile, DbHook, TrxHook};

pub async fn list(db: &mut DbHook, from: &Uuid) -> sqlx::Result<Vec<DollProfile>> {
	sqlx::query_as!(
		DollProfile,
		"select * from doll_profiles where bound_to_id = $1 and archived_at is null",
		from
	)
	.fetch_all(&mut **db)
	.await
}

pub async fn list_archived(db: &mut DbHook, from: &Uuid) -> sqlx::Result<Vec<i32>> {
	sqlx::query_scalar!(
		"select id from doll_profiles where bound_to_id = $1 and archived_at is not null",
		from
	)
	.fetch_all(&mut **db)
	.await
}

pub async fn get(
	db: &mut DbHook,
	ident: i32,
	microchip_id: &str,
	include_archived: bool,
) -> sqlx::Result<Option<DollProfile>> {
	if include_archived {
		sqlx::query_as!(
			DollProfile,
			r#"
				select * from doll_profiles where (id = $1 or microchip_id = $2)
			"#,
			ident,
			microchip_id
		)
		.fetch_optional(&mut **db)
		.await
	} else {
		sqlx::query_as!(
			DollProfile,
			r#"
				select * from doll_profiles where (id = $1 or microchip_id = $2) and archived_at is null
			"#,
			ident,
			microchip_id
		)
		.fetch_optional(&mut **db)
		.await
	}
}

pub async fn check_ids(db: &mut DbHook, idents: &Vec<i32>) -> sqlx::Result<Vec<i32>> {
	sqlx::query_scalar!(
		"select id from doll_profiles where id in (select * from unnest($1::int[]))",
		idents
	)
	.fetch_all(&mut **db)
	.await
}

pub async fn id_exists(db: &mut DbHook, ident: i32, microchip_id: &str) -> sqlx::Result<bool> {
	Ok(sqlx::query!(
		"select id from doll_profiles where id = $1 or microchip_id = $2",
		ident,
		microchip_id
	)
	.fetch_optional(&mut **db)
	.await?
	.is_some())
}

pub async fn create(db: &mut DbHook, doll: CreateDollProfile<'_>) -> sqlx::Result<()> {
	sqlx::query!(
		r#"
			insert into doll_profiles
			(id, microchip_id, name, pronoun_subject, pronoun_object, pronoun_possessive, handler_name, handler_link, kind, breed, behaviour, description, chassis_type, chassis_id, chassis_color, bound_to_id)
			values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
		"#,
		doll.id,
		doll.microchip_id,
		doll.name,
		doll.pronoun_subject,
		doll.pronoun_object,
		doll.pronoun_possessive,
		doll.handler_name,
		doll.handler_link,
		doll.kind,
		doll.breed,
		doll.behaviour,
		doll.description,
		doll.chassis_type,
		doll.chassis_id,
		doll.chassis_color,
		doll.bound_to_id,
	).execute(&mut **db).await?;

	Ok(())
}

/// editing a doll_profile will also unarchive it
pub async fn edit(db: &mut DbHook, doll: CreateDollProfile<'_>) -> sqlx::Result<()> {
	sqlx::query!(
		r#"
			update doll_profiles
			set microchip_id = $1,
			name = $2,
			pronoun_subject = $3,
			pronoun_object = $4,
			pronoun_possessive = $5,
			handler_name = $6,
			handler_link = $7,
			kind = $8,
			breed = $9,
			behaviour = $10,
			description = $11,
			chassis_type = $12,
			chassis_id = $13,
			chassis_color = $14,
			archived_at = null,
			updated_at = current_timestamp
			where id = $15
		"#,
		doll.microchip_id,
		doll.name,
		doll.pronoun_subject,
		doll.pronoun_object,
		doll.pronoun_possessive,
		doll.handler_name,
		doll.handler_link,
		doll.kind,
		doll.breed,
		doll.behaviour,
		doll.description,
		doll.chassis_type,
		doll.chassis_id,
		doll.chassis_color,
		doll.id,
	)
	.execute(&mut **db)
	.await?;

	Ok(())
}

/// deleting a doll profile only wipes the data associated to it but retains two bits of info:
/// - the tag's ID
/// - the account which created this tag
///
/// this is to ensure that no one else will be able to re-use this tag for some time while allowing
/// the account holder to "re-create" one with this ID.
///
/// A period of time after which deleted accounts will have their IDs freed is to be set.
pub async fn delete(trx: &mut TrxHook<'_>, id: i32) -> sqlx::Result<()> {
	sqlx::query!(
		r#"
			update doll_profiles
			set microchip_id = null,
			name = '',
			pronoun_subject = '',
			pronoun_object = '',
			pronoun_possessive = '',
			handler_name = '',
			handler_link = null,
			kind = null,
			breed = null,
			behaviour = null,
			description = null,
			chassis_type = null,
			chassis_id = null,
			chassis_color = null,
			updated_at = current_timestamp,
			archived_at = current_timestamp
			where id = $1
		"#,
		id
	)
	.execute(&mut **trx)
	.await?;

	Ok(())
}

pub async fn delete_all_from_account(trx: &mut TrxHook<'_>, from: &Uuid) -> sqlx::Result<()> {
	let null_uuid = uuid!("00000000-0000-0000-0000-000000000000");

	// 1. archive all tags from the account
	let tags = sqlx::query_as!(
		DollProfile,
		"select * from doll_profiles where bound_to_id = $1 and archived_at is null",
		from
	)
	.fetch_all(&mut **trx)
	.await?;
	for tag in tags {
		delete(trx, tag.id).await?;
	}

	// 2. unlink archived tags from the account
	sqlx::query!("update doll_profiles set bound_to_id = $1 where archived_at is not null and bound_to_id = $2", null_uuid, from)
		.execute(&mut **trx)
		.await?;

	Ok(())
}
