use async_trait::async_trait;
use chrono::{Duration, Utc};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use uuid::Uuid;

use athena_auth_core::{AuthContext, AuthError, AuthResult};
use athena_auth_core::{
    AuthEmailRenderContext, build_auth_email_message, verification_email_for_context,
};
use athena_auth_core::{AuthRequest, AuthResponse, CreateEmailSendFailure, CreateVerification};
use athena_auth_core::{AuthUser, DatabaseAdapter, User};

use athena_auth_core::utils::cookie_utils::create_session_cookie;

use super::StatusResponse;
use super::helpers::{extract_session_client_info, spawn_background_task};

pub(super) mod handlers;
pub(super) mod types;

#[cfg(test)]
mod tests;

use handlers::*;
use types::*;

async fn record_email_send_failure<DB: DatabaseAdapter>(
    ctx: &AuthContext<DB>,
    user_id: Option<&str>,
    recipient_email: &str,
    flow: &str,
    provider: Option<&str>,
    error_message: &str,
    metadata: Option<serde_json::Value>,
) {
    let log_row = CreateEmailSendFailure {
        user_id: user_id.map(std::string::ToString::to_string),
        recipient_email: recipient_email.to_string(),
        flow: flow.to_string(),
        provider: provider.map(std::string::ToString::to_string),
        error_message: error_message.to_string(),
        metadata,
    };

    if let Err(log_err) = ctx.database.create_email_send_failure(log_row).await {
        tracing::error!(
            recipient_email = %recipient_email,
            flow = %flow,
            provider = ?provider,
            failure_log_error = %log_err,
            "Failed to persist email send failure"
        );
    }
}

/// Trait for custom email sending logic.
///
/// When set on [`EmailVerificationConfig::send_verification_email`], this
/// callback overrides the default `EmailProvider`-based sending.
#[async_trait]
pub trait SendVerificationEmail: Send + Sync {
    async fn send(&self, user: &User, url: &str, token: &str) -> AuthResult<()>;
}

/// Shorthand for the async hook closure type used by
/// [`EmailVerificationConfig::before_email_verification`] and
/// [`EmailVerificationConfig::after_email_verification`].
pub type EmailVerificationHook =
    Arc<dyn Fn(&User) -> Pin<Box<dyn Future<Output = AuthResult<()>> + Send>> + Send + Sync>;

/// Email verification plugin for handling email verification flows
pub struct EmailVerificationPlugin {
    config: EmailVerificationConfig,
}

impl Clone for EmailVerificationPlugin {
    fn clone(&self) -> Self {
        Self {
            config: self.config.clone(),
        }
    }
}

#[derive(athena_auth_core::PluginConfig)]
#[plugin(name = "EmailVerificationPlugin")]
pub struct EmailVerificationConfig {
    /// How long a verification token stays valid. Default: 24 hours.
    #[config(default = Duration::hours(24))]
    pub verification_token_expiry: Duration,
    /// Whether to send email notifications (on sign-up). Default: true.
    #[config(default = true)]
    pub send_email_notifications: bool,
    /// Whether email verification is required before sign-in. Default: false.
    #[config(default = false)]
    pub require_verification_for_signin: bool,
    /// Whether to auto-verify newly created users. Default: false.
    #[config(default = false)]
    pub auto_verify_new_users: bool,
    /// When true, automatically send a verification email on sign-in if the
    /// user is unverified. Default: false.
    #[config(default = false)]
    pub send_on_sign_in: bool,
    /// When true, create a session after email verification and return the
    /// session token in the verify-email response. Default: false.
    #[config(default = false)]
    pub auto_sign_in_after_verification: bool,
    /// Optional custom email sender. When set this overrides the default
    /// `EmailProvider`-based sending.
    #[config(default = None, skip)]
    pub send_verification_email: Option<Arc<dyn SendVerificationEmail>>,
    /// Hook invoked **before** email verification (before updating the user).
    #[config(default = None)]
    pub before_email_verification: Option<EmailVerificationHook>,
    /// Hook invoked **after** email verification (after the user has been updated).
    #[config(default = None)]
    pub after_email_verification: Option<EmailVerificationHook>,
}

impl Clone for EmailVerificationConfig {
    fn clone(&self) -> Self {
        Self {
            verification_token_expiry: self.verification_token_expiry,
            send_email_notifications: self.send_email_notifications,
            require_verification_for_signin: self.require_verification_for_signin,
            auto_verify_new_users: self.auto_verify_new_users,
            send_on_sign_in: self.send_on_sign_in,
            auto_sign_in_after_verification: self.auto_sign_in_after_verification,
            send_verification_email: self.send_verification_email.clone(),
            before_email_verification: self.before_email_verification.clone(),
            after_email_verification: self.after_email_verification.clone(),
        }
    }
}

