use crate::repo::{Repo, AccessTokenRepoTrait};
use chrono::{DateTime, Utc};
use sqlx::PgPool;
use crate::model::auth::AccessToken;

#[derive(Clone)]
pub struct AccessTokenRepo {
	pool: PgPool
}

impl Repo for AccessTokenRepo {
	type Target = AccessToken;

	fn new(pool: PgPool) -> Self {
		Self { pool }
	}

	async fn get_by_id(&self, id: i64) -> Option<Self::Target> {
		sqlx::query_as!(AccessToken, "SELECT id, code FROM access_tokens WHERE id = $1", id)
			.fetch_optional(&self.pool)
			.await
			.unwrap_or(None)
	}
}

#[async_trait]
impl AccessTokenRepoTrait for AccessTokenRepo {
	async fn get_by_id(&self, id: i64) -> Option<AccessToken> {
		Repo::get_by_id(self, id).await
	}

	async fn create_new(&self,
		uid: i64, name: &str, code: &str, max_uses: i32,
		start_date: DateTime<Utc>, expiry_date: DateTime<Utc>
	) -> Result<i64, sqlx::Error> {
		sqlx::query!(
			"INSERT INTO access_tokens (name, code, creator_id, max_uses, created_at, expires_at)
			VALUES ($1, $2, $3, $4, $5, $6) RETURNING id",
			name,
			code,
			uid,
			max_uses,
			start_date,
			expiry_date
		).fetch_optional(&self.pool).await.and_then(|row| row.map(|r| r.id).ok_or(sqlx::Error::RowNotFound))
	}

	async fn use_token(&self, id: i64) -> Result<(), sqlx::Error> {
		sqlx::query!("UPDATE access_tokens SET uses = uses + 1 WHERE id = $1", id)
			.execute(&self.pool)
			.await?;
		Ok(())
	}

	async fn get_code_not_expired(&self, code: &str) -> Result<Option<AccessToken>, sqlx::Error> {
		sqlx::query_as!(AccessToken,
			"SELECT id, code FROM access_tokens
                WHERE code = $1
                AND created_at < NOW()
                AND expires_at > NOW()
                AND uses < max_uses",
			code
		)
		.fetch_optional(&self.pool)
		.await
	}
}