use std::sync::Arc;

use serde::Deserialize;

use athena_auth_core::error::{
    normalize_auth_error_response_trace_id, request_trace_id_from_auth_request,
};
use athena_auth_core::utils::cookie_utils::create_clear_session_cookie;
use athena_auth_core::{
    AccountOps, AuthConfig, AuthContext, AuthError, AuthPlugin, AuthRequest, AuthResponse,
    AuthResult, BeforeRequestAction, DatabaseAdapter, DatabaseHooks, EmailProvider,
    HealthCheckResponse, HttpMethod, OkResponse, OpenApiBuilder, OpenApiSpec, SessionManager,
    SessionOps, StatusMessageResponse, SuccessMessageResponse, UpdateUser, UpdateUserRequest,
    UserOps, VerificationOps, core_paths,
    entity::{AuthAccount, AuthSession, AuthUser, AuthVerification},
    extract_origin,
    middleware::{
        self, BodyLimitConfig, BodyLimitMiddleware, CorsConfig, CorsMiddleware, CsrfConfig,
        CsrfMiddleware, Middleware, RateLimitConfig, RateLimitMiddleware,
    },
    resolve_avatar_value,
};

#[derive(Debug)]
struct AuthFailureTraceContext<'a> {
    external_path: &'a str,
    origin: Option<&'a str>,
    referer: Option<&'a str>,
    host: Option<&'a str>,
    forwarded_host: Option<&'a str>,
    forwarded_proto: Option<&'a str>,
    query_present: bool,
    authorization_header_present: bool,
    cookie_header_present: bool,
    session_cookie_name: &'a str,
    session_cookie_present: bool,
    origin_trusted: Option<bool>,
    referer_origin: Option<String>,
    referer_origin_trusted: Option<bool>,
    base_url: &'a str,
    public_base_path: &'a str,
    cookie_domain: Option<String>,
    trusted_origins_count: usize,
}

fn auth_failure_trace_context<'a>(
    config: &'a AuthConfig,
    req: &'a AuthRequest,
) -> AuthFailureTraceContext<'a> {
    let origin = req.header_ignore_ascii_case("origin").map(String::as_str);
    let referer = req.header_ignore_ascii_case("referer").map(String::as_str);
    let referer_origin = referer.and_then(extract_origin);

    AuthFailureTraceContext {
        external_path: req.path(),
        origin,
        referer,
        host: req.header_ignore_ascii_case("host").map(String::as_str),
        forwarded_host: req
            .header_ignore_ascii_case("x-forwarded-host")
            .map(String::as_str),
        forwarded_proto: req
            .header_ignore_ascii_case("x-forwarded-proto")
            .map(String::as_str),
        query_present: !req.query.is_empty(),
        authorization_header_present: req.header_ignore_ascii_case("authorization").is_some(),
        cookie_header_present: req.header_ignore_ascii_case("cookie").is_some(),
        session_cookie_name: config.session.cookie_name.as_str(),
        session_cookie_present: req.header_ignore_ascii_case("cookie").is_some_and(
            |cookie_header| {
                cookie_header.split(';').map(str::trim).any(|entry| {
                    let Some((name, value)) = entry.split_once('=') else {
                        return false;
                    };
                    name == config.session.cookie_name && !value.is_empty()
                })
            },
        ),
        origin_trusted: origin.map(|value| config.is_origin_trusted(value)),
        referer_origin_trusted: referer_origin
            .as_deref()
            .map(|value| config.is_origin_trusted(value)),
        referer_origin,
        base_url: config.base_url.as_str(),
        public_base_path: config.effective_public_base_path(),
        cookie_domain: config.effective_session_cookie_domain(),
        trusted_origins_count: config.trusted_origins.len(),
    }
}

#[derive(Debug, Deserialize)]
struct ChangeEmailRequest {
    #[serde(rename = "newEmail")]
    new_email: String,
}

