use axum::{extract::State, http::StatusCode, response::IntoResponse, Extension, Json};
use mongodb::bson::oid::ObjectId;
use serde::{Deserialize, Serialize};
use validator::Validate;

use crate::{
    auth::jwt::Claims, config::AppState, middleware::ClientIp, models::audit_log::AuditEventType,
    models::user::User,
};

#[derive(Debug, Serialize)]
pub struct UserProfileResponse {
    pub id: String,
    pub email: String,
    pub username: String,
    pub created_at: i64,
    pub last_active: i64,
    pub email_verified: bool,
}

impl TryFrom<User> for UserProfileResponse {
    type Error = anyhow::Error;

    fn try_from(user: User) -> Result<Self, Self::Error> {
        Ok(Self {
            id: user.id.map(|id| id.to_string()).unwrap_or_default(),
            email: user.email,
            username: user.username,
            created_at: user.created_at.timestamp_millis(),
            last_active: user.last_active.timestamp_millis(),
            email_verified: user.email_verified,
        })
    }
}

#[derive(Debug, Deserialize, Validate)]
pub struct UpdateProfileRequest {
    #[validate(length(min = 1))]
    pub username: Option<String>,
}

pub async fn get_profile(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
) -> impl IntoResponse {
    let user_id = match ObjectId::parse_str(&claims.sub) {
        Ok(oid) => oid,
        Err(_) => {
            return (
                StatusCode::UNAUTHORIZED,
                Json(serde_json::json!({ "error": "invalid token" })),
            )
                .into_response()
        }
    };

    match state.db.find_user_by_id(&user_id).await {
        Ok(Some(user)) => {
            let response: UserProfileResponse = user.try_into().unwrap();
            (StatusCode::OK, Json(response)).into_response()
        }
        Ok(None) => (
            StatusCode::NOT_FOUND,
            Json(serde_json::json!({
                "error": "user not found"
            })),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to get user profile: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "failed to get profile"
                })),
            )
                .into_response()
        }
    }
}

pub async fn update_profile(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
    Json(req): Json<UpdateProfileRequest>,
) -> impl IntoResponse {
    if let Err(errors) = req.validate() {
        return (
            StatusCode::BAD_REQUEST,
            Json(serde_json::json!({
                "error": "validation failed",
                "details": errors.to_string()
            })),
        )
            .into_response();
    }

    let user_id = match ObjectId::parse_str(&claims.sub) {
        Ok(oid) => oid,
        Err(_) => {
            return (
                StatusCode::UNAUTHORIZED,
                Json(serde_json::json!({ "error": "invalid token" })),
            )
                .into_response()
        }
    };

    let mut user = match state.db.find_user_by_id(&user_id).await {
        Ok(Some(u)) => u,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(serde_json::json!({
                    "error": "user not found"
                })),
            )
                .into_response()
        }
        Err(e) => {
            tracing::error!("Failed to get user: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "database error"
                })),
            )
                .into_response();
        }
    };

    if let Some(username) = req.username {
        user.username = username;
    }

    match state.db.update_user(&user).await {
        Ok(_) => {
            let response: UserProfileResponse = user.try_into().unwrap();
            (StatusCode::OK, Json(response)).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to update user: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "failed to update profile"
                })),
            )
                .into_response()
        }
    }
}

pub async fn delete_account(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
) -> impl IntoResponse {
    let user_id = match ObjectId::parse_str(&claims.sub) {
        Ok(oid) => oid,
        Err(_) => {
            return (
                StatusCode::UNAUTHORIZED,
                Json(serde_json::json!({ "error": "invalid token" })),
            )
                .into_response()
        }
    };

    match state.db.delete_user(&user_id).await {
        Ok(_) => (StatusCode::NO_CONTENT, ()).into_response(),
        Err(e) => {
            tracing::error!("Failed to delete user: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "failed to delete account"
                })),
            )
                .into_response()
        }
    }
}

#[derive(Debug, Deserialize, Validate)]
pub struct ChangePasswordRequest {
    #[validate(length(min = 8))]
    pub current_password: String,
    #[validate(length(min = 8))]
    pub new_password: String,
}