impl EmailVerificationConfig {
    /// Backward-compatible helper: return the expiry duration expressed as
    /// whole hours (truncated).
    pub fn expiry_hours(&self) -> i64 {
        self.verification_token_expiry.num_hours()
    }
}

impl EmailVerificationPlugin {
    /// Backward-compatible builder: set token expiry in hours.
    pub fn verification_token_expiry_hours(mut self, hours: i64) -> Self {
        self.config.verification_token_expiry = Duration::hours(hours);
        self
    }

    pub fn custom_send_verification_email(
        mut self,
        sender: Arc<dyn SendVerificationEmail>,
    ) -> Self {
        self.config.send_verification_email = Some(sender);
        self
    }

    pub(crate) fn spawn_send_verification_on_sign_in<DB: DatabaseAdapter>(
        &self,
        user: DB::User,
        callback_url: Option<String>,
        ctx: AuthContext<DB>,
    ) {
        let plugin = self.clone();
        let email = user.email().map(str::to_string);

        spawn_background_task("send_verification_on_sign_in", async move {
            if let Err(error) = plugin
                .send_verification_on_sign_in(&user, callback_url.as_deref(), &ctx)
                .await
            {
                tracing::warn!(
                    user_id = %user.id(),
                    email = ?email,
                    error = %error,
                    "verification email on sign-in failed or skipped"
                );
            }
        });
    }
}

athena_auth_core::impl_auth_plugin! {
    EmailVerificationPlugin, "email-verification";
    routes {
        post "/send-verification-email" => handle_send_verification_email, "send_verification_email";
        get "/verify-email" => handle_verify_email, "verify_email";
    }
    extra {
        async fn on_user_created(&self, user: &DB::User, ctx: &AuthContext<DB>) -> AuthResult<()> {
            // Send verification email for new users if configured.
            // Also fire when a custom sender is set, even if send_email_notifications is false.
            if (self.config.send_email_notifications || self.config.send_verification_email.is_some())
                && !user.email_verified()
                && let Some(email) = user.email()
                && let Err(e) = self
                    .send_verification_email_for_user(
                        user,
                        email,
                        None,
                        "signup_auto_verification",
                        ctx,
                    )
                    .await
            {
                tracing::warn!(
                    email = %email,
                    error = %e,
                    "Failed to send verification email"
                );
            }
            Ok(())
        }
    }
}

// ---------------------------------------------------------------------------
// Route handlers (delegate to core functions)
// ---------------------------------------------------------------------------

impl EmailVerificationPlugin {
    async fn handle_send_verification_email<DB: DatabaseAdapter>(
        &self,
        req: &AuthRequest,
        ctx: &AuthContext<DB>,
    ) -> AuthResult<AuthResponse> {
        let body: SendVerificationEmailRequest = match athena_auth_core::validate_request_body(req)
        {
            Ok(v) => v,
            Err(resp) => return Ok(resp),
        };
        let response: StatusResponse =
            send_verification_email_core(&body, &self.config, ctx).await?;
        Ok(AuthResponse::json(200, &response)?)
    }

    async fn handle_verify_email<DB: DatabaseAdapter>(
        &self,
        req: &AuthRequest,
        ctx: &AuthContext<DB>,
    ) -> AuthResult<AuthResponse> {
        let token: &String = req
            .query
            .get("token")
            .ok_or_else(|| AuthError::bad_request("Verification token is required"))?;
        let callback_url: Option<String> = req.query.get("callbackURL").cloned();
        let query: VerifyEmailQuery = VerifyEmailQuery {
            token: token.clone(),
            callback_url,
        };

        let (ip_address, user_agent) = extract_session_client_info(ctx, req);

        match verify_email_core(&query, &self.config, ip_address, user_agent, ctx).await? {
            VerifyEmailResult::AlreadyVerified(data) => Ok(AuthResponse::json(200, &data)?),
            VerifyEmailResult::Redirect { url, session_token } => {
                let mut headers: std::collections::HashMap<String, String> =
                    std::collections::HashMap::new();
                headers.insert("Location".to_string(), url);
                if let Some(token) = session_token {
                    let cookie = create_session_cookie(&token, &ctx.config);
                    headers.insert("Set-Cookie".to_string(), cookie);
                }
                Ok(AuthResponse {
                    status: 302,
                    headers,
                    body: Vec::new(),
                })
            }
            VerifyEmailResult::Json(data) => Ok(AuthResponse::json(200, &data)?),
            VerifyEmailResult::JsonWithSession {
                response,
                session_token,
            } => {
                let cookie = create_session_cookie(&session_token, &ctx.config);
                Ok(AuthResponse::json(200, &response)?.with_header("Set-Cookie", cookie))
            }
        }
    }