/// The main AthenaAuth instance, generic over the database adapter.
pub struct AthenaAuth<DB: DatabaseAdapter> {
    config: Arc<AuthConfig>,
    plugins: Vec<Box<dyn AuthPlugin<DB>>>,
    middlewares: Vec<Box<dyn Middleware>>,
    database: Arc<DB>,
    session_manager: SessionManager<DB>,
    context: AuthContext<DB>,
}

/// Initial builder for configuring athena-auth
///
/// Call `.database(adapter)` to obtain a [`TypedAuthBuilder`] that can
/// accept plugins and hooks.
pub struct AuthBuilder {
    config: AuthConfig,
    csrf_config: Option<CsrfConfig>,
    rate_limit_config: Option<RateLimitConfig>,
    cors_config: Option<CorsConfig>,
    body_limit_config: Option<BodyLimitConfig>,
    custom_middlewares: Vec<Box<dyn Middleware>>,
}

/// Typed builder returned by [`AuthBuilder::database`].
///
/// Accepts plugins, hooks, and middleware before calling `.build()`.
pub struct TypedAuthBuilder<DB: DatabaseAdapter> {
    config: AuthConfig,
    database: Arc<DB>,
    plugins: Vec<Box<dyn AuthPlugin<DB>>>,
    hooks: Vec<Arc<dyn DatabaseHooks<DB>>>,
    csrf_config: Option<CsrfConfig>,
    rate_limit_config: Option<RateLimitConfig>,
    cors_config: Option<CorsConfig>,
    body_limit_config: Option<BodyLimitConfig>,
    custom_middlewares: Vec<Box<dyn Middleware>>,
}

impl AuthBuilder {
    /// Create a new AthenaAuth builder.
    ///
    /// ## `AuthConfig`
    /// - `config` - The configuration for the AthenaAuth instance.
    /// - `csrf_config` - The configuration for the CSRF protection.
    /// - `rate_limit_config` - The configuration for the rate limiting.
    /// - `cors_config` - The configuration for the CORS.
    /// - `body_limit_config` - The configuration for the body size limit.
    /// - `custom_middlewares` - The custom middlewares to be used.
    ///
    /// ## Returns
    /// - `Self` - The AthenaAuth builder.
    ///
    pub fn new(config: AuthConfig) -> Self {
        Self {
            config,
            csrf_config: None,
            rate_limit_config: None,
            cors_config: None,
            body_limit_config: None,
            custom_middlewares: Vec::new(),
        }
    }

    /// Set the database adapter, returning a [`TypedAuthBuilder`].
    ///
    /// ## `DB`
    /// - `database` - The database adapter to be used.
    ///
    /// ## Returns
    /// - `TypedAuthBuilder<DB>` - The typed AthenaAuth builder.
    ///
    pub fn database<DB: DatabaseAdapter>(self, database: DB) -> TypedAuthBuilder<DB> {
        TypedAuthBuilder {
            config: self.config,
            database: Arc::new(database),
            plugins: Vec::new(),
            hooks: Vec::new(),
            csrf_config: self.csrf_config,
            rate_limit_config: self.rate_limit_config,
            cors_config: self.cors_config,
            body_limit_config: self.body_limit_config,
            custom_middlewares: self.custom_middlewares,
        }
    }

    /// Configure CSRF protection.
    pub fn csrf(mut self, config: CsrfConfig) -> Self {
        self.csrf_config = Some(config);
        self
    }

    /// Configure rate limiting.
    pub fn rate_limit(mut self, config: RateLimitConfig) -> Self {
        self.rate_limit_config = Some(config);
        self
    }

    /// Configure CORS.
    pub fn cors(mut self, config: CorsConfig) -> Self {
        self.cors_config = Some(config);
        self
    }

    /// Configure body size limit.
    pub fn body_limit(mut self, config: BodyLimitConfig) -> Self {
        self.body_limit_config = Some(config);
        self
    }

