use crate::model::{Error, Result};
use super::{DataManager, drivers::common};
use oiseau::{cache::Cache, execute, params, query_row, query_rows};

pub const NAME_REGEX: &str = r"[^\w_\-\.,!]+";

impl DataManager {
    pub async fn init(&self) -> Result<()> {
        let conn = match self.0.connect().await {
            Ok(c) => c,
            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
        };

        execute!(&conn, common::CREATE_TABLE_USERS).unwrap();
        execute!(&conn, common::CREATE_TABLE_GROUPS).unwrap();
        execute!(&conn, common::CREATE_TABLE_GROUP_MEMBERSHIPS).unwrap();
        execute!(&conn, common::CREATE_TABLE_POLLS).unwrap();
        execute!(&conn, common::CREATE_TABLE_POLL_VOTES).unwrap();
        execute!(&conn, common::CREATE_TABLE_POSTS).unwrap();
        execute!(&conn, common::CREATE_TABLE_REACTIONS).unwrap();
        execute!(&conn, common::CREATE_TABLE_NOTIFICATIONS).unwrap();
        execute!(&conn, common::CREATE_TABLE_USER_FOLLOWS).unwrap();
        execute!(&conn, common::CREATE_TABLE_USER_BLOCKS).unwrap();
        execute!(&conn, common::CREATE_TABLE_IP_BANS).unwrap();
        execute!(&conn, common::CREATE_TABLE_AUDIT_LOG).unwrap();
        execute!(&conn, common::CREATE_TABLE_REPORTS).unwrap();
        execute!(&conn, common::CREATE_TABLE_USER_WARNINGS).unwrap();
        execute!(&conn, common::CREATE_TABLE_REQUESTS).unwrap();
        execute!(&conn, common::CREATE_TABLE_QUESTIONS).unwrap();
        execute!(&conn, common::CREATE_TABLE_IP_BLOCKS).unwrap();
        execute!(&conn, common::CREATE_TABLE_DRAFTS).unwrap();
        execute!(&conn, common::CREATE_TABLE_APPS).unwrap();

        for x in common::VERSION_MIGRATIONS.split(";") {
            execute!(&conn, x).unwrap();
        }

        self.0
            .1
            .set("atto.active_connections:users".to_string(), "0".to_string())
            .await;

        self.1.init().await.expect("failed to init buckets manager");
        Ok(())
    }

    pub async fn get_table_row_count(&self, table: &str) -> Result<i32> {
        let conn = match self.0.connect().await {
            Ok(c) => c,
            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
        };

        let res = query_row!(
            &conn,
            &format!("SELECT COUNT(*)::int FROM {}", table),
            params![],
            |x| Ok(x.get::<usize, i32>(0))
        );

        if let Err(e) = res {
            return Err(Error::DatabaseError(e.to_string()));
        }

        Ok(res.unwrap())
    }

