use crate::repo::{UserRepo, AccessTokenRepoTrait};
use crate::model::user::{User, UserRole};
use crate::model::auth::AccessToken;
use rocket::async_trait;
use std::sync::Mutex;
use chrono::Utc;
use std::sync::Arc;
use sqlx::Error;
use crate::api::totp::TotpStatus;
use crate::api::totp::TotpStatus::Disabled;

pub struct MockAccessTokenRepo {
    pub tokens: Mutex<Vec<AccessToken>>,
}

#[async_trait]
impl AccessTokenRepoTrait for MockAccessTokenRepo {
    async fn get_by_id(&self, id: i64) -> Option<AccessToken> {
        self.tokens.lock().unwrap().iter().find(|t| t.id == id).map(|t| AccessToken { id: t.id, code: t.code.clone() })
    }
    async fn create_new(&self, _uid: i64, _name: &str, code: &str, _max_uses: i32, _start_date: chrono::DateTime<Utc>, _expiry_date: chrono::DateTime<Utc>) -> Result<i64, sqlx::Error> {
        let mut tokens = self.tokens.lock().unwrap();
        let id = tokens.len() as i64 + 1;
        tokens.push(AccessToken { id, code: code.to_string() });
        Ok(id)
    }

    async fn use_token(&self, id: i64) -> Result<(), Error> {
        // let mut tokens = self.tokens.lock().unwrap();
        // if let Some(pos) = tokens.iter().position(|t| t.id == id) {
        //     tokens.get_mut(pos).uses =
        // }
        Ok(())
    }

    async fn get_code_not_expired(&self, code: &str) -> Result<Option<AccessToken>, Error> {
        Ok(self.tokens.lock().unwrap()
            .iter().find(|t| t.code == code)
            .map(|t| AccessToken { id: t.id, code: t.code.clone() }))
    }
}

pub struct MockUserRepo {
    pub users: Mutex<Vec<User>>,
}

#[async_trait]
impl UserRepo for MockUserRepo {
    fn pool(&self) -> &sqlx::PgPool {
        unimplemented!("MockUserRepo does not have a real pool")
    }
    async fn get_by_id(&self, id: i64) -> Option<User> {
        self.users.lock().unwrap().iter().find(|u| u.id == id).cloned()
    }
    async fn save(&self, user: &User) -> Result<(), sqlx::Error> {
        let mut users = self.users.lock().unwrap();
        if let Some(pos) = users.iter().position(|u| u.id == user.id) {
            users[pos] = user.clone();
        }
        Ok(())
    }

    async fn is_admin(&self, uid: i64) -> Result<UserRole, Error> {
        let user = self.users.lock().unwrap().iter().find(|u| u.id == uid).cloned();
        if let Some(user) = user {
            if user.id == 1 {
                return Ok(UserRole::Admin);
            } else {
                return Ok(UserRole::User);
            }
        }

        panic!("user not found in test")
    }

    async fn new_user(&self, email: &str, username: &str, pass_hash: &str) -> Result<i64, sqlx::Error> {
        let mut users = self.users.lock().unwrap();
        let id = users.len() as i64 + 1;
        users.push(User {
            id,
            email: Some(email.to_string()),
            username: username.to_string(),
            nickname: None,
            passhash: pass_hash.to_string(),
            totp_status: Disabled,
            totp_secret: None,
            created_at: Some(Utc::now()),
            updated_at: Some(Utc::now()),
        });
        Ok(id)
    }
    async fn get_by_username(&self, username: &str) -> Result<Option<User>, sqlx::Error> {
        Ok(self.users.lock().unwrap().iter().find(|u| u.username == username).cloned())
    }
    async fn delete_by_id(&self, id: i64) -> Result<(), sqlx::Error> {
        self.users.lock().unwrap().retain(|u| u.id != id);
        Ok(())
    }
    async fn set_display_name(&self, id: i64, display_name: Option<String>) -> Result<(), sqlx::Error> {
        if let Some(u) = self.users.lock().unwrap().iter_mut().find(|u| u.id == id) {
            u.nickname = display_name;
        }
        Ok(())
    }
    async fn set_username(&self, id: i64, username: &str) -> Result<(), sqlx::Error> {
        if let Some(u) = self.users.lock().unwrap().iter_mut().find(|u| u.id == id) {
            u.username = username.to_string();
        }
        Ok(())
    }
    async fn set_twofa_enabled(&self, id: i64, enabled: &TotpStatus) -> Result<(), sqlx::Error> {
        if let Some(u) = self.users.lock().unwrap().iter_mut().find(|u| u.id == id) {
            u.totp_status = *enabled;
        }
        Ok(())
    }
    async fn get_totp_secret(&self, id: i64) -> Result<Option<String>, sqlx::Error> {
        Ok(self.users.lock().unwrap().iter().find(|u| u.id == id).and_then(|u| u.totp_secret.clone()))
    }
    async fn set_totp_secret(&self, id: i64, secret: Option<String>) -> Result<(), sqlx::Error> {
        if let Some(u) = self.users.lock().unwrap().iter_mut().find(|u| u.id == id) {
            u.totp_secret = secret;
        }
        Ok(())
    }
    async fn get_pass_hash(&self, id: i64) -> Result<String, sqlx::Error> {
        Ok(self.users.lock().unwrap().iter().find(|u| u.id == id).map(|u| u.passhash.clone()).unwrap())
    }
    async fn set_pass_hash(&self, id: i64, pass_hash: &str) -> Result<(), sqlx::Error> {
        if let Some(u) = self.users.lock().unwrap().iter_mut().find(|u| u.id == id) {
            u.passhash = pass_hash.to_string();
        }
        Ok(())
    }
    async fn set_email(&self, id: i64, email: &str) -> Result<(), sqlx::Error> {
        if let Some(u) = self.users.lock().unwrap().iter_mut().find(|u| u.id == id) {
            u.email = Some(email.to_string());
        }
        Ok(())
    }
    async fn set_role(&self, _id: i64, _role: &str) -> Result<(), sqlx::Error> {
        Ok(())
    }
}

pub struct MockTokenRepo {
    pub tokens: Mutex<Vec<AccessToken>>,
}

#[async_trait]
impl AccessTokenRepoTrait for MockTokenRepo {
    async fn get_by_id(&self, id: i64) -> Option<AccessToken> {
        self.tokens.lock().unwrap().iter().find(|t| t.id == id).map(|t| AccessToken { id: t.id, code: t.code.clone() })
    }
    async fn create_new(&self, _uid: i64, _name: &str, code: &str, _max_uses: i32, _start_date: chrono::DateTime<Utc>, _expiry_date: chrono::DateTime<Utc>) -> Result<i64, sqlx::Error> {
        let mut tokens = self.tokens.lock().unwrap();
        let id = tokens.len() as i64 + 1;
        tokens.push(AccessToken { id, code: code.to_string() });
        Ok(id)
    }
    async fn use_token(&self, _id: i64) -> Result<(), sqlx::Error> {
        Ok(())
    }
    async fn get_code_not_expired(&self, code: &str) -> Result<Option<AccessToken>, sqlx::Error> {
        Ok(self.tokens.lock().unwrap().iter().find(|t| t.code == code).map(|t| AccessToken { id: t.id, code: t.code.clone() }))
    }
}