    /// Send a verification email for a specific user.
    ///
    /// If [`EmailVerificationConfig::send_verification_email`] is set the
    /// custom callback is used; otherwise the default `EmailProvider` path is
    /// taken.
    async fn send_verification_email_for_user<DB: DatabaseAdapter>(
        &self,
        user: &DB::User,
        email: &str,
        callback_url: Option<&str>,
        flow: &str,
        ctx: &AuthContext<DB>,
    ) -> AuthResult<()> {
        // Generate verification token
        let verification_token: String = format!("verify_{}", Uuid::new_v4());
        let expires_at: chrono::DateTime<Utc> = Utc::now() + self.config.verification_token_expiry;

        // Create verification token
        let create_verification = CreateVerification {
            identifier: email.to_string(),
            value: verification_token.clone(),
            expires_at,
        };

        ctx.database
            .create_verification(create_verification)
            .await?;

        let verification_url = build_verification_url(ctx, &verification_token, callback_url);

        // Use custom sender if configured, otherwise fall back to EmailProvider
        if let Some(ref custom_sender) = self.config.send_verification_email {
            let wrapped = User::from(user);
            if let Err(err) = custom_sender
                .send(&wrapped, &verification_url, &verification_token)
                .await
            {
                record_email_send_failure(
                    ctx,
                    Some(wrapped.id.as_str()),
                    email,
                    flow,
                    Some("custom_sender"),
                    &err.to_string(),
                    None,
                )
                .await;
                tracing::error!(
                    recipient_email = %email,
                    flow = %flow,
                    provider = "custom_sender",
                    error = %err,
                    "Failed to send verification email"
                );
                return Err(err);
            }
            tracing::info!(
                recipient_email = %email,
                flow = %flow,
                user_id = %user.id(),
                provider = "custom_sender",
                "verification email sent"
            );
        } else if self.config.send_email_notifications {
            if ctx.email_provider.is_some() {
                let bodies = verification_email_for_context(
                    ctx,
                    &verification_url,
                    AuthEmailRenderContext::new().with_user_id(user.id()),
                )
                .await;
                let message = match build_auth_email_message(ctx, email, &bodies).await {
                    Ok(message) => message,
                    Err(error) => {
                        record_email_send_failure(
                            ctx,
                            Some(user.id()),
                            email,
                            flow,
                            Some("email_provider"),
                            &error.to_string(),
                            error.metadata().cloned(),
                        )
                        .await;
                        return Err(AuthError::internal(error.to_string()));
                    }
                };
                if let Err(error) = ctx.email_provider()?.send_message(&message).await {
                    record_email_send_failure(
                        ctx,
                        Some(user.id()),
                        email,
                        flow,
                        Some("email_provider"),
                        &error.to_string(),
                        message.metadata.clone(),
                    )
                    .await;
                    return Err(error);
                }
                tracing::info!(
                    recipient_email = %email,
                    flow = %flow,
                    user_id = %user.id(),
                    provider = "email_provider",
                    "verification email sent"
                );
            } else {
                let missing_provider = "No email provider configured";
                record_email_send_failure(
                    ctx,
                    Some(user.id()),
                    email,
                    flow,
                    None,
                    missing_provider,
                    None,
                )
                .await;
                tracing::error!(
                    recipient_email = %email,
                    flow = %flow,
                    error = missing_provider,
                    "Verification email was not sent"
                );
                return Err(AuthError::config(missing_provider));
            }
        }

        Ok(())
    }

    /// Send a verification email on sign-in for an unverified user.
    ///
    /// Callers (e.g. the sign-in plugin) should invoke this when
    /// [`EmailVerificationConfig::send_on_sign_in`] is `true` and the user is
    /// not yet verified.
    pub async fn send_verification_on_sign_in<DB: DatabaseAdapter>(
        &self,
        user: &DB::User,
        callback_url: Option<&str>,
        ctx: &AuthContext<DB>,
    ) -> AuthResult<()> {
        if !self.config.send_on_sign_in {
            return Ok(());
        }

        if user.email_verified() {
            return Ok(());
        }

        if let Some(email) = user.email() {
            self.send_verification_email_for_user(
                user,
                email,
                callback_url,
                "sign_in_verification",
                ctx,
            )
            .await?;
        }

        Ok(())
    }

    /// Check if `send_on_sign_in` is enabled.
    pub fn should_send_on_sign_in(&self) -> bool {
        self.config.send_on_sign_in
    }