    pub async fn get_table_row_count_where(&self, table: &str, r#where: &str) -> Result<i32> {
        let conn = match self.0.connect().await {
            Ok(c) => c,
            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
        };

        let res = query_row!(
            &conn,
            &format!("SELECT COUNT(*)::int FROM {} WHERE {}", table, r#where),
            params![],
            |x| Ok(x.get::<usize, i32>(0))
        );

        if let Err(e) = res {
            return Err(Error::DatabaseError(e.to_string()));
        }

        Ok(res.unwrap())
    }

    pub async fn list_tables(&self) -> Result<Vec<String>> {
        let conn = match self.0.connect().await {
            Ok(x) => x,
            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
        };

        let res = query_rows!(
            &conn,
            "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname != 'pg_catalog' AND schemaname != 'information_schema'",
            params![],
            |x| x.get::<usize, String>(0)
        );

        if let Err(e) = res {
            return Err(Error::DatabaseError(e.to_string()));
        }

        Ok(res.unwrap())
    }
}

#[macro_export]
macro_rules! auto_method {
    // case 0
    ($name:ident()@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt) => {
        pub async fn $name(&self, id: &$crate::model::id::Id) -> Result<$returns_> {
            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = query_row!(&conn, $query, &[&id.printable()], |x| {
                Ok(Self::$select_fn(x))
            });

            if res.is_err() {
                return Err(Error::GeneralNotFound($name_.to_string()));
            }

            Ok(res.unwrap())
        }
    };

    // case 1
    ($name:ident()@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt --cache-key-tmpl=$cache_key_tmpl:literal) => {
        pub async fn $name(&self, id: &$crate::model::id::Id) -> Result<$returns_> {
            if let Some(cached) = self.0.1.get(format!($cache_key_tmpl, id)).await {
                match serde_json::from_str(&cached) {
                    Ok(x) => return Ok(x),
                    Err(_) => {
                        self.0.1.remove(format!($cache_key_tmpl, id)).await;
                    }
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = oiseau::query_row!(&conn, $query, &[&id.printable()], |x| {
                Ok(Self::$select_fn(x))
            });

            if res.is_err() {
                return Err(Error::GeneralNotFound($name_.to_string()));
            }

            let x = res.unwrap();
            self.0
                .1
                .set(
                    format!($cache_key_tmpl, id),
                    serde_json::to_string(&x).unwrap(),
                )
                .await;

            Ok(x)
        }
    };

    // case 2
    ($name:ident(Id :: usize)@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt --cache-key-tmpl=$cache_key_tmpl:literal) => {
        pub async fn $name(&self, id: &$crate::model::id::Id) -> Result<$returns_> {
            if let Some(cached) = self.0.1.get(format!($cache_key_tmpl, id)).await {
                match serde_json::from_str(&cached) {
                    Ok(x) => return Ok(x),
                    Err(_) => {
                        self.0.1.remove(format!($cache_key_tmpl, id)).await;
                    }
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = oiseau::query_row!(&conn, $query, &[&(id.as_usize() as i64)], |x| {
                Ok(Self::$select_fn(x))
            });

            if res.is_err() {
                return Err(Error::GeneralNotFound($name_.to_string()));
            }

            let x = res.unwrap();
            self.0
                .1
                .set(
                    format!($cache_key_tmpl, id),
                    serde_json::to_string(&x).unwrap(),
                )
                .await;

            Ok(x)
        }
    };

    // case 3
    ($name:ident($selector_t:ty)@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt) => {
        pub async fn $name(&self, selector: $selector_t) -> Result<$returns_> {
            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res =
                oiseau::query_row!(&conn, $query, &[&selector], |x| { Ok(Self::$select_fn(x)) });

            if res.is_err() {
                return Err(Error::GeneralNotFound($name_.to_string()));
            }

            Ok(res.unwrap())
        }
    };

    // case 4
    ($name:ident($selector_t:ty)@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt --cache-key-tmpl=$cache_key_tmpl:literal) => {
        pub async fn $name(&self, selector: $selector_t) -> Result<$returns_> {
            let selector = selector.to_string().to_lowercase();

            if let Some(cached) = self.0.1.get(format!($cache_key_tmpl, selector)).await {
                match serde_json::from_str(&cached) {
                    Ok(x) => return Ok(x),
                    Err(_) => {
                        self.0.1.remove(format!($cache_key_tmpl, selector)).await;
                    }
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = query_row!(&conn, $query, &[&selector.to_string()], |x| {
                Ok(Self::$select_fn(x))
            });

            if res.is_err() {
                return Err(Error::GeneralNotFound($name_.to_string()));
            }

            let x = res.unwrap();
            self.0
                .1
                .set(
                    format!($cache_key_tmpl, selector),
                    serde_json::to_string(&x).unwrap(),
                )
                .await;

            Ok(x)
        }
    };

    // case 5
    ($name:ident($selector_t:ty as i64)@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt --cache-key-tmpl=$cache_key_tmpl:literal) => {
        pub async fn $name(&self, selector: $selector_t) -> Result<$returns_> {
            if let Some(cached) = self
                .0
                .1
                .get(format!($cache_key_tmpl, selector.to_string()))
                .await
            {
                match serde_json::from_str(&cached) {
                    Ok(x) => return Ok(x),
                    Err(_) => {
                        self.0
                            .1
                            .remove(format!($cache_key_tmpl, selector.to_string()))
                            .await
                    }
                };
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = oiseau::query_row!(&conn, $query, &[&(selector as i64)], |x| {
                Ok(Self::$select_fn(x))
            });

            if res.is_err() {
                return Err(Error::GeneralNotFound($name_.to_string()));
            }

            let x = res.unwrap();
            self.0
                .1
                .set(
                    format!($cache_key_tmpl, selector),
                    serde_json::to_string(&x).unwrap(),
                )
                .await;

            Ok(x)
        }
    };

    // case 6
    ($name:ident()@$select_fn:ident:$permission:expr; -> $query:literal) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, user: &User) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if user.id != y.owner {
                if !user.permissions.check($permission) {
                    return Err(Error::NotAllowed);
                } else {
                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
                        user.id,
                        format!("invoked `{}` with x value `{id}`", stringify!($name)),
                    ))
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            Ok(())
        }
    };

    // case 7
    ($name:ident()@$select_fn:ident:$permission:expr; -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, user: &User) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if user.id != y.owner {
                if !user.permissions.check($permission) {
                    return Err(Error::NotAllowed);
                } else {
                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
                        user.id,
                        format!("invoked `{}` with x value `{id}`", stringify!($name)),
                    ))
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.0.1.remove(format!($cache_key_tmpl, id)).await;

            Ok(())
        }
    };

    // case 8
    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, user: &User, x: $x) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if user.id != y.owner {
                if !user.permissions.check($permission) {
                    return Err(Error::NotAllowed);
                } else {
                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
                        user.id,
                        format!("invoked `{}` with x value `{id}`", stringify!($name)),
                    ))
                    .await?
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&x, &id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            Ok(())
        }
    };

