//! The `SettingsService` is responsible for managing user account settings, allowing users to
//! update their username, password, display name, email, and delete their account.
//! It interacts with the `AuthService` to handle authentication and password-related functionality
//! and the `UserRepository` to perform updates to user accounts in the data store.

use crate::error::{ApiResult, AppError};
use crate::repo::UserRepo;
use crate::svc::auth_svc::AuthService;
use std::sync::Arc;

#[derive(Clone)]
pub struct SettingsService {
	auth: AuthService,
	users: Arc<dyn UserRepo>,
}

impl SettingsService {
	pub fn new(auth: AuthService, users: Arc<dyn UserRepo>) -> Self {
		Self { auth, users }
	}

	pub async fn change_username(&self, uid: i64, new: &str) -> ApiResult<()> {
		self.users.set_username(uid, new).await?;
		Ok(())
	}

	pub async fn change_password(&self, uid: i64, old: &str, new: &str) -> ApiResult<()> {
		self.auth.verify_user_password(uid, old).await?;
		let hashed = self.auth.hash_password(new)?;
		self.users.set_pass_hash(uid, &hashed).await?;
		Ok(())
	}

	pub async fn change_display_name(&self, uid: i64, new: Option<String>) -> ApiResult<()> {
		self.users.set_display_name(uid, new).await?;
		Ok(())
	}

	pub async fn change_email(&self, uid: i64, new: &str) -> ApiResult<()> {
		self.users.set_email(uid, new).await?;
		Ok(())
	}

	pub async fn delete_account(&self, uid: i64, password: &str, totp_code: &Option<String>) -> ApiResult<()> {
		self.auth.verify_user_password(uid, password).await?;

		// check 2fa code is correct if enabled
		if self.auth.get_totp_status(uid).await? {

			let Some(totp_code) = totp_code else {
				return Err(AppError::unauthorised("2fa code is required"))
			};

			self.auth.verify_user_totp(uid, totp_code).await?;
		}

		self.users.delete_by_id(uid).await?;
		Ok(())
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::repo::mock::{MockUserRepo, MockTokenRepo};
	use std::sync::Mutex;
	use chrono::Utc;
	use crate::svc::access_token_svc::AccessTokenService;

	fn setup() -> SettingsService {
		unsafe {
			std::env::set_var("JWT_SECRET", "test_secret");
		}
		let users = Arc::new(MockUserRepo { users: Mutex::new(vec![]) });
		let token_repo = Arc::new(MockTokenRepo { tokens: Mutex::new(vec![]) });
		let tokens_svc = AccessTokenService::new(token_repo);
		let auth = AuthService::new(users.clone(), tokens_svc.clone());
		SettingsService::new(auth, users)
	}

	#[tokio::test]
	async fn test_change_username() {
		let settings = setup();
		let uid = settings.users.new_user("test@example.com", "old", "pass").await.unwrap();

		settings.change_username(uid, "new").await.unwrap();
		let user = settings.users.get_by_id(uid).await.unwrap();
		assert_eq!(user.username, "new");
	}

	#[tokio::test]
	async fn test_change_password() {
		let settings = setup();
		let pass = "old_pass";
		let hashed = settings.auth.hash_password(pass).unwrap();
		let uid = settings.users.new_user("test@example.com", "user", &hashed).await.unwrap();

		settings.change_password(uid, pass, "new_pass").await.unwrap();
		let _user = settings.users.get_by_id(uid).await.unwrap();
		assert!(settings.auth.verify_user_password(uid, "new_pass").await.is_ok());
	}

	#[tokio::test]
	async fn test_delete_account() {
		let settings = setup();
		let pass = "password";
		let hashed = settings.auth.hash_password(pass).unwrap();
		let uid = settings.users.new_user("test@example.com", "user", &hashed).await.unwrap();

		let res = settings.delete_account(uid, pass, &None).await;
		assert!(res.is_ok());

		let user = settings.users.get_by_id(uid).await;
		assert!(user.is_none());
	}
}