pub async fn change_password(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
    Extension(client_ip): Extension<ClientIp>,
    Json(req): Json<ChangePasswordRequest>,
) -> impl IntoResponse {
    if let Err(errors) = req.validate() {
        return (
            StatusCode::BAD_REQUEST,
            Json(serde_json::json!({
                "error": "validation failed",
                "details": errors.to_string()
            })),
        )
            .into_response();
    }

    // The middleware already validated this against a real user, but guard
    // against a malformed subject anyway rather than panicking.
    let user_id = match ObjectId::parse_str(&claims.sub) {
        Ok(oid) => oid,
        Err(_) => {
            return (
                StatusCode::UNAUTHORIZED,
                Json(serde_json::json!({ "error": "invalid token" })),
            )
                .into_response()
        }
    };

    let mut user = match state.db.find_user_by_id(&user_id).await {
        Ok(Some(u)) => u,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(serde_json::json!({
                    "error": "user not found"
                })),
            )
                .into_response()
        }
        Err(e) => {
            tracing::error!("Failed to get user: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "database error"
                })),
            )
                .into_response();
        }
    };

    // Verify current password
    match user.verify_password(&req.current_password) {
        Ok(true) => {}
        Ok(false) => {
            return (
                StatusCode::UNAUTHORIZED,
                Json(serde_json::json!({
                    "error": "current password is incorrect"
                })),
            )
                .into_response()
        }
        Err(e) => {
            tracing::error!("Failed to verify password: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "failed to verify password"
                })),
            )
                .into_response();
        }
    }

    // Update password (this also bumps token_version in memory)
    match user.update_password(req.new_password) {
        Ok(_) => {}
        Err(e) => {
            tracing::error!("Failed to hash new password: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "failed to update password"
                })),
            )
                .into_response();
        }
    }

    match state.db.update_user(&user).await {
        Ok(_) => {
            // Invalidate all sessions: the bumped token_version rejects existing
            // access tokens, and we revoke every refresh token immediately so
            // they can't mint new access tokens.
            if let Some(ref repo) = state.refresh_token_repo {
                if let Err(e) = repo.revoke_all_by_user(&user_id.to_string()).await {
                    tracing::warn!("Failed to revoke refresh tokens on password change: {}", e);
                }
            }
            state.token_version_cache.invalidate(&user_id).await;

            // Audit the credential change.
            if let Some(ref audit) = state.audit_logger {
                let _ = audit
                    .log_event(
                        AuditEventType::PasswordChanged,
                        Some(user_id),
                        Some(user.email.clone()),
                        client_ip.as_str().to_string(),
                        None,
                        None,
                    )
                    .await;
            }

            (StatusCode::NO_CONTENT, ()).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to update user: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "failed to update password"
                })),
            )
                .into_response()
        }
    }
}

#[derive(Debug, Serialize)]
pub struct UserSettingsResponse {
    pub recovery_enabled: bool,
    pub email_verified: bool,
}

impl From<User> for UserSettingsResponse {
    fn from(user: User) -> Self {
        Self {
            recovery_enabled: user.recovery_enabled,
            email_verified: user.email_verified,
        }
    }
}

pub async fn get_settings(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
) -> impl IntoResponse {
    let user_id = match ObjectId::parse_str(&claims.sub) {
        Ok(oid) => oid,
        Err(_) => {
            return (
                StatusCode::UNAUTHORIZED,
                Json(serde_json::json!({ "error": "invalid token" })),
            )
                .into_response()
        }
    };

    match state.db.find_user_by_id(&user_id).await {
        Ok(Some(user)) => {
            let response: UserSettingsResponse = user.into();
            (StatusCode::OK, Json(response)).into_response()
        }
        Ok(None) => (
            StatusCode::NOT_FOUND,
            Json(serde_json::json!({
                "error": "user not found"
            })),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to get user: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "failed to get settings"
                })),
            )
                .into_response()
        }
    }
}

#[derive(Debug, Deserialize, Validate)]
pub struct UpdateSettingsRequest {
    pub recovery_enabled: Option<bool>,
}

pub async fn update_settings(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
    Json(req): Json<UpdateSettingsRequest>,
) -> impl IntoResponse {
    let user_id = match ObjectId::parse_str(&claims.sub) {
        Ok(oid) => oid,
        Err(_) => {
            return (
                StatusCode::UNAUTHORIZED,
                Json(serde_json::json!({ "error": "invalid token" })),
            )
                .into_response()
        }
    };

    let mut user = match state.db.find_user_by_id(&user_id).await {
        Ok(Some(u)) => u,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(serde_json::json!({
                    "error": "user not found"
                })),
            )
                .into_response()
        }
        Err(e) => {
            tracing::error!("Failed to get user: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "database error"
                })),
            )
                .into_response();
        }
    };

    if let Some(recovery_enabled) = req.recovery_enabled {
        if !recovery_enabled {
            user.remove_recovery_phrase();
        }
        // Note: Enabling recovery requires a separate endpoint to set the phrase
    }

    match state.db.update_user(&user).await {
        Ok(_) => {
            let response: UserSettingsResponse = user.into();
            (StatusCode::OK, Json(response)).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to update user: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "failed to update settings"
                })),
            )
                .into_response()
        }
    }
}