    /// Set the email provider.
    pub fn email_provider<E: EmailProvider + 'static>(mut self, provider: E) -> Self {
        let arc_provider: Arc<dyn EmailProvider> = Arc::new(provider);
        self.config.email_provider = Some(arc_provider);
        self
    }
}

impl<DB: DatabaseAdapter> TypedAuthBuilder<DB> {
    /// Add a plugin to the authentication system.
    pub fn plugin<P: AuthPlugin<DB> + 'static>(mut self, plugin: P) -> Self {
        let boxed_plugin: Box<dyn AuthPlugin<DB>> = Box::new(plugin);
        self.plugins.push(boxed_plugin);
        self
    }

    /// Add a database lifecycle hook.
    pub fn hook<H: DatabaseHooks<DB> + 'static>(mut self, hook: H) -> Self {
        let arc_hook: Arc<dyn DatabaseHooks<DB>> = Arc::new(hook);
        self.hooks.push(arc_hook);
        self
    }

    /// Configure CSRF protection.
    pub fn csrf(mut self, config: CsrfConfig) -> Self {
        self.csrf_config = Some(config);
        self
    }

    /// Configure rate limiting.
    pub fn rate_limit(mut self, config: RateLimitConfig) -> Self {
        self.rate_limit_config = Some(config);
        self
    }

    /// Configure CORS.
    pub fn cors(mut self, config: CorsConfig) -> Self {
        self.cors_config = Some(config);
        self
    }

    /// Configure body size limit.
    pub fn body_limit(mut self, config: BodyLimitConfig) -> Self {
        self.body_limit_config = Some(config);
        self
    }

    /// Set the email provider for sending emails.
    pub fn email_provider<E: EmailProvider + 'static>(mut self, provider: E) -> Self {
        let arc_provider: Arc<dyn EmailProvider> = Arc::new(provider);
        self.config.email_provider = Some(arc_provider);
        self
    }

    /// Add a custom middleware.
    pub fn middleware<M: Middleware + 'static>(mut self, mw: M) -> Self {
        let boxed_mw: Box<dyn Middleware> = Box::new(mw);
        self.custom_middlewares.push(boxed_mw);
        self
    }

    /// Build the AthenaAuth instance.
    pub async fn build(self) -> AuthResult<AthenaAuth<DB>> {
        // Validate configuration
        self.config.validate()?;

        let config: Arc<AuthConfig> = Arc::new(self.config);

        // If hooks are registered, the user should wrap the adapter themselves:
        //   let db = HookedDatabaseAdapter::new(Arc::new(my_db)).with_hook(hook);
        //   AthenaAuth::new(config).database(db).plugin(...).build().await
        if !self.hooks.is_empty() {
            return Err(AuthError::config(
                "Use HookedDatabaseAdapter directly: \
                 AthenaAuth::new(config).database(HookedDatabaseAdapter::new(Arc::new(db)).with_hook(h))",
            ));
        }

        let database: Arc<DB> = self.database;
        let session_user_cache = athena_auth_core::SessionUserCache::default();
        let session_manager: SessionManager<DB> = SessionManager::new_with_cache(
            config.clone(),
            database.clone(),
            session_user_cache.clone(),
        );
        let mut context: AuthContext<DB> = AuthContext::new(config.clone(), database.clone());
        context.session_user_cache = session_user_cache;

        for plugin in &self.plugins {
            plugin.on_init(&mut context).await?;
        }

        // Build middleware chain (order matters: body limit → rate limit → CSRF → CORS → custom)
        let mw_body: Box<dyn Middleware> = Box::new(BodyLimitMiddleware::new(
            self.body_limit_config.unwrap_or_default(),
        ));
        let mw_rate: Box<dyn Middleware> = Box::new(RateLimitMiddleware::new(
            self.rate_limit_config.unwrap_or_default(),
        ));
        let mw_csrf: Box<dyn Middleware> = Box::new(CsrfMiddleware::new(
            self.csrf_config.unwrap_or_default(),
            config.clone(),
        ));
        let mw_cors: Box<dyn Middleware> = Box::new(CorsMiddleware::new(
            self.cors_config.unwrap_or_default(),
            config.clone(),
        ));

        let mut middlewares: Vec<Box<dyn Middleware>> = vec![mw_body, mw_rate, mw_csrf, mw_cors];

        middlewares.extend(self.custom_middlewares);

        Ok(AthenaAuth {
            config,
            plugins: self.plugins,
            middlewares,
            database,
            session_manager,
            context,
        })
    }
}

