use crate::repo::{Repo, UserRepo};
use crate::model::user::{User, UserRole};
use sqlx::PgPool;
use crate::api::totp::TotpStatus;
use crate::model::user::UserRole::Admin;

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

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

    pub fn pool(&self) -> &PgPool {
        &self.pool
    }
}

impl Repo for UserRepository {
	type Target = User;
	fn new(pool: PgPool) -> Self {
		Self::new(pool)
	}

	async fn get_by_id(&self, id: i64) -> Option<Self::Target> {
		sqlx::query_as!(
            User,
            "SELECT id, email, username, nickname, passhash, totp_status as \"totp_status!: TotpStatus\", totp_secret, created_at, updated_at FROM users WHERE id = $1",
            id
        )
			.fetch_optional(&self.pool)
			.await
			.map_err(|e| {
				tracing::error!("Database error in get_by_id: {}", e);
				e
			})
			.ok()?
	}
}

#[async_trait]
impl UserRepo for UserRepository {
    fn pool(&self) -> &sqlx::PgPool {
        &self.pool
    }

	async fn get_by_id(&self, id: i64) -> Option<User> {
		Repo::get_by_id(self, id).await
	}

	async fn save(&self, user: &User) -> Result<(), sqlx::Error> {
		sqlx::query!(
			"UPDATE users SET email = $1, username = $2, nickname = $3, passhash = $4, totp_status = $5, totp_secret = $6, created_at = $7, updated_at = $8 WHERE id = $9",
			user.email,
			user.username,
			user.nickname,
			user.passhash,
			user.totp_status as TotpStatus,
			user.totp_secret,
			user.created_at,
			user.updated_at,
			user.id
		).execute(&self.pool).await?;
		Ok(())
	}

	async fn is_admin(&self, uid: i64) -> Result<UserRole, sqlx::Error> {
		sqlx::query!(
			"SELECT role AS \"user_role!: UserRole\" FROM users WHERE id = $1", uid
		)
		.fetch_one(&self.pool)
		.await
		.map(|row| row.user_role)
	}

	async fn new_user(&self, email: &str, username: &str, passhash: &str) -> Result<i64, sqlx::Error> {
		sqlx::query!(
			"INSERT INTO users (email, username, passhash) VALUES ($1, $2, $3) RETURNING id",
			email,
			username,
			passhash
		)
		.fetch_optional(&self.pool)
		.await
		.and_then(|row| row.map(|r| r.id).ok_or(sqlx::Error::RowNotFound))
	}

	async fn get_by_username(&self, username: &str) -> Result<Option<User>, sqlx::Error> {
		sqlx::query_as!(
            User,
            "SELECT id, email, username, nickname, passhash, totp_status as \"totp_status!: TotpStatus\", totp_secret, created_at, updated_at FROM users WHERE username = $1",
            username
        )
		.fetch_optional(&self.pool)
		.await
	}

	async fn delete_by_id(&self, id: i64) -> Result<(), sqlx::Error> {
		sqlx::query!("DELETE FROM users WHERE id = $1", id)
			.execute(&self.pool)
			.await?;
		Ok(())
	}

	async fn set_display_name(&self, id: i64, display_name: Option<String>) -> Result<(), sqlx::Error> {
		sqlx::query!(
            "UPDATE users SET nickname = $1 WHERE id = $2",
            display_name,
            id
        )
		.execute(&self.pool).await?;
		Ok(())
	}

	async fn set_username(&self, id: i64, username: &str) -> Result<(), sqlx::Error> {
		sqlx::query!(
            "UPDATE users SET username = $1 WHERE id = $2",
            username,
            id
        )
		.execute(&self.pool).await?;
		Ok(())
	}

	async fn set_twofa_enabled(&self, id: i64, enabled: &TotpStatus) -> Result<(), sqlx::Error> {
		sqlx::query!(
            "UPDATE users SET totp_status = $1 WHERE id = $2",
            enabled as &TotpStatus,
            id
        )
		.execute(&self.pool).await?;
		Ok(())
	}

	async fn get_totp_secret(&self, id: i64) -> Result<Option<String>, sqlx::Error> {
		sqlx::query!(
            "SELECT totp_secret FROM users WHERE id = $1",
            id
        )
			.fetch_optional(&self.pool)
			.await
			.map(|opt| opt.and_then(|row| row.totp_secret))
	}

	async fn set_totp_secret(&self, id: i64, secret: Option<String>) -> Result<(), sqlx::Error> {
		sqlx::query!(
            "UPDATE users SET totp_secret = $1 WHERE id = $2",
            secret,
            id
        )
		.execute(&self.pool).await?;
		Ok(())
	}

	async fn get_pass_hash(&self, id: i64) -> Result<String, sqlx::Error> {
		sqlx::query!(
            "SELECT passhash FROM users WHERE id = $1",
			id
        )
			.fetch_optional(&self.pool)
			.await
			.and_then(|row| row.map(|r| r.passhash).ok_or(sqlx::Error::RowNotFound))
	}

	async fn set_pass_hash(&self, id: i64, pass_hash: &str) -> Result<(), sqlx::Error> {
		sqlx::query!(
            "UPDATE users SET passhash = $1 WHERE id = $2",
            pass_hash,
            id
        )
		.execute(&self.pool).await?;
		Ok(())
	}

	async fn set_email(&self, id: i64, email: &str) -> Result<(), sqlx::Error> {
		sqlx::query!(
            "UPDATE users SET email = $1 WHERE id = $2",
            email,
            id
        )
		.execute(&self.pool).await?;
		Ok(())
	}

	async fn set_role(&self, id: i64, role: &str) -> Result<(), sqlx::Error> {
		sqlx::query(
			"UPDATE users SET role = $1::user_role WHERE id = $2"
		)
		.bind(role)
		.bind(id)
		.execute(&self.pool).await?;
		Ok(())
	}
}

























