use crate::AppService;
use crate::error::AppError;
use argon2::{Argon2, PasswordHash, PasswordVerifier};
use models::users::{user_email, user_email_change, user_password};
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;

#[derive(Debug, Clone, Deserialize, Serialize, utoipa::ToSchema)]
pub struct EmailChangeRequest {
    pub new_email: String,
    pub password: String,
}

#[derive(Debug, Clone, Deserialize, Serialize, utoipa::ToSchema)]
pub struct EmailVerifyRequest {
    pub token: String,
}

#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct EmailResponse {
    pub email: Option<String>,
}

impl AppService {
    /// Get the current email address for the authenticated user.
    pub async fn auth_get_email(&self, ctx: &Session) -> Result<EmailResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;

        let email = user_email::Entity::find()
            .filter(user_email::Column::User.eq(user_uid))
            .one(&self.db)
            .await
            .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        Ok(EmailResponse {
            email: email.map(|e| e.email),
        })
    }

    /// Request an email change: validates password, stores a pending token,
    /// and sends a verification email to the new address.
    pub async fn auth_email_change_request(
        &self,
        ctx: &Session,
        params: EmailChangeRequest,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;

        // Verify password
        let password = self.auth_rsa_decode(ctx, params.password).await?;

        let user_password = user_password::Entity::find()
            .filter(user_password::Column::User.eq(user_uid))
            .one(&self.db)
            .await
            .ok()
            .flatten()
            .ok_or(AppError::UserNotFound)?;

        let hash =
            PasswordHash::new(&user_password.password_hash).map_err(|_| AppError::UserNotFound)?;
        Argon2::default()
            .verify_password(password.as_bytes(), &hash)
            .map_err(|_| AppError::InvalidPassword)?;

        // Check new email is not already taken
        let existing = user_email::Entity::find()
            .filter(user_email::Column::Email.eq(&params.new_email))
            .one(&self.db)
            .await
            .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        if existing.is_some() {
            return Err(AppError::EmailExists);
        }

        // Generate token and store pending change
        let token = self.generate_reset_token();
        let expires_at = chrono::Utc::now() + chrono::Duration::hours(24);

        let _ = user_email_change::Entity::delete_many()
            .filter(user_email_change::Column::UserUid.eq(user_uid))
            .filter(user_email_change::Column::Used.eq(false))
            .exec(&self.db)
            .await;

        user_email_change::ActiveModel {
            token: Set(token.clone()),
            user_uid: Set(user_uid),
            new_email: Set(params.new_email.clone()),
            expires_at: Set(expires_at),
            used: Set(false),
            created_at: Set(chrono::Utc::now()),
        }
        .insert(&self.db)
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        // Queue verification email via Redis Stream
        let domain = self
            .config
            .main_domain()
            .map_err(|_| AppError::DoMainNotSet)?;

        let verify_link = format!("{}/auth/verify-email?token={}", domain, token);

        let envelope = queue::EmailEnvelope {
            id: Uuid::new_v4(),
            to: params.new_email.clone(),
            subject: "Confirm Email Change".to_string(),
            body: format!(
                "You have requested to change your email address.\n\n\
                Please click the link below to confirm:\n\n{}\n\n\
                This link will expire in 24 hours.\n\n\
                If you did not request this change, please ignore this email.",
                verify_link
            ),
            created_at: chrono::Utc::now(),
        };

        self.queue_producer
            .publish_email(envelope)
            .await
            .map_err(|e| AppError::InternalServerError(e.to_string()))?;

        tracing::info!(new_email = %params.new_email, user_uid = %user_uid, "Email change verification queued");
        Ok(())
    }

    /// Verify an email change token and apply the new email.
    pub async fn auth_email_verify(&self, params: EmailVerifyRequest) -> Result<(), AppError> {
        let change = user_email_change::Entity::find()
            .filter(user_email_change::Column::Token.eq(&params.token))
            .filter(user_email_change::Column::Used.eq(false))
            .one(&self.db)
            .await
            .map_err(|e| AppError::DatabaseError(e.to_string()))?
            .ok_or(AppError::NotFound("Invalid or expired token".to_string()))?;

        // Check expiry
        if change.expires_at < chrono::Utc::now() {
            return Err(AppError::NotFound("Token has expired".to_string()));
        }

        // Update or insert the new email in user_email
        let existing_email = user_email::Entity::find()
            .filter(user_email::Column::User.eq(change.user_uid))
            .one(&self.db)
            .await
            .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        match existing_email {
            Some(email_model) => {
                let mut active: user_email::ActiveModel = email_model.into();
                active.email = Set(change.new_email.clone());
                active.update(&self.db).await
            }
            None => {
                user_email::ActiveModel {
                    user: Set(change.user_uid),
                    email: Set(change.new_email.clone()),
                    created_at: Set(chrono::Utc::now()),
                }
                .insert(&self.db)
                .await
            }
        }
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        // Mark token as used
        let new_email = change.new_email.clone();
        let user_uid = change.user_uid;
        let mut used_change: user_email_change::ActiveModel = change.into();
        used_change.used = Set(true);
        used_change
            .update(&self.db)
            .await
            .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        tracing::info!(new_email = %new_email, user_uid = %user_uid, "Email changed successfully");
        Ok(())
    }
}
