use crate::auth::entity::Profile;

#[derive(Debug)]
pub struct LoginRequest {
    pub email: super::entity::Email,
    pub password: super::entity::Password,
}

#[derive(Debug)]
pub struct LoginSuccess {
    pub token: String,
}

#[derive(Debug, thiserror::Error)]
pub enum LoginError {
    #[error("invalid credentials")]
    InvalidCredentials,
    #[error(transparent)]
    Internal(#[from] anyhow::Error),
}

#[derive(Debug)]
pub struct SignupRequest {
    pub email: super::entity::Email,
    pub password: super::entity::Password,
}

#[derive(Debug, thiserror::Error)]
pub enum SignupError {
    #[error("email address already used")]
    EmailConflict,
    #[error(transparent)]
    Internal(#[from] anyhow::Error),
}

#[derive(Debug, thiserror::Error)]
pub enum VerifyError {
    #[error("expired token")]
    ExpiredToken,
    #[error("invalid token")]
    InvalidToken,
    #[error(transparent)]
    Internal(#[from] anyhow::Error),
}

#[cfg_attr(any(test, feature = "mocks"), auto_impl::auto_impl(Arc))]
#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait AuthenticationService: Send + Sync + 'static {
    fn login(
        &self,
        request: LoginRequest,
    ) -> impl Future<Output = Result<LoginSuccess, LoginError>> + Send;
    fn signup(
        &self,
        request: SignupRequest,
    ) -> impl Future<Output = Result<LoginSuccess, SignupError>> + Send;
    fn verify(&self, token: &str) -> impl Future<Output = Result<Profile, VerifyError>> + Send;
}

#[cfg_attr(any(test, feature = "mocks"), auto_impl::auto_impl(Arc))]
#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait AuthenticationRepository: Send + Sync + 'static {
    fn find_by_credentials(
        &self,
        email: &str,
        password: &str,
    ) -> impl Future<Output = anyhow::Result<Option<Profile>>> + Send;
    fn create(
        &self,
        email: &str,
        password: &str,
    ) -> impl Future<Output = Result<Profile, SignupError>> + Send;
}

#[cfg_attr(any(test, feature = "mocks"), auto_impl::auto_impl(Arc))]
#[cfg_attr(any(test, feature = "mocks"), mockall::automock)]
pub trait TokenRepository: Send + Sync + 'static {
    fn create_token(
        &self,
        profile: &Profile,
    ) -> impl Future<Output = anyhow::Result<String>> + Send;
    fn decode_token(
        &self,
        token: &str,
    ) -> impl Future<Output = Result<Profile, VerifyError>> + Send;
}