    // case 9
    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, user: &User, x: $x) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if user.id != y.owner {
                if !user.permissions.check($permission) {
                    return Err(Error::NotAllowed);
                } else {
                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
                        user.id,
                        format!("invoked `{}` with x value `{x}`", stringify!($name)),
                    ))
                    .await?
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, params![&x, &id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.0.1.remove(format!($cache_key_tmpl, id)).await;

            Ok(())
        }
    };

    // case 10
    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal --serde) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, user: &User, x: $x) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if user.id != y.owner {
                if !user.permissions.check($permission) {
                    return Err(Error::NotAllowed);
                } else {
                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
                        user.id,
                        format!("invoked `{}` with x value `{id}`", stringify!($name), id),
                    ))
                    .await?
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(
                &conn,
                $query,
                &[&serde_json::to_string(&x).unwrap(), &id.printable()]
            );

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            Ok(())
        }
    };

    // case 11
    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:literal) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, user: &User, x: $x) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if user.id != y.owner {
                if !user.permissions.check($permission) {
                    return Err(Error::NotAllowed);
                } else {
                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
                        user.id,
                        format!("invoked `{}` with x value `{id}`", stringify!($name)),
                    ))
                    .await?
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(
                &conn,
                $query,
                params![&serde_json::to_string(&x).unwrap(), &id.printable()]
            );

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.0.1.remove(format!($cache_key_tmpl, id)).await;

            Ok(())
        }
    };

    // case 12
    ($name:ident($x:ty) -> $query:literal) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, x: $x) -> Result<()> {
            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&x, &id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            Ok(())
        }
    };

    // case 13
    ($name:ident($x:ty) -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, x: $x) -> Result<()> {
            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&x, &id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.0.1.remove(format!($cache_key_tmpl, id)).await;

            Ok(())
        }
    };

    // case 14
    ($name:ident($x:ty) -> $query:literal --serde) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, x: $x) -> Result<()> {
            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(
                &conn,
                $query,
                &[&serde_json::to_string(&x).unwrap(), &id.printable()]
            );

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            Ok(())
        }
    };

    // case 15
    ($name:ident($x:ty) -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:literal) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, x: $x) -> Result<()> {
            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(
                &conn,
                $query,
                params![&serde_json::to_string(&x).unwrap(), &id.printable()]
            );

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.0.1.remove(format!($cache_key_tmpl, id)).await;

            Ok(())
        }
    };

    // case 16
    ($name:ident() -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --incr) => {
        pub async fn $name(&self, id: &$crate::model::id::Id) -> Result<()> {
            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.0.1.remove(format!($cache_key_tmpl, id)).await;
            Ok(())
        }
    };

    // case 17
    ($name:ident() -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --decr) => {
        pub async fn $name(&self, id: &$crate::model::id::Id) -> Result<()> {
            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.0.1.remove(format!($cache_key_tmpl, id)).await;

            Ok(())
        }
    };

    // case 18
    ($name:ident()@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --decr=$field:ident) => {
        pub async fn $name(&self, id: &$crate::model::id::Id) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if (y.$field as isize) - 1 < 0 {
                return Ok(());
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.0.1.remove(format!($cache_key_tmpl, id)).await;

            Ok(())
        }
    };

    // case 19
    ($name:ident()@$select_fn:ident:$permission:expr; -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, user: &User) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if user.id != y.owner {
                if !user.permissions.check($permission) {
                    return Err(Error::NotAllowed);
                } else {
                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
                        user.id,
                        format!("invoked `{}` with x value `{id}`", stringify!($name)),
                    ))
                    .await?
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;

            Ok(())
        }
    };

    // case 20
    ($name:ident($x:ty)@$select_fn:ident:$permission:path; -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, user: &User, x: $x) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if user.id != y.owner {
                if !user.permissions.check($permission) {
                    return Err(Error::NotAllowed);
                } else {
                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
                        user.id.to_owned(),
                        format!("invoked `{}` with x value `{x}`", stringify!($name)),
                    ))
                    .await?
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, params![&x, &id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;

            Ok(())
        }
    };

    // case 21
    ($name:ident($x:ty)@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, x: $x) -> Result<()> {
            let y = self.$select_fn(id).await?;

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, params![&x, &id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;

            Ok(())
        }
    };

    // case 22
    ($name:ident(Id :: usize, $x:ty)@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, x: $x) -> Result<()> {
            let y = self.$select_fn(id).await?;

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, params![&x, &(id.as_usize() as i64)]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;
            Ok(())
        }
    };

    // case 23
    ($name:ident($x:ty)@$select_fn:ident -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:ident) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, x: $x) -> Result<()> {
            let y = self.$select_fn(id).await?;

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(
                &conn,
                $query,
                params![&serde_json::to_string(&x).unwrap(), &id.printable()]
            );

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;
            Ok(())
        }
    };

    // case 24
    ($name:ident(Id :: usize, $x:ty)@$select_fn:ident -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:ident) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, x: $x) -> Result<()> {
            let y = self.$select_fn(id).await?;

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(
                &conn,
                $query,
                params![&serde_json::to_string(&x).unwrap(), &(id.as_usize() as i64)]
            );

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;
            Ok(())
        }
    };

    // case 25
    ($name:ident($x:ty)@$select_fn:ident:$permission:expr; -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:ident) => {
        pub async fn $name(&self, id: &$crate::model::id::Id, user: &User, x: $x) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if user.id != y.owner {
                if !user.permissions.check($permission) {
                    return Err(Error::NotAllowed);
                } else {
                    self.create_audit_log_entry($crate::model::moderation::AuditLogEntry::new(
                        user.id.to_owned(),
                        format!("invoked `{}` with x value `{x:?}`", stringify!($name)),
                    ))
                    .await?
                }
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(
                &conn,
                $query,
                params![&serde_json::to_string(&x).unwrap(), &id.printable()]
            );

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;

            Ok(())
        }
    };

    // case 26
    ($name:ident()@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident --incr) => {
        pub async fn $name(&self, id: &$crate::model::id::Id) -> Result<()> {
            let y = self.$select_fn(id).await?;

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;

            Ok(())
        }
    };

    // case 27
    ($name:ident(Id :: usize)@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident --incr) => {
        pub async fn $name(&self, id: &$crate::model::id::Id) -> Result<()> {
            let y = self.$select_fn(id).await?;

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&(id.as_usize() as i64)]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;

            Ok(())
        }
    };

    // case 28
    ($name:ident()@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident --decr=$field:ident) => {
        pub async fn $name(&self, id: &$crate::model::id::Id) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if (y.$field as isize) - 1 < 0 {
                return Ok(());
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&id.printable()]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;

            Ok(())
        }
    };

    // case 29
    ($name:ident(Id :: usize)@$select_fn:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:ident --decr=$field:ident) => {
        pub async fn $name(&self, id: &$crate::model::id::Id) -> Result<()> {
            let y = self.$select_fn(id).await?;

            if (y.$field as isize) - 1 < 0 {
                return Ok(());
            }

            let conn = match self.0.connect().await {
                Ok(c) => c,
                Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
            };

            let res = execute!(&conn, $query, &[&(id.as_usize() as i64)]);

            if let Err(e) = res {
                return Err(Error::DatabaseError(e.to_string()));
            }

            self.$cache_key_tmpl(&y).await;

            Ok(())
        }
    };
}