    /// Check if email verification is required for signin
    pub fn is_verification_required(&self) -> bool {
        self.config.require_verification_for_signin
    }

    /// Check if user is verified or verification is not required
    pub async fn is_user_verified_or_not_required(&self, user: &impl AuthUser) -> bool {
        user.email_verified() || !self.config.require_verification_for_signin
    }
}

// ---------------------------------------------------------------------------
// Axum plugin
// ---------------------------------------------------------------------------

#[cfg(feature = "axum")]
mod axum_impl {
    use super::*;
    use std::sync::Arc;

    use athena_auth_core::{AuthError, AuthState, ValidatedJson};
    use axum::extract::{Extension, Query, State};
    use axum::response::IntoResponse;
    use axum::{Json, http::header};

    /// Plugin state stored as an axum extension.
    ///
    /// `EmailVerificationConfig` is NOT Clone (callback fields are `Arc<dyn ...>`
    /// without `Clone`), so we clone each field individually.
    struct PluginState {
        config: EmailVerificationConfig,
    }

    fn clone_config(c: &EmailVerificationConfig) -> EmailVerificationConfig {
        EmailVerificationConfig {
            verification_token_expiry: c.verification_token_expiry,
            send_email_notifications: c.send_email_notifications,
            require_verification_for_signin: c.require_verification_for_signin,
            auto_verify_new_users: c.auto_verify_new_users,
            send_on_sign_in: c.send_on_sign_in,
            auto_sign_in_after_verification: c.auto_sign_in_after_verification,
            send_verification_email: c.send_verification_email.clone(),
            before_email_verification: c.before_email_verification.clone(),
            after_email_verification: c.after_email_verification.clone(),
        }
    }

    async fn handle_send_verification_email<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        ValidatedJson(body): ValidatedJson<SendVerificationEmailRequest>,
    ) -> Result<Json<StatusResponse>, AuthError> {
        let ctx = state.to_context();
        let response = send_verification_email_core(&body, &ps.config, &ctx).await?;
        Ok(Json(response))
    }

    async fn handle_verify_email<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        Query(query): Query<VerifyEmailQuery>,
    ) -> Result<axum::response::Response, AuthError> {
        let ctx = state.to_context();
        // Note: axum Query extractor doesn't give us headers; pass None for
        // ip/user-agent (these are only used for session creation metadata).
        match verify_email_core(&query, &ps.config, None, None, &ctx).await? {
            VerifyEmailResult::AlreadyVerified(data) => Ok(Json(data).into_response()),
            VerifyEmailResult::Redirect { url, session_token } => {
                if let Some(token) = session_token {
                    let cookie = state.session_cookie(&token);
                    Ok((
                        [(header::SET_COOKIE, cookie)],
                        axum::response::Redirect::to(&url),
                    )
                        .into_response())
                } else {
                    Ok(axum::response::Redirect::to(&url).into_response())
                }
            }
            VerifyEmailResult::Json(data) => Ok(Json(data).into_response()),
            VerifyEmailResult::JsonWithSession {
                response,
                session_token,
            } => {
                let cookie = state.session_cookie(&session_token);
                Ok(([(header::SET_COOKIE, cookie)], Json(response)).into_response())
            }
        }
    }

    #[async_trait::async_trait]
    impl<DB: DatabaseAdapter> athena_auth_core::AxumPlugin<DB> for EmailVerificationPlugin {
        fn name(&self) -> &'static str {
            "email-verification"
        }

        fn router(&self) -> axum::Router<AuthState<DB>> {
            use axum::routing::{get, post};

            let plugin_state = Arc::new(PluginState {
                config: clone_config(&self.config),
            });

            axum::Router::new()
                .route(
                    "/send-verification-email",
                    post(handle_send_verification_email::<DB>),
                )
                .route("/verify-email", get(handle_verify_email::<DB>))
                .layer(Extension(plugin_state))
        }

        async fn on_user_created(
            &self,
            user: &DB::User,
            ctx: &athena_auth_core::AuthContext<DB>,
        ) -> athena_auth_core::AuthResult<()> {
            // Delegate to the AuthPlugin implementation logic
            if (self.config.send_email_notifications
                || self.config.send_verification_email.is_some())
                && !user.email_verified()
                && let Some(email) = user.email()
                && let Err(e) = self
                    .send_verification_email_for_user(
                        user,
                        email,
                        None,
                        "signup_auto_verification",
                        ctx,
                    )
                    .await
            {
                tracing::warn!(
                    email = %email,
                    error = %e,
                    "Failed to send verification email"
                );
            }
            Ok(())
        }
    }
}