impl<DB: DatabaseAdapter> AthenaAuth<DB> {
    /// Create a new AthenaAuth builder.
    #[allow(clippy::new_ret_no_self)]
    pub fn new(config: AuthConfig) -> AuthBuilder {
        AuthBuilder::new(config)
    }

    /// Handle an authentication request.
    ///
    /// Errors from plugins and core handlers are automatically converted
    /// into standardized JSON responses via [`AuthError::into_response`],
    /// producing `{ "message": "..." }` with the appropriate HTTP status code.
    pub async fn handle_request(&self, req: AuthRequest) -> AuthResult<AuthResponse> {
        // Ignore any caller-supplied virtual session value; only internal
        // before_request hooks may inject this during dispatch.
        let trace_path: String = req.path.clone();
        let trace_method: HttpMethod = req.method.clone();
        let trace_id: String = request_trace_id_from_auth_request(&req);
        let mut req: AuthRequest =
            AuthRequest::from_parts(req.method, req.path, req.headers, req.body, req.query);

        match self.handle_request_inner(&mut req, trace_id.as_str()).await {
            Ok(response) => {
                let response = normalize_auth_error_response_trace_id(response, trace_id.as_str());
                let status = response.status;
                if status >= 500 {
                    tracing::error!(
                        path = %trace_path,
                        ?trace_method,
                        trace_id = %trace_id,
                        status,
                        "auth response"
                    );
                } else if status >= 400 {
                    tracing::warn!(
                        path = %trace_path,
                        ?trace_method,
                        trace_id = %trace_id,
                        status,
                        "auth response"
                    );
                } else {
                    tracing::debug!(
                        path = %trace_path,
                        ?trace_method,
                        status,
                        "auth response"
                    );
                }
                // Run after-request middleware chain
                middleware::run_after(&self.middlewares, &req, response).await
            }
            Err(err) => {
                let status = err.status_code();
                let failure = auth_failure_trace_context(&self.config, &req);
                if status >= 500 {
                    tracing::error!(
                        path = %trace_path,
                        ?trace_method,
                        trace_id = %trace_id,
                        error = %err,
                        status,
                        external_path = %failure.external_path,
                        host = ?failure.host,
                        forwarded_host = ?failure.forwarded_host,
                        forwarded_proto = ?failure.forwarded_proto,
                        origin = ?failure.origin,
                        origin_trusted = ?failure.origin_trusted,
                        referer = ?failure.referer,
                        referer_origin = ?failure.referer_origin,
                        referer_origin_trusted = ?failure.referer_origin_trusted,
                        query_present = failure.query_present,
                        authorization_header_present = failure.authorization_header_present,
                        cookie_header_present = failure.cookie_header_present,
                        session_cookie_name = %failure.session_cookie_name,
                        session_cookie_present = failure.session_cookie_present,
                        base_url = %failure.base_url,
                        public_base_path = %failure.public_base_path,
                        cookie_domain = ?failure.cookie_domain,
                        trusted_origins_count = failure.trusted_origins_count,
                        "auth request failed"
                    );
                } else {
                    tracing::warn!(
                        path = %trace_path,
                        ?trace_method,
                        trace_id = %trace_id,
                        error = %err,
                        status,
                        external_path = %failure.external_path,
                        host = ?failure.host,
                        forwarded_host = ?failure.forwarded_host,
                        forwarded_proto = ?failure.forwarded_proto,
                        origin = ?failure.origin,
                        origin_trusted = ?failure.origin_trusted,
                        referer = ?failure.referer,
                        referer_origin = ?failure.referer_origin,
                        referer_origin_trusted = ?failure.referer_origin_trusted,
                        query_present = failure.query_present,
                        authorization_header_present = failure.authorization_header_present,
                        cookie_header_present = failure.cookie_header_present,
                        session_cookie_name = %failure.session_cookie_name,
                        session_cookie_present = failure.session_cookie_present,
                        base_url = %failure.base_url,
                        public_base_path = %failure.public_base_path,
                        cookie_domain = ?failure.cookie_domain,
                        trusted_origins_count = failure.trusted_origins_count,
                        "auth request failed"
                    );
                }
                // Convert error to standardized response, then run after-middleware
                let should_clear_session_cookie: bool =
                    self.should_clear_session_cookie_on_auth_failure(&req, &err);
                let mut response: AuthResponse =
                    err.into_response_with_trace_id(env!("CARGO_PKG_VERSION"), trace_id);
                if should_clear_session_cookie {
                    response = response
                        .with_header("Set-Cookie", create_clear_session_cookie(&self.config));
                }
                middleware::run_after(&self.middlewares, &req, response).await
            }
        }
    }

