use crate::database::*;
use crate::util::result::{Error, Result};

use mongodb::bson::{doc, from_document};
use rocket::http::RawStr;
use rocket::request::FromParam;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use validator::Validate;

#[derive(Validate, Serialize, Deserialize)]
pub struct Ref {
    #[validate(length(min = 26, max = 26))]
    pub id: String,
}

impl Ref {
    pub fn from(id: String) -> Result<Ref> {
        Ok(Ref { id })
    }

    pub async fn fetch<T: DeserializeOwned>(&self, collection: &'static str) -> Result<T> {
        let doc = get_collection(&collection)
            .find_one(
                doc! {
                    "_id": &self.id
                },
                None,
            )
            .await
            .map_err(|_| Error::DatabaseError {
                operation: "find_one",
                with: &collection,
            })?
            .ok_or_else(|| Error::UnknownUser)?;

        Ok(from_document::<T>(doc).map_err(|_| Error::DatabaseError {
            operation: "from_document",
            with: &collection,
        })?)
    }

    pub async fn fetch_user(&self) -> Result<User> {
        self.fetch("users").await
    }

    pub async fn fetch_channel(&self) -> Result<Channel> {
        self.fetch("channels").await
    }

    pub async fn fetch_message(&self) -> Result<Message> {
        self.fetch("messages").await
    }
}

impl User {
    pub fn as_ref(&self) -> Ref {
        Ref {
            id: self.id.to_string(),
        }
    }
}

impl<'r> FromParam<'r> for Ref {
    type Error = &'r RawStr;

    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
        if let Ok(result) = Ref::from(param.to_string()) {
            if result.validate().is_ok() {
                return Ok(result);
            }
        }

        Err(param)
    }
}