use diesel::prelude::*;

use crate::connections::db_connection::db;
use crate::constants;
use crate::models::tokens::{NewToken, Token, TokenID};
use crate::models::users::UserID;
use crate::schema::tokens;
use crate::utils::rand_utils::rand_string;
use crate::utils::time_utils::time;

pub struct NewTokenInfo {
    pub user_id: UserID,
    pub name: String,
    pub max_inactivity: u32,
    pub ip_net: Option<ipnet::IpNet>,
    pub read_only: bool,
    pub right_account: bool,
    pub right_movement: bool,
    pub right_inbox: bool,
    pub right_file: bool,
    pub right_auth: bool,
    pub right_stats: bool,
    pub right_backup: bool,
}

/// Create a new token
pub async fn create(new_token: NewTokenInfo) -> anyhow::Result<Token> {
    let ip_net = new_token.ip_net.map(|i| i.to_string());
    let token = rand_string(constants::TOKENS_LEN);
    let t = NewToken {
        name: &new_token.name,
        user_id: new_token.user_id.0,
        time_create: time() as i64,
        time_used: time() as i64,
        max_inactivity: new_token.max_inactivity as i32,
        read_only: new_token.read_only,
        ip_net: ip_net.as_deref(),
        token_value: &token,
        right_auth: new_token.right_auth,
        right_account: new_token.right_account,
        right_movement: new_token.right_movement,
        right_inbox: new_token.right_inbox,
        right_file: new_token.right_file,
        right_stats: new_token.right_stats,
        right_backup: new_token.right_backup,
    };

    let res = diesel::insert_into(tokens::table)
        .values(&t)
        .get_result(&mut db()?)?;

    Ok(res)
}

/// Get a single token by its id
pub async fn get_by_id(token_id: TokenID) -> anyhow::Result<Token> {
    Ok(tokens::table
        .filter(tokens::dsl::id.eq(token_id.0))
        .get_result(&mut db()?)?)
}

/// Get a single token by its name
pub fn get_by_name(name: &str) -> anyhow::Result<Token> {
    Ok(tokens::table
        .filter(tokens::dsl::name.eq(name))
        .get_result(&mut db()?)?)
}

/// Get a single token by its value
pub async fn get_by_value(value: &str) -> anyhow::Result<Token> {
    Ok(tokens::table
        .filter(tokens::dsl::token_value.eq(value))
        .get_result(&mut db()?)?)
}

/// Get the token of a user
pub async fn get_list_user(id: UserID) -> anyhow::Result<Vec<Token>> {
    Ok(tokens::table
        .filter(tokens::dsl::user_id.eq(id.0))
        .get_results(&mut db()?)?)
}

/// Update last used value of a token
pub async fn update_time_used(token: &Token) -> anyhow::Result<()> {
    diesel::update(tokens::dsl::tokens.filter(tokens::dsl::id.eq(token.id().0)))
        .set(tokens::dsl::time_used.eq(time() as i64))
        .execute(&mut db()?)?;
    Ok(())
}

/// Delete the token of a user
pub async fn delete(user_id: UserID, token_id: TokenID) -> anyhow::Result<()> {
    diesel::delete(
        tokens::dsl::tokens.filter(
            tokens::dsl::id
                .eq(token_id.0)
                .and(tokens::dsl::user_id.eq(user_id.0)),
        ),
    )
    .execute(&mut db()?)?;
    Ok(())
}

/// Remove outdated token
pub async fn cleanup() -> anyhow::Result<()> {
    let query = format!(
        "DELETE from tokens where time_used + max_inactivity < {};",
        time()
    );
    diesel::sql_query(query).execute(&mut db()?)?;
    Ok(())
}