    /// Inner request handler that may return errors.
    async fn handle_request_inner(
        &self,
        req: &mut AuthRequest,
        trace_id: &str,
    ) -> AuthResult<AuthResponse> {
        // Run before-request middleware chain
        if let Some(response) = middleware::run_before(&self.middlewares, req).await? {
            return Ok(response);
        }

        // Strip base_path prefix from the request path for internal routing.
        // This happens BEFORE plugin hooks so that `before_request` sees the
        // same normalised path that `on_request` / core handlers use.
        // External callers send e.g. "/api/auth/sign-in/email"; internally
        // handlers match against "/sign-in/email".
        let base_path: &String = &self.config.base_path;
        let stripped_path = if !base_path.is_empty() && base_path != "/" {
            req.path().strip_prefix(base_path).unwrap_or(req.path())
        } else {
            req.path()
        };

        // Build a request with the stripped path for all subsequent dispatch
        let mut internal_req: AuthRequest = if stripped_path != req.path() {
            let mut r = req.clone();
            r.path = stripped_path.to_string();
            r
        } else {
            req.clone()
        };

        if self.config.is_path_disabled(internal_req.path()) {
            return Err(AuthError::not_found("This endpoint has been disabled"));
        }

        // Run plugin before_request hooks (e.g. API-key → session emulation)
        // Plugins now see the normalised (base_path-stripped) path.
        for plugin in &self.plugins {
            if let Some(action) = plugin.before_request(&internal_req, &self.context).await? {
                match action {
                    BeforeRequestAction::Respond(response) => {
                        return Ok(response);
                    }
                    BeforeRequestAction::InjectSession {
                        user_id,
                        session_token: _,
                    } => {
                        // Set the virtual user id on the request so that
                        // `extract_current_user` can resolve the user without
                        // creating a real database session.  This mirrors the
                        // TypeScript `ctx.context.session` virtual-session
                        // approach — no DB writes on every API-key request.
                        internal_req.set_virtual_user_id(user_id);
                    }
                }
            }
        }

        // If a request matches both a core route and a plugin route, prefer
        // the plugin handler first so plugin-configured workflows (such as
        // verification-gated change-email/delete-user) are honored.
        if self.is_core_plugin_collision(&internal_req)
            && let Some(response) = self.handle_plugin_request(&internal_req).await?
        {
            return Ok(response);
        }

        // Handle core endpoints first
        if let Some(response) = self.handle_core_request(&internal_req).await? {
            return Ok(response);
        }

        // Try each plugin until one handles the request
        if let Some(response) = self.handle_plugin_request(&internal_req).await? {
            return Ok(response);
        }

        let core_route_match = self.is_core_route(&internal_req);
        let plugin_route_match = self.request_matches_plugin_route(&internal_req);
        tracing::warn!(
            trace_id = %trace_id,
            external_path = %req.path(),
            internal_path = %internal_req.path(),
            method = ?internal_req.method(),
            base_path = %base_path,
            core_route_match,
            plugin_route_match,
            "no auth handler matched"
        );
        Err(AuthError::not_found("No handler found for this request"))
    }

