use std::fmt;

use model_derive::Model;
use serde::{Deserialize, Serialize};
use sqlx::{PgExecutor, Type, query_as};

use crate::db::{Id, NoId};

#[derive(Clone, Debug, Deserialize, Serialize, Type, PartialEq)]
#[sqlx(type_name = "authentication_key_type", rename_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum AuthenticationKeyType {
    Ssh,
    Gpg,
}

impl fmt::Display for AuthenticationKeyType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            AuthenticationKeyType::Ssh => "SSH",
            AuthenticationKeyType::Gpg => "GPG",
        })
    }
}

#[derive(Clone, Debug, Deserialize, Model, Serialize, PartialEq)]
#[table(authentication_key)]
pub struct AuthenticationKey<I = NoId> {
    pub id: I,
    pub yubikey_id: Option<i64>,
    pub name: Option<String>,
    pub user_id: Id,
    pub key: String,
    #[model(enum)]
    pub key_type: AuthenticationKeyType,
}

impl AuthenticationKey {
    #[must_use]
    pub fn new(
        user_id: Id,
        key: String,
        name: Option<String>,
        key_type: AuthenticationKeyType,
        yubikey_id: Option<i64>,
    ) -> Self {
        Self {
            id: NoId,
            yubikey_id,
            user_id,
            key,
            name,
            key_type,
        }
    }
}

impl AuthenticationKey<Id> {
    pub async fn find_by_user_id<'e, E>(
        executor: E,
        user_id: Id,
        key_type: Option<AuthenticationKeyType>,
    ) -> sqlx::Result<Vec<Self>>
    where
        E: PgExecutor<'e>,
    {
        match key_type {
            Some(key_type) => {
                query_as!(
                    Self,
                    "SELECT id, user_id, yubikey_id \"yubikey_id?\", key, \
                    name, key_type \"key_type: AuthenticationKeyType\" \
                    FROM authentication_key WHERE user_id = $1 AND key_type = $2",
                    user_id,
                    &key_type as &AuthenticationKeyType
                )
                .fetch_all(executor)
                .await
            }
            None => {
                query_as!(
                    Self,
                    "SELECT id, user_id, yubikey_id \"yubikey_id?\", key, \
                    name, key_type \"key_type: AuthenticationKeyType\" \
                    FROM authentication_key WHERE user_id = $1",
                    user_id
                )
                .fetch_all(executor)
                .await
            }
        }
    }
}
