// Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

use std::str::FromStr;

use db_core::dev::*;

use sqlx::mysql::MySqlPoolOptions;
use sqlx::types::time::OffsetDateTime;
use sqlx::ConnectOptions;
use sqlx::MySqlPool;
use uuid::Uuid;

pub mod errors;
#[cfg(test)]
pub mod tests;

#[derive(Clone)]
pub struct Database {
    pub pool: MySqlPool,
}

/// Use an existing database pool
pub struct Conn(pub MySqlPool);

/// Connect to database
pub enum ConnectionOptions {
    /// fresh connection
    Fresh(Fresh),
    /// existing connection
    Existing(Conn),
}

pub struct Fresh {
    pub pool_options: MySqlPoolOptions,
    pub disable_logging: bool,
    pub url: String,
}

pub mod dev {
    pub use super::errors::*;
    pub use super::Database;
    pub use db_core::dev::*;
    pub use prelude::*;
    pub use sqlx::Error;
}

pub mod prelude {
    pub use super::*;
    pub use db_core::prelude::*;
}

#[async_trait]
impl Connect for ConnectionOptions {
    type Pool = Database;
    async fn connect(self) -> DBResult<Self::Pool> {
        let pool = match self {
            Self::Fresh(fresh) => {
                let mut connect_options =
                    sqlx::mysql::MySqlConnectOptions::from_str(&fresh.url).unwrap();
                if fresh.disable_logging {
                    connect_options.disable_statement_logging();
                }
                fresh
                    .pool_options
                    .connect_with(connect_options)
                    .await
                    .map_err(|e| DBError::DBError(Box::new(e)))?
            }

            Self::Existing(conn) => conn.0,
        };
        Ok(Database { pool })
    }
}

use dev::*;

#[async_trait]
impl Migrate for Database {
    async fn migrate(&self) -> DBResult<()> {
        sqlx::migrate!("./migrations/")
            .run(&self.pool)
            .await
            .map_err(|e| DBError::DBError(Box::new(e)))?;
        Ok(())
    }
}

#[async_trait]
impl MCDatabase for Database {
    /// ping DB
    async fn ping(&self) -> bool {
        use sqlx::Connection;

        if let Ok(mut con) = self.pool.acquire().await {
            con.ping().await.is_ok()
        } else {
            false
        }
    }

    /// register a new user
    async fn register(&self, p: &Register) -> DBResult<()> {
        let res = if let Some(email) = &p.email {
            sqlx::query!(
                "insert into mcaptcha_users 
        (name , password, email, secret) values (?, ?, ?, ?)",
                &p.username,
                &p.hash,
                &email,
                &p.secret,
            )
            .execute(&self.pool)
            .await
        } else {
            sqlx::query!(
                "INSERT INTO mcaptcha_users 
        (name , password,  secret) VALUES (?, ?, ?)",
                &p.username,
                &p.hash,
                &p.secret,
            )
            .execute(&self.pool)
            .await
        };
        res.map_err(map_register_err)?;
        Ok(())
    }

    /// delete a user
    async fn delete_user(&self, username: &str) -> DBResult<()> {
        sqlx::query!("DELETE FROM mcaptcha_users WHERE name = (?)", username)
            .execute(&self.pool)
            .await
            .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
        Ok(())
    }

    /// check if username exists
    async fn username_exists(&self, username: &str) -> DBResult<bool> {
        match sqlx::query!("SELECT name from mcaptcha_users WHERE name = ?", username,)
            .fetch_one(&self.pool)
            .await
        {
            Ok(_) => Ok(true),
            Err(sqlx::Error::RowNotFound) => Ok(false),
            Err(e) => Err(map_register_err(e)),
        }
    }