    fn is_core_plugin_collision(&self, req: &AuthRequest) -> bool {
        self.is_core_route(req) && self.request_matches_plugin_route(req)
    }

    fn is_core_route(&self, req: &AuthRequest) -> bool {
        matches!(
            (req.method(), req.path()),
            (HttpMethod::Get, core_paths::OK)
                | (HttpMethod::Get, core_paths::ERROR)
                | (HttpMethod::Get, core_paths::HEALTH)
                | (HttpMethod::Get, core_paths::OPENAPI_SPEC)
                | (HttpMethod::Post, core_paths::UPDATE_USER)
                | (
                    HttpMethod::Post | HttpMethod::Delete,
                    core_paths::DELETE_USER
                )
                | (HttpMethod::Post, core_paths::CHANGE_EMAIL)
                | (HttpMethod::Get, core_paths::DELETE_USER_CALLBACK)
        )
    }

    fn request_matches_plugin_route(&self, req: &AuthRequest) -> bool {
        self.plugins.iter().any(|plugin| {
            plugin
                .routes()
                .iter()
                .any(|route| route.method == *req.method() && route.path == req.path())
        })
    }

    async fn handle_plugin_request(&self, req: &AuthRequest) -> AuthResult<Option<AuthResponse>> {
        for plugin in &self.plugins {
            if let Some(response) = plugin.on_request(req, &self.context).await? {
                return Ok(Some(response));
            }
        }
        Ok(None)
    }

    /// Get the configuration.
    pub fn config(&self) -> &AuthConfig {
        &self.config
    }

    /// Get the database adapter.
    pub fn database(&self) -> &Arc<DB> {
        &self.database
    }

    /// Get the session manager.
    pub fn session_manager(&self) -> &SessionManager<DB> {
        &self.session_manager
    }

    /// Get all routes from plugins.
    pub fn routes(&self) -> Vec<(String, &dyn AuthPlugin<DB>)> {
        let mut routes: Vec<(String, &dyn AuthPlugin<DB>)> = Vec::new();
        for plugin in &self.plugins {
            for route in plugin.routes() {
                routes.push((route.path, plugin.as_ref()));
            }
        }
        routes
    }

    /// Get all plugins.
    pub fn plugins(&self) -> &[Box<dyn AuthPlugin<DB>>] {
        &self.plugins
    }

    /// Get plugin by name.
    pub fn get_plugin(&self, name: &str) -> Option<&dyn AuthPlugin<DB>> {
        self.plugins
            .iter()
            .find(|p| p.name() == name)
            .map(|p| p.as_ref())
    }

