use crate::{
    domain::vo::ApiResponse,
    error::ErrorResponse,
    infra::{
        mail::{
            mailer,
            worker::{EmailJob, MAIL_QUEUE},
        },
        middleware::UserId,
        redis::redis_key::{BusinessType, RedisKey},
    },
    repositories::{email_log_repository::EmailLogRepository, user_repository::UserRepository},
    services::auth_service::AuthService,
    AppState,
};
use axum::{extract::State, Json};
use rand::Rng;
use serde::Deserialize;
use serde_json::json;
use validator::{Validate, ValidationError};

#[derive(Debug, Deserialize, Validate)]
pub struct VerificationRequest {
    #[validate(email)]
    pub email: String,
}

fn valid_code(code: &str) -> Result<(), ValidationError> {
    if code.len() == 6 && code.bytes().all(|v| v.is_ascii_digit()) {
        Ok(())
    } else {
        Err(ValidationError::new("code"))
    }
}
#[derive(Debug, Deserialize, Validate)]
pub struct ResetPasswordRequest {
    #[validate(email)]
    pub email: String,
    #[validate(custom = "valid_code")]
    pub code: String,
    #[validate(length(min = 8, max = 128))]
    pub new_password: String,
}

fn code_key(email: &str) -> RedisKey {
    RedisKey::new(BusinessType::Auth)
        .add_identifier("verify_code")
        .add_identifier(email)
}

pub async fn send_verification_code(
    State(state): State<AppState>,
    Json(input): Json<VerificationRequest>,
) -> Result<Json<ApiResponse<()>>, ErrorResponse> {
    input
        .validate()
        .map_err(|e| ErrorResponse::bad_request(e.to_string()))?;
    let redis = state
        .redis_client
        .as_ref()
        .ok_or_else(|| ErrorResponse::unavailable("Redis service unavailable"))?;
    let key = code_key(&input.email);
    if redis
        .exists_key(&key)
        .await
        .map_err(|e| ErrorResponse::unavailable(e.to_string()))?
    {
        return Err(ErrorResponse::too_many_requests(
            "verification code already sent",
        ));
    }
    let code = format!("{:06}", rand::thread_rng().gen_range(0..1_000_000));
    if state.config.email.queue_enabled {
        redis
            .set_key_ex(
                &key,
                &code,
                state.config.email.verification_code_ttl_seconds,
            )
            .await
            .map_err(|e| ErrorResponse::unavailable(e.to_string()))?;
        redis
            .queue_push(
                MAIL_QUEUE,
                &EmailJob {
                    recipient: input.email,
                    code,
                },
            )
            .await
            .map_err(|e| ErrorResponse::unavailable(e.to_string()))?;
        return Ok(Json(ApiResponse::success_with_message(
            (),
            "verification email queued",
        )));
    }
    let result = mailer::send_verification(&state.config.email, &input.email, &code).await;
    EmailLogRepository::new(state.pool.clone())
        .add(
            None,
            input.email.clone(),
            if result.is_ok() { "sent" } else { "failed" }.into(),
            result.as_ref().err().map(ToString::to_string),
        )
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    result.map_err(|e| ErrorResponse::unavailable(e.to_string()))?;
    redis
        .set_key_ex(
            &key,
            &code,
            state.config.email.verification_code_ttl_seconds,
        )
        .await
        .map_err(|e| ErrorResponse::unavailable(e.to_string()))?;
    Ok(Json(ApiResponse::success_with_message(
        (),
        "verification code sent",
    )))
}

pub async fn reset_password(
    State(state): State<AppState>,
    Json(input): Json<ResetPasswordRequest>,
) -> Result<Json<ApiResponse<()>>, ErrorResponse> {
    input
        .validate()
        .map_err(|e| ErrorResponse::bad_request(e.to_string()))?;
    let redis = state
        .redis_client
        .as_ref()
        .ok_or_else(|| ErrorResponse::unavailable("Redis service unavailable"))?;
    let key = code_key(&input.email);
    let stored = redis
        .get_key(&key)
        .await
        .map_err(|e| ErrorResponse::unavailable(e.to_string()))?
        .ok_or_else(|| ErrorResponse::bad_request("verification code expired"))?;
    if stored != input.code {
        return Err(ErrorResponse::bad_request("invalid verification code"));
    }
    let repo = UserRepository::new(state.pool.clone());
    repo.update_password_by_email(
        &input.email,
        AuthService::hash_password(&input.new_password)
            .map_err(|e| ErrorResponse::internal(e.to_string()))?,
    )
    .await
    .map_err(|e| ErrorResponse::bad_request(e.to_string()))?;
    redis
        .delete_key(&key)
        .await
        .map_err(|e| ErrorResponse::unavailable(e.to_string()))?;
    Ok(Json(ApiResponse::success_with_message(
        (),
        "password reset",
    )))
}

pub async fn latest_log(
    State(state): State<AppState>,
    UserId(user_id): UserId,
) -> Result<Json<ApiResponse<serde_json::Value>>, ErrorResponse> {
    let value = EmailLogRepository::new(state.pool)
        .latest(&user_id)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success(
        serde_json::to_value(value).unwrap_or_default(),
    )))
}
pub async fn queue_status(
    State(state): State<AppState>,
) -> Result<Json<ApiResponse<serde_json::Value>>, ErrorResponse> {
    let pending = if let Some(redis) = &state.redis_client {
        redis
            .queue_len(MAIL_QUEUE)
            .await
            .map_err(|e| ErrorResponse::unavailable(e.to_string()))?
    } else {
        0
    };
    Ok(Json(ApiResponse::success(
        json!({"enabled": state.config.email.queue_enabled, "redis_available": state.redis_client.is_some(), "pending": pending, "workers": state.config.email.worker_pool_size}),
    )))
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn validates_code() {
        assert!(valid_code("123456").is_ok());
        assert!(valid_code("12x").is_err());
    }
}