    /// get user email
    async fn get_email(&self, username: &str) -> DBResult<Option<String>> {
        struct Email {
            email: Option<String>,
        }

        let res = sqlx::query_as!(
            Email,
            "SELECT email FROM mcaptcha_users WHERE name = ?",
            username
        )
        .fetch_one(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;
        Ok(res.email)
    }

    /// check if email exists
    async fn email_exists(&self, email: &str) -> DBResult<bool> {
        match sqlx::query!("SELECT name from mcaptcha_users WHERE email = ?", email)
            .fetch_one(&self.pool)
            .await
        {
            Ok(_) => Ok(true),
            Err(sqlx::Error::RowNotFound) => Ok(false),
            Err(e) => Err(map_register_err(e)),
        }
    }

    /// update a user's email
    async fn update_email(&self, p: &UpdateEmail) -> DBResult<()> {
        sqlx::query!(
            "UPDATE mcaptcha_users set email = ?
            WHERE name = ?",
            &p.new_email,
            &p.username,
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;

        Ok(())
    }

    /// get a user's password
    async fn get_password(&self, l: &Login) -> DBResult<NameHash> {
        struct Password {
            name: String,
            password: String,
        }

        let rec = match l {
            Login::Username(u) => sqlx::query_as!(
                Password,
                r#"SELECT name, password  FROM mcaptcha_users WHERE name = ?"#,
                u,
            )
            .fetch_one(&self.pool)
            .await
            .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?,

            Login::Email(e) => sqlx::query_as!(
                Password,
                r#"SELECT name, password  FROM mcaptcha_users WHERE email = ?"#,
                e,
            )
            .fetch_one(&self.pool)
            .await
            .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?,
        };

        let res = NameHash {
            hash: rec.password,
            username: rec.name,
        };

        Ok(res)
    }

    /// update user's password
    async fn update_password(&self, p: &NameHash) -> DBResult<()> {
        sqlx::query!(
            "UPDATE mcaptcha_users set password = ?
            WHERE name = ?",
            &p.hash,
            &p.username,
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;

        Ok(())
    }

    /// update username
    async fn update_username(&self, current: &str, new: &str) -> DBResult<()> {
        sqlx::query!(
            "UPDATE mcaptcha_users set name = ?
            WHERE name = ?",
            new,
            current,
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;

        Ok(())
    }

    /// get a user's secret
    async fn get_secret(&self, username: &str) -> DBResult<Secret> {
        let secret = sqlx::query_as!(
            Secret,
            r#"SELECT secret  FROM mcaptcha_users WHERE name = ?"#,
            username,
        )
        .fetch_one(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;

        Ok(secret)
    }

    /// get a user's secret from a captcha key
    async fn get_secret_from_captcha(&self, key: &str) -> DBResult<Secret> {
        let secret = sqlx::query_as!(
            Secret,
            r#"SELECT secret  FROM mcaptcha_users WHERE ID = (
                    SELECT user_id FROM mcaptcha_config WHERE captcha_key = ?
                    )"#,
            key,
        )
        .fetch_one(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;

        Ok(secret)
    }

    /// update a user's secret
    async fn update_secret(&self, username: &str, secret: &str) -> DBResult<()> {
        sqlx::query!(
            "UPDATE mcaptcha_users set secret = ?
        WHERE name = ?",
            &secret,
            &username,
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;

        Ok(())
    }

    /// create new captcha
    async fn create_captcha(&self, username: &str, p: &CreateCaptcha) -> DBResult<()> {
        sqlx::query!(
            "INSERT INTO mcaptcha_config
        (`captcha_key`, `user_id`, `duration`, `name`)
        VALUES (?, (SELECT ID FROM mcaptcha_users WHERE name = ?), ?, ?)",
            p.key,
            username,
            p.duration as i32,
            p.description,
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;

        Ok(())
    }

    /// Get captcha config
    async fn get_captcha_config(&self, username: &str, key: &str) -> DBResult<Captcha> {
        let captcha = sqlx::query_as!(
            InternaleCaptchaConfig,
            "SELECT `config_id`, `duration`, `name`, `captcha_key` from mcaptcha_config WHERE
                        `captcha_key` = ? AND
                        user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?) ",
            &key,
            &username,
        )
        .fetch_one(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(captcha.into())
    }

    /// Get all captchas belonging to user
    async fn get_all_user_captchas(&self, username: &str) -> DBResult<Vec<Captcha>> {
        let mut res = sqlx::query_as!(
            InternaleCaptchaConfig,
            "SELECT captcha_key, name, config_id, duration FROM mcaptcha_config WHERE
            user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?) ",
            &username,
        )
        .fetch_all(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;

        let mut captchas = Vec::with_capacity(res.len());

        res.drain(0..).for_each(|r| captchas.push(r.into()));

        Ok(captchas)
    }

    /// update captcha metadata; doesn't change captcha key
    async fn update_captcha_metadata(
        &self,
        username: &str,
        p: &CreateCaptcha,
    ) -> DBResult<()> {
        sqlx::query!(
            "UPDATE mcaptcha_config SET name = ?, duration = ?
            WHERE user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)
            AND captcha_key = ?",
            p.description,
            p.duration,
            username,
            p.key,
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(())
    }

    /// update captcha key; doesn't change metadata
    async fn update_captcha_key(
        &self,
        username: &str,
        old_key: &str,
        new_key: &str,
    ) -> DBResult<()> {
        sqlx::query!(
            "UPDATE mcaptcha_config SET captcha_key = ? 
        WHERE captcha_key = ? AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)",
            new_key,
            old_key,
            username,
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(())
    }

    /// Add levels to captcha
    async fn add_captcha_levels(
        &self,
        username: &str,
        captcha_key: &str,
        levels: &[Level],
    ) -> DBResult<()> {
        use futures::future::try_join_all;
        let mut futs = Vec::with_capacity(levels.len());

        for level in levels.iter() {
            let difficulty_factor = level.difficulty_factor as i32;
            let visitor_threshold = level.visitor_threshold as i32;
            let fut = sqlx::query!(
                "INSERT INTO mcaptcha_levels (
            difficulty_factor, 
            visitor_threshold,
            config_id) VALUES  (
            ?, ?, (
                SELECT config_id FROM mcaptcha_config WHERE
                captcha_key = (?) AND user_id = (
                SELECT ID FROM mcaptcha_users WHERE name = ?
                    )));",
                difficulty_factor,
                visitor_threshold,
                &captcha_key,
                username,
            )
            .execute(&self.pool);
            futs.push(fut);
        }

        try_join_all(futs)
            .await
            .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(())
    }

    /// check if captcha exists
    async fn captcha_exists(
        &self,
        username: Option<&str>,
        captcha_key: &str,
    ) -> DBResult<bool> {
        //        let mut exists = false;

        #[allow(dead_code)]
        struct ConfigId {
            config_id: i32,
        }
        let res = match username {
            Some(username) => {
                sqlx::query_as!(
                    ConfigId,
                    "SELECT config_id FROM mcaptcha_config
                        WHERE
                            captcha_key = ? 
                        AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)",
                    captcha_key,
                    username
                )
                .fetch_one(&self.pool)
                .await
                //                if let Some(x) = x.exists {
                //                    exists = x;
                //                };
            }

            None => {
                sqlx::query_as!(
                    ConfigId,
                    "SELECT config_id from mcaptcha_config WHERE captcha_key = ?",
                    &captcha_key,
                )
                .fetch_one(&self.pool)
                .await
            } //if let Some(x) = x.exists {
              //    exists = x;
              //};
        };
        match res {
            Ok(_) => Ok(true),
            Err(sqlx::Error::RowNotFound) => Ok(false),
            Err(e) => Err(map_register_err(e)),
        }
    }

    /// Delete all levels of a captcha
    async fn delete_captcha_levels(
        &self,
        username: &str,
        captcha_key: &str,
    ) -> DBResult<()> {
        sqlx::query!(
            "DELETE FROM mcaptcha_levels 
        WHERE config_id = (
            SELECT config_id FROM mcaptcha_config where captcha_key= (?) 
            AND user_id = (
            SELECT ID from mcaptcha_users WHERE name = ?
            )
            )",
            captcha_key,
            username
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(())
    }

    /// Delete captcha
    async fn delete_captcha(&self, username: &str, captcha_key: &str) -> DBResult<()> {
        sqlx::query!(
            "DELETE FROM mcaptcha_config where captcha_key= (?)
                AND
            user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)",
            captcha_key,
            username,
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(())
    }

    /// Get captcha levels
    async fn get_captcha_levels(
        &self,
        username: Option<&str>,
        captcha_key: &str,
    ) -> DBResult<Vec<Level>> {
        struct I32Levels {
            difficulty_factor: i32,
            visitor_threshold: i32,
        }
        let levels = match username {
            None => sqlx::query_as!(
                I32Levels,
                "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels  WHERE
            config_id = (
                SELECT config_id FROM mcaptcha_config where captcha_key= (?)
                ) ORDER BY difficulty_factor ASC;",
                captcha_key,
            )
            .fetch_all(&self.pool)
            .await
            .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?,

            Some(username) => sqlx::query_as!(
                I32Levels,
                "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels  WHERE
            config_id = (
                SELECT config_id FROM mcaptcha_config where captcha_key= (?)
                AND user_id = (SELECT ID from mcaptcha_users WHERE name = ?)
                )
            ORDER BY difficulty_factor ASC;",
                captcha_key,
                username
            )
            .fetch_all(&self.pool)
            .await
            .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?,
        };

        let mut new_levels = Vec::with_capacity(levels.len());
        for l in levels.iter() {
            new_levels.push(Level {
                difficulty_factor: l.difficulty_factor as u32,
                visitor_threshold: l.visitor_threshold as u32,
            });
        }
        Ok(new_levels)
    }

    /// Get captcha's cooldown period
    async fn get_captcha_cooldown(&self, captcha_key: &str) -> DBResult<i32> {
        struct DurationResp {
            duration: i32,
        }

        let resp = sqlx::query_as!(
            DurationResp,
            "SELECT duration FROM mcaptcha_config  
            where captcha_key= ?",
            captcha_key,
        )
        .fetch_one(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(resp.duration)
    }
    /// Add traffic configuration
    async fn add_traffic_pattern(
        &self,
        username: &str,
        captcha_key: &str,
        pattern: &TrafficPattern,
    ) -> DBResult<()> {
        sqlx::query!(
            "INSERT INTO mcaptcha_sitekey_user_provided_avg_traffic (
            config_id,
            avg_traffic,
            peak_sustainable_traffic,
            broke_my_site_traffic
            ) VALUES ( 
             (SELECT config_id FROM mcaptcha_config where captcha_key= (?)
             AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)
            ), ?, ?, ?)",
            //payload.avg_traffic,
            captcha_key,
            username,
            pattern.avg_traffic as i32,
            pattern.peak_sustainable_traffic as i32,
            pattern.broke_my_site_traffic.as_ref().map(|v| *v as i32),
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
        Ok(())
    }

    /// Get traffic configuration
    async fn get_traffic_pattern(
        &self,
        username: &str,
        captcha_key: &str,
    ) -> DBResult<TrafficPattern> {
        struct Traffic {
            peak_sustainable_traffic: i32,
            avg_traffic: i32,
            broke_my_site_traffic: Option<i32>,
        }
        let res = sqlx::query_as!(
            Traffic,
            "SELECT 
          avg_traffic, 
          peak_sustainable_traffic, 
          broke_my_site_traffic 
        FROM 
          mcaptcha_sitekey_user_provided_avg_traffic 
        WHERE 
          config_id = (
            SELECT 
              config_id 
            FROM 
              mcaptcha_config 
            WHERE 
              captcha_key = ? 
              AND user_id = (
                SELECT 
                  id 
                FROM 
                  mcaptcha_users 
                WHERE 
                  NAME = ?
              )
          )
        ",
            captcha_key,
            username
        )
        .fetch_one(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
        Ok(TrafficPattern {
            broke_my_site_traffic: res.broke_my_site_traffic.as_ref().map(|v| *v as u32),
            avg_traffic: res.avg_traffic as u32,
            peak_sustainable_traffic: res.peak_sustainable_traffic as u32,
        })
    }

    /// Delete traffic configuration
    async fn delete_traffic_pattern(
        &self,
        username: &str,
        captcha_key: &str,
    ) -> DBResult<()> {
        sqlx::query!(
            "DELETE FROM mcaptcha_sitekey_user_provided_avg_traffic
        WHERE config_id = (
            SELECT config_id 
            FROM 
                mcaptcha_config 
            WHERE
                captcha_key = ?
            AND 
                user_id = (SELECT ID FROM mcaptcha_users WHERE name = ?)
            );",
            captcha_key,
            username,
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
        Ok(())
    }

    /// create new notification
    async fn create_notification(&self, p: &AddNotification) -> DBResult<()> {
        let now = now_unix_time_stamp();
        sqlx::query!(
            "INSERT INTO mcaptcha_notifications (
              heading, message, tx, rx, received)
              VALUES  (
              ?, ?,
                  (SELECT ID FROM mcaptcha_users WHERE name = ?),
                  (SELECT ID FROM mcaptcha_users WHERE name = ?),
                  ?
                      );",
            p.heading,
            p.message,
            p.from,
            p.to,
            now
        )
        .execute(&self.pool)
        .await
        .map_err(map_register_err)?;

        Ok(())
    }

    /// get all unread notifications
    async fn get_all_unread_notifications(
        &self,
        username: &str,
    ) -> DBResult<Vec<Notification>> {
        let mut inner_notifications = sqlx::query_file_as!(
            InnerNotification,
            "./src/get_all_unread_notifications.sql",
            &username
        )
        .fetch_all(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?;

        let mut notifications = Vec::with_capacity(inner_notifications.len());

        inner_notifications
            .drain(0..)
            .for_each(|n| notifications.push(n.into()));

        Ok(notifications)
    }

    /// mark a notification read
    async fn mark_notification_read(&self, username: &str, id: i32) -> DBResult<()> {
        sqlx::query_file_as!(
            Notification,
            "./src/mark_notification_read.sql",
            id,
            &username
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::NotificationNotFound))?;

        Ok(())
    }

    /// record PoWConfig fetches
    async fn record_fetch(&self, key: &str) -> DBResult<()> {
        let now = now_unix_time_stamp();
        let _ = sqlx::query!(
        "INSERT INTO mcaptcha_pow_fetched_stats 
        (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)",
        key,
        &now,
    )
    .execute(&self.pool)
    .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
        Ok(())
    }

    /// record PoWConfig solves
    async fn record_solve(&self, key: &str) -> DBResult<()> {
        let now = OffsetDateTime::now_utc();
        let _ = sqlx::query!(
        "INSERT INTO mcaptcha_pow_solved_stats 
        (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)",
        key,
        &now,
    )
    .execute(&self.pool)
    .await
    .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
        Ok(())
    }

    /// record PoWConfig confirms
    async fn record_confirm(&self, key: &str) -> DBResult<()> {
        let now = now_unix_time_stamp();
        let _ = sqlx::query!(
        "INSERT INTO mcaptcha_pow_confirmed_stats 
        (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)",
        key,
        &now
    )
    .execute(&self.pool)
    .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
        Ok(())
    }

    /// fetch PoWConfig fetches
    async fn fetch_config_fetched(&self, user: &str, key: &str) -> DBResult<Vec<i64>> {
        let records = sqlx::query_as!(
            Date,
            "SELECT time FROM mcaptcha_pow_fetched_stats
            WHERE 
                config_id = (
                    SELECT 
                        config_id FROM mcaptcha_config 
                    WHERE 
                        captcha_key = ?
                    AND
                        user_id = (
                        SELECT 
                            ID FROM mcaptcha_users WHERE name = ?))
                ORDER BY time DESC",
            &key,
            &user,
        )
        .fetch_all(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(Date::dates_to_unix(records))
    }

    /// fetch PoWConfig solves
    async fn fetch_solve(&self, user: &str, key: &str) -> DBResult<Vec<i64>> {
        let records = sqlx::query_as!(
            Date,
            "SELECT time FROM mcaptcha_pow_solved_stats 
            WHERE config_id = (
                SELECT config_id FROM mcaptcha_config 
                WHERE 
                    captcha_key = ?
                AND
                     user_id = (
                        SELECT 
                            ID FROM mcaptcha_users WHERE name = ?)) 
                ORDER BY time DESC",
            &key,
            &user
        )
        .fetch_all(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(Date::dates_to_unix(records))
    }

    /// fetch PoWConfig confirms
    async fn fetch_confirm(&self, user: &str, key: &str) -> DBResult<Vec<i64>> {
        let records = sqlx::query_as!(
            Date,
            "SELECT time FROM mcaptcha_pow_confirmed_stats 
            WHERE 
                config_id = (
                    SELECT config_id FROM mcaptcha_config 
                WHERE 
                    captcha_key = ?
                AND
                     user_id = (
                        SELECT 
                            ID FROM mcaptcha_users WHERE name = ?))
                ORDER BY time DESC",
            &key,
            &user
        )
        .fetch_all(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(Date::dates_to_unix(records))
    }

    /// record PoW timing
    async fn analysis_save(
        &self,
        captcha_id: &str,
        d: &CreatePerformanceAnalytics,
    ) -> DBResult<()> {
        let _ = sqlx::query!(
            "INSERT INTO mcaptcha_pow_analytics 
            (config_id, time, difficulty_factor, worker_type)
        VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?, ?, ?)",
            captcha_id,
            d.time as i32,
            d.difficulty_factor as i32,
            &d.worker_type,
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
        Ok(())
    }

    /// fetch PoW analytics
    async fn analytics_fetch(
        &self,
        captcha_id: &str,
        limit: usize,
        offset: usize,
    ) -> DBResult<Vec<PerformanceAnalytics>> {
        struct P {
            id: i32,
            time: i32,
            difficulty_factor: i32,
            worker_type: String,
        }

        impl From<P> for PerformanceAnalytics {
            fn from(v: P) -> Self {
                Self {
                    id: v.id as usize,
                    time: v.time as u32,
                    difficulty_factor: v.difficulty_factor as u32,
                    worker_type: v.worker_type,
                }
            }
        }

        let mut c = sqlx::query_as!(
            P,
            "SELECT
                id, time, difficulty_factor, worker_type
            FROM
                mcaptcha_pow_analytics
            WHERE
                config_id = (
                    SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?
                ) 
            ORDER BY ID
            LIMIT ? OFFSET ?",
            &captcha_id,
            limit as i64,
            offset as i64,
        )
        .fetch_all(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
        let mut res = Vec::with_capacity(c.len());
        for i in c.drain(0..) {
            res.push(i.into())
        }

        Ok(res)
    }

    /// Create psuedo ID against campaign ID to publish analytics
    async fn analytics_create_psuedo_id_if_not_exists(
        &self,
        captcha_id: &str,
    ) -> DBResult<()> {
        let id = Uuid::new_v4();
        sqlx::query!(
            "
            INSERT INTO
                mcaptcha_psuedo_campaign_id (config_id, psuedo_id)
            VALUES (
                (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?)),
                ?
            );",
            captcha_id,
            &id.to_string(),
        )
        .execute(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(())
    }

    /// Get psuedo ID from campaign ID
    async fn analytics_get_psuedo_id_from_capmaign_id(
        &self,
        captcha_id: &str,
    ) -> DBResult<String> {
        struct ID {
            psuedo_id: String,
        }

        let res = sqlx::query_as!(
            ID,
            "SELECT psuedo_id FROM
                mcaptcha_psuedo_campaign_id
            WHERE
                 config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?));
            ",
            captcha_id
        ).fetch_one(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;

        Ok(res.psuedo_id)
    }

    /// Get campaign ID from psuedo ID
    async fn analytics_get_capmaign_id_from_psuedo_id(
        &self,
        psuedo_id: &str,
    ) -> DBResult<String> {
        struct ID {
            captcha_key: String,
        }

        let res = sqlx::query_as!(
            ID,
            "SELECT
                captcha_key
            FROM
                mcaptcha_config
            WHERE
                 config_id = (
                     SELECT
                         config_id
                     FROM
                         mcaptcha_psuedo_campaign_id
                     WHERE
                         psuedo_id = ?
                 );",
            psuedo_id
        )
        .fetch_one(&self.pool)
        .await
        .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
        Ok(res.captcha_key)
    }

    async fn analytics_delete_all_records_for_campaign(
        &self,
        campaign_id: &str,
    ) -> DBResult<()> {
        let _ = sqlx::query!(
            "
        DELETE FROM
            mcaptcha_psuedo_campaign_id
        WHERE config_id = (
            SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?
        );",
            campaign_id
        )
        .execute(&self.pool)
        .await;

        let _ = sqlx::query!(
            "
            DELETE FROM
                mcaptcha_pow_analytics
            WHERE
                config_id = (
                    SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?
            ) ",
            campaign_id
        )
        .execute(&self.pool)
        .await;

        Ok(())
    }
}

#[derive(Clone)]
struct Date {
    time: OffsetDateTime,
}

impl Date {
    fn dates_to_unix(mut d: Vec<Self>) -> Vec<i64> {
        let mut dates = Vec::with_capacity(d.len());
        d.drain(0..)
            .for_each(|x| dates.push(x.time.unix_timestamp()));
        dates
    }
}

fn now_unix_time_stamp() -> OffsetDateTime {
    OffsetDateTime::now_utc()
}

#[derive(Debug, Clone, PartialEq)]
/// Represents notification
pub struct InnerNotification {
    /// receiver name  of the notification
    pub name: String,
    /// heading of the notification
    pub heading: String,
    /// message of the notification
    pub message: String,
    /// when notification was received
    pub received: OffsetDateTime,
    /// db assigned ID of the notification
    pub id: i32,
}

impl From<InnerNotification> for Notification {
    fn from(n: InnerNotification) -> Self {
        Notification {
            name: Some(n.name),
            heading: Some(n.heading),
            message: Some(n.message),
            received: Some(n.received.unix_timestamp()),
            id: Some(n.id),
        }
    }
}

#[derive(Clone)]
struct InternaleCaptchaConfig {
    config_id: i32,
    duration: i32,
    name: String,
    captcha_key: String,
}

impl From<InternaleCaptchaConfig> for Captcha {
    fn from(i: InternaleCaptchaConfig) -> Self {
        Self {
            config_id: i.config_id,
            duration: i.duration,
            description: i.name,
            key: i.captcha_key,
        }
    }
}