    /// List all plugin names.
    pub fn plugin_names(&self) -> Vec<&'static str> {
        self.plugins.iter().map(|p| p.name()).collect()
    }

    fn should_clear_session_cookie_on_auth_failure(
        &self,
        req: &AuthRequest,
        err: &AuthError,
    ) -> bool {
        matches!(err, AuthError::Unauthenticated | AuthError::SessionNotFound)
            && self.request_has_session_cookie(req)
    }

    fn request_has_session_cookie(&self, req: &AuthRequest) -> bool {
        let Some(cookie_header) = req.headers.get("cookie") else {
            return false;
        };
        let cookie_name: &str = self.config.session.cookie_name.as_str();
        cookie_header.split(';').map(str::trim).any(|entry| {
            let Some((name, value)) = entry.split_once('=') else {
                return false;
            };
            name == cookie_name && !value.is_empty()
        })
    }

    /// Generate the OpenAPI spec for all registered routes.
    pub fn openapi_spec(&self) -> OpenApiSpec {
        let mut builder: OpenApiBuilder =
            OpenApiBuilder::new("Athena Auth", env!("CARGO_PKG_VERSION"))
                .description("Authentication API")
                .core_routes();

        for plugin in &self.plugins {
            builder = builder.plugin(plugin.as_ref());
        }

        builder.build()
    }

    /// Handle core authentication requests.
    async fn handle_core_request(&self, req: &AuthRequest) -> AuthResult<Option<AuthResponse>> {
        match (req.method(), req.path()) {
            (HttpMethod::Get, core_paths::OK) => {
                Ok(Some(AuthResponse::json(200, &OkResponse { ok: true })?))
            }
            (HttpMethod::Get, core_paths::ERROR) => {
                Ok(Some(AuthResponse::json(200, &OkResponse { ok: false })?))
            }
            (HttpMethod::Get, core_paths::HEALTH) => Ok(Some(AuthResponse::json(
                200,
                &HealthCheckResponse {
                    status: "ok",
                    service: "athena-auth",
                },
            )?)),
            (HttpMethod::Get, core_paths::OPENAPI_SPEC) => {
                let spec: OpenApiSpec = self.openapi_spec();
                Ok(Some(AuthResponse::json(200, &spec)?))
            }
            (HttpMethod::Post, core_paths::UPDATE_USER) => {
                Ok(Some(self.handle_update_user(req).await?))
            }
            (HttpMethod::Post | HttpMethod::Delete, core_paths::DELETE_USER) => {
                Ok(Some(self.handle_delete_user(req).await?))
            }
            (HttpMethod::Post, core_paths::CHANGE_EMAIL) => {
                Ok(Some(self.handle_change_email(req).await?))
            }
            (HttpMethod::Get, core_paths::DELETE_USER_CALLBACK) => {
                Ok(Some(self.handle_delete_user_callback(req).await?))
            }
            _ => Ok(None),
        }
    }

    /// Handle user profile update.
    async fn handle_update_user(&self, req: &AuthRequest) -> AuthResult<AuthResponse> {
        let current_user: <DB as UserOps>::User = self.extract_current_user(req).await?;

        let update_req: UpdateUserRequest = req
            .body_as_json()
            .map_err(|e| AuthError::bad_request(format!("Invalid JSON: {}", e)))?;
        let resolved_image = match update_req.image.as_deref() {
            Some(image) => Some(
                resolve_avatar_value(
                    &self.config,
                    image,
                    athena_auth_core::AvatarTemplateContext::for_user(current_user.id()),
                )
                .await?,
            ),
            None => None,
        };

        let update_user: UpdateUser = UpdateUser {
            email: update_req.email,
            name: update_req.name,
            image: resolved_image,
            email_verified: None,
            username: update_req.username,
            display_username: update_req.display_username,
            role: update_req.role,
            banned: None,
            ban_reason: None,
            ban_expires: None,
            two_factor_enabled: None,
            metadata: update_req.metadata,
            last_sign_in_at: None,
        };

        let updated_user = self
            .database
            .update_user(current_user.id(), update_user)
            .await?;
        if let Err(cache_error) = self
            .context
            .cache_session_user(updated_user.id(), &updated_user)
        {
            tracing::warn!(
                user_id = %updated_user.id(),
                error = %cache_error,
                "failed to refresh session user cache after update-user"
            );
        }

        Ok(AuthResponse::json(
            200,
            &athena_auth_core::StatusResponse { status: true },
        )?)
    }

    /// Handle user deletion.
    async fn handle_delete_user(&self, req: &AuthRequest) -> AuthResult<AuthResponse> {
        let current_user: <DB as UserOps>::User = self.extract_current_user(req).await?;

        self.database
            .delete_user_sessions(current_user.id())
            .await?;
        self.database.delete_user(current_user.id()).await?;
        self.context
            .invalidate_session_user_cache(current_user.id());

        let response: SuccessMessageResponse = SuccessMessageResponse {
            success: true,
            message: "User account successfully deleted".to_string(),
        };

        Ok(AuthResponse::json(200, &response)?)
    }

    /// Handle email change.
    async fn handle_change_email(&self, req: &AuthRequest) -> AuthResult<AuthResponse> {
        let current_user: <DB as UserOps>::User = self.extract_current_user(req).await?;

        let change_req: ChangeEmailRequest = req
            .body_as_json()
            .map_err(|e| AuthError::bad_request(format!("Invalid JSON: {}", e)))?;

        if !change_req.new_email.contains('@') || change_req.new_email.is_empty() {
            return Err(AuthError::bad_request("Invalid email address"));
        }

        if self
            .database
            .get_user_by_email(&change_req.new_email)
            .await?
            .is_some()
        {
            return Err(AuthError::conflict("A user with this email already exists"));
        }

        let update_user: UpdateUser = UpdateUser {
            email: Some(change_req.new_email),
            name: None,
            image: None,
            email_verified: Some(false),
            username: None,
            display_username: None,
            role: None,
            banned: None,
            ban_reason: None,
            ban_expires: None,
            two_factor_enabled: None,
            metadata: None,
            last_sign_in_at: None,
        };

        let updated_user = self
            .database
            .update_user(current_user.id(), update_user)
            .await?;
        if let Err(cache_error) = self
            .context
            .cache_session_user(updated_user.id(), &updated_user)
        {
            tracing::warn!(
                user_id = %updated_user.id(),
                error = %cache_error,
                "failed to refresh session user cache after change-email"
            );
        }

        Ok(AuthResponse::json(
            200,
            &StatusMessageResponse {
                status: true,
                message: "Email updated".to_string(),
            },
        )?)
    }

    /// Handle delete-user callback (token-based deletion confirmation).
    async fn handle_delete_user_callback(&self, req: &AuthRequest) -> AuthResult<AuthResponse> {
        let token: &String = req
            .query
            .get("token")
            .ok_or_else(|| AuthError::bad_request("Deletion token is required"))?;

        let verification: <DB as VerificationOps>::Verification = self
            .database
            .get_verification_by_value(token)
            .await?
            .ok_or_else(|| AuthError::bad_request("Invalid or expired deletion token"))?;

        let user_id: &str = verification.identifier();

        self.database.delete_user_sessions(user_id).await?;

        let accounts: Vec<<DB as AccountOps>::Account> =
            self.database.get_user_accounts(user_id).await?;
        for account in accounts {
            self.database.delete_account(account.id()).await?;
        }

        self.database.delete_user(user_id).await?;
        self.database.delete_verification(verification.id()).await?;
        self.context.invalidate_session_user_cache(user_id);

        let response: SuccessMessageResponse = SuccessMessageResponse {
            success: true,
            message: "User account successfully deleted".to_string(),
        };

        Ok(AuthResponse::json(200, &response)?)
    }

    /// Extract current user from request (validates session).
    ///
    /// If a virtual session was injected by a `before_request` hook (e.g.
    /// API-key session emulation), the user is resolved directly by ID
    /// **without** a database session lookup — matching the TypeScript
    /// `ctx.context.session` virtual-session behaviour.
    async fn extract_current_user(&self, req: &AuthRequest) -> AuthResult<DB::User> {
        // Fast path: virtual session injected by before_request hook
        if let Some(uid) = req.virtual_user_id() {
            return self
                .database
                .get_user_by_id(uid)
                .await?
                .ok_or(AuthError::UserNotFound);
        }

        let token: String = self
            .session_manager
            .extract_session_token(req)
            .ok_or(AuthError::Unauthenticated)?;

        let session: <DB as SessionOps>::Session = self
            .session_manager
            .get_session(&token)
            .await?
            .ok_or(AuthError::SessionNotFound)?;

        let user: <DB as UserOps>::User = self
            .database
            .get_user_by_id(session.user_id())
            .await?
            .ok_or(AuthError::UserNotFound)?;

        Ok(user)
    }
}
