use crate::controllers::HttpResult;
use crate::controllers::server_controller::ServerConstraints;
use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod};
use crate::models::tokens::{Token, TokenID};
use crate::services::tokens_service;
use crate::services::tokens_service::NewTokenInfo;
use actix_web::{HttpResponse, web};

#[derive(serde::Deserialize)]
pub struct CreateTokenBody {
    name: String,
    ip_net: Option<ipnet::IpNet>,
    max_inactivity: u32,
    read_only: bool,
    right_account: bool,
    right_movement: bool,
    right_inbox: bool,
    right_file: bool,
    right_auth: bool,
    right_stats: bool,
    right_backup: bool,
}

#[derive(serde::Serialize)]
pub struct CreateTokenResult {
    #[serde(flatten)]
    info: Token,
    token: String,
}

/// Create a new token
pub async fn create(auth: AuthExtractor, req: web::Json<CreateTokenBody>) -> HttpResult {
    if matches!(auth.method, AuthenticatedMethod::Token(_)) {
        return Ok(HttpResponse::Forbidden()
            .json("It is not allowed to create a token using another token!"));
    }

    let constraints = ServerConstraints::default();

    if !lazy_regex::regex!("^[a-zA-Z0-9 :-]+$").is_match(&req.name) {
        return Ok(HttpResponse::BadRequest().json("Token name contains invalid characters!"));
    }

    if !constraints.token_name.check_str(&req.name) {
        return Ok(HttpResponse::BadRequest().json("Invalid token name length!"));
    }

    if !constraints
        .token_max_inactivity
        .check_u32(req.max_inactivity)
    {
        return Ok(HttpResponse::BadRequest().json("Invalid token max inactivity!"));
    }

    let token = tokens_service::create(NewTokenInfo {
        user_id: auth.user_id(),
        max_inactivity: req.max_inactivity,
        ip_net: req.ip_net,
        name: req.name.clone(),
        read_only: req.read_only,
        right_account: req.right_account,
        right_movement: req.right_movement,
        right_inbox: req.right_inbox,
        right_file: req.right_file,
        right_auth: req.right_auth,
        right_stats: req.right_stats,
        right_backup: req.right_backup,
    })
    .await?;

    Ok(HttpResponse::Created().json(CreateTokenResult {
        token: token.token_value.to_string(),
        info: token,
    }))
}

/// Get the list of tokens of the user
pub async fn get_list(auth: AuthExtractor) -> HttpResult {
    Ok(HttpResponse::Ok().json(tokens_service::get_list_user(auth.user_id()).await?))
}

#[derive(serde::Deserialize)]
pub struct TokenIDInPath {
    id: TokenID,
}

/// Delete an API access token
pub async fn delete(auth: AuthExtractor, path: web::Path<TokenIDInPath>) -> HttpResult {
    tokens_service::delete(auth.user_id(), path.id).await?;
    Ok(HttpResponse::Accepted().finish())
}
