use crate::api::auth::{Claims, TokenScope};
use crate::api::totp::totp_gen;
use crate::error::{ApiResult, AppError};
use crate::model::auth::AuthResponse;
use crate::repo::{UserRepo, AccessTokenRepoTrait};
use std::sync::Arc;
use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::SaltString;
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use chrono::{DateTime, Utc};
use uuid::Uuid;
use crate::api::totp::TotpStatus::{Disabled, Enabled};
use crate::model::user::UserRole::Admin;
use crate::svc::access_token_svc::AccessTokenService;

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

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

	pub async fn signup(&self,
		email: &str, username: &str,
		password: &str, access_token: &str
	) -> ApiResult<AuthResponse> {
		let tok_id = self.tokens.get_valid_token(access_token).await?.id;

		let pass = password.to_string();
		let svc = self.clone();
		let hashed = tokio::task::spawn_blocking(move || svc.hash_password(&pass))
			.await
			.map_err(|_| AppError::internal("blocking task panicked"))??;

		let uid = self.users
			.new_user(email, username, &hashed).await?;

		self.tokens.use_token(tok_id).await?;

		let jwt = Claims::new(uid as usize, TokenScope::Full).encode();
		Ok(AuthResponse {
			token: jwt,
			totp_required: false
		})
	}

	pub async fn login(&self, username: &str, password: &str) -> ApiResult<AuthResponse> {
		let user = self.users
			.get_by_username(username).await?
			.ok_or(AppError::unauthorised("invalid username"))?;

		let pass = password.to_string();
		let user_hash = user.passhash.clone();
		let svc = self.clone();
		tokio::task::spawn_blocking(move || svc.verify_password(&user_hash, &pass))
			.await
			.map_err(|_| AppError::internal("blocking task panicked"))??;

		let scope = if user.totp_status == Enabled { TokenScope::TotpPending } else { TokenScope::Full };
		let jwt = Claims::new(user.id as usize, scope).encode();

		Ok(AuthResponse {
			token: jwt,
			totp_required: user.totp_status == Enabled
		})
	}

	pub async fn login_totp(&self, uid: i64, code: &str) -> ApiResult<AuthResponse> {
		let secret = self.users.get_totp_secret(uid).await?
			.ok_or(AppError::unauthorised("2fa not enabled"))?;

		self.verify_2fa(uid, &secret, code)?;

		let jwt = Claims::new(uid as usize, TokenScope::Full).encode();

		Ok(AuthResponse {
			token: jwt,
			totp_required: false
		})
	}

	pub async fn disable_totp(&self, uid: i64, password: &str, totp_code: &str) -> ApiResult<AuthResponse> {
		let mut user = self.users.get_by_id(uid).await
			.ok_or(AppError::internal("user not found"))?;

		let Some(secret) = user.totp_secret else {
			return Err(AppError::bad_request("2fa not enabled"));
		};

		self.verify_password(&user.passhash, password)?;
		self.verify_2fa(uid, &secret, totp_code)?;

		user.totp_secret = None;
		user.totp_status = Disabled;
		self.users.save(&user).await?;

		Ok(AuthResponse {
			token: Claims::new(uid as usize, TokenScope::Full).encode(),
			totp_required: false
		})
	}

	pub async fn is_admin(&self, uid: i64) -> ApiResult<bool> {
		Ok(self.users.is_admin(uid).await? == Admin)
	}

	pub async fn get_totp_status(&self, uid: i64) -> ApiResult<bool> {
		Ok(
			self.users.get_totp_secret(uid).await?.is_some()
		)
	}

	pub async fn confirm_totp(&self, uid: i64, totp_code: &str) -> ApiResult<()> {
		let secret = self.users.get_totp_secret(uid).await?
			.ok_or(AppError::bad_request("2fa setup not initialised"))?;

		self.verify_2fa(uid, &secret, totp_code)?;

		self.users.set_twofa_enabled(uid, &Enabled).await?;

		Ok(())
	}

	pub async fn get_or_create_totp_secret(
		&self, uid: i64, password: &str,
	) -> ApiResult<String> {
		let user = self.users.get_by_id(uid).await
			.ok_or(AppError::internal("user not found"))?;

		let pass = password.to_string();
		let user_hash = user.passhash.clone();
		let svc = self.clone();
		tokio::task::spawn_blocking(move || svc.verify_password(&user_hash, &pass))
			.await
			.map_err(|_| AppError::internal("blocking task panicked"))??;

		if let Some(secret) = user.totp_secret {
			return Ok(secret);
		}

		let new_secret = totp_rs::Secret::generate_secret()
			.to_encoded()
			.to_string();

		self.users.set_totp_secret(uid, Some(new_secret.clone())).await?;

		Ok(new_secret)
	}

	pub async fn verify_user_password(&self, uid: i64, password: &str) -> ApiResult<()> {
		let hash = self.users.get_pass_hash(uid).await
			.map_err(|_| AppError::internal("user not found"))?;

		let pass = password.to_string();
		let svc = self.clone();
		tokio::task::spawn_blocking(move || svc.verify_password(&hash, &pass))
			.await
			.map_err(|_| AppError::internal("blocking task panicked"))??;

		Ok(())
	}

	pub async fn verify_user_totp(&self, uid: i64, totp_code: &str) -> ApiResult<()> {
		let secret = self.users.get_totp_secret(uid).await?
			.ok_or(AppError::internal("user not found"))?;

		self.verify_2fa(uid, &secret, totp_code)
	}


	pub fn hash_password(&self, password: &str) -> ApiResult<String> {
		let salt = SaltString::generate(&mut OsRng);
		Argon2::default()
			.hash_password(password.as_bytes(), &salt)
			.map_err(|_| AppError::internal("failed to hash password"))
			.map(|hash| hash.to_string())
	}

	// Private helpers
	 fn verify_password(&self, pass_hash: &str, password: &str) -> ApiResult<()> {
		let parsed_hash = PasswordHash::new(&pass_hash)
			.map_err(|_| AppError::internal("invalid password hash"))?;

		Argon2::default()
			.verify_password(password.as_bytes(), &parsed_hash)
			.map_err(|_| AppError::unauthorised("incorrect password"))?;

		Ok(())
	}

	pub fn verify_2fa(&self, uid: i64, totp_secret: &str, totp_code: &str) -> ApiResult<()> {
		 if totp_gen(uid, totp_secret.as_bytes())
			.map_err(|_| AppError::internal("invalid totp secret"))?
			.check_current(totp_code)
			.map_err(|_| AppError::internal("invalid totp code"))? {
			 Ok(())
		 } else {
			 Err(AppError::unauthorised("incorrect totp code"))
		 }
	 }
}

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

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

	#[tokio::test]
	async fn test_signup_and_login() {
		let auth = setup();
		let code = auth.tokens.create(1, "test", 1, Utc::now(), Utc::now()).await.unwrap();

		let signup_res = auth.signup("test@example.com", "tester", "password123", &code).await;
		assert!(signup_res.is_ok());

		let login_res = auth.login("tester", "password123").await;
		assert!(login_res.is_ok());
		let login_data = login_res.unwrap();
		assert!(!login_data.totp_required);
		assert!(!login_data.token.is_empty());
	}

	#[tokio::test]
	async fn test_login_invalid_password() {
		let auth = setup();
		let token_code = auth.tokens.create(1, "test", 1, Utc::now(), Utc::now()).await.unwrap();
		auth.signup("test@example.com", "tester", "password123", &token_code).await.unwrap();

		let login_res = auth.login("tester", "wrong_password").await;
		assert!(login_res.is_err());
		if let Err(AppError::Unauthorised(msg)) = login_res {
			assert_eq!(msg, "incorrect password");
		} else {
			panic!("Expected Unauthorised error");
		}
	}

	#[tokio::test]
	async fn test_invite() {
		let auth = setup();
		let res = auth.tokens.create(1, "invite", 1, Utc::now(), Utc::now() + chrono::Duration::days(1)).await;
		assert!(res.is_ok());
		let code = res.unwrap();
		assert!(!code.is_empty());

		let token = auth.tokens.get_valid_token(&code).await;
		assert!(token.is_ok());
	}
}