use std::sync::Arc;

use axum::{
    extract::{Request, State},
    http::{header, HeaderMap, StatusCode},
    middleware::Next,
    response::{IntoResponse, Response},
};
use rustelo_core_lib::auth::{AuthError, Permission, Role, User, UserProfile};
use tower_cookies::Cookies;
use uuid::Uuid;

use super::{
    jwt::JwtService,
    repository::{AuthRepository, AuthRepositoryTrait},
};

/// Authentication context that gets added to request extensions
#[derive(Debug, Clone)]
pub struct AuthContext {
    pub user: Option<User>,
    #[allow(dead_code)]
    pub session_id: Option<String>,
}

// TODO: Implement FromRequestParts for AuthContext
// For now, we'll handle extraction manually in handlers

impl AuthContext {
    pub fn new() -> Self {
        Self {
            user: None,
            session_id: None,
        }
    }

    pub fn with_user(user: User) -> Self {
        Self {
            user: Some(user),
            session_id: None,
        }
    }

    pub fn with_session(user: User, session_id: String) -> Self {
        Self {
            user: Some(user),
            session_id: Some(session_id),
        }
    }

    pub fn is_authenticated(&self) -> bool {
        self.user.is_some()
    }

    #[allow(dead_code)]
    pub fn has_permission(&self, permission: &Permission) -> bool {
        self.user
            .as_ref()
            .map_or(false, |user| user.has_permission(permission))
    }

    #[allow(dead_code)]
    pub fn has_role(&self, role: &Role) -> bool {
        self.user.as_ref().map_or(false, |user| user.has_role(role))
    }

    #[allow(dead_code)]
    pub fn is_admin(&self) -> bool {
        self.user.as_ref().map_or(false, |user| user.is_admin())
    }

    #[allow(dead_code)]
    pub fn user_id(&self) -> Option<Uuid> {
        self.user.as_ref().map(|user| user.id)
    }
}

/// Authentication middleware that extracts user from JWT token or session
pub async fn auth_middleware(
    State((jwt_service, auth_repo)): State<(Arc<JwtService>, Arc<AuthRepository>)>,
    cookies: Cookies,
    mut request: Request,
    next: Next,
) -> Response {
    let headers = request.headers().clone();
    let auth_context = extract_auth_context(&jwt_service, &auth_repo, &cookies, &headers).await;

    // Add auth context to request extensions
    request.extensions_mut().insert(auth_context);

    next.run(request).await
}

/// Extract authentication context from request
async fn extract_auth_context(
    jwt_service: &JwtService,
    auth_repo: &AuthRepository,
    cookies: &Cookies,
    headers: &HeaderMap,
) -> AuthContext {
    // 1. JWT in Authorization: Bearer <token> header (API / programmatic clients)
    if let Some(user) = extract_user_from_jwt(jwt_service, headers).await {
        return AuthContext::with_user(user);
    }

    // 2. JWT stored in the session_token cookie (OTP / passwordless login path).
    //    OTP auth issues a signed JWT and stores it in this HttpOnly cookie; it
    //    does NOT create a DB session row, so we verify the signature directly.
    if let Some(user) = extract_user_from_cookie_jwt(jwt_service, cookies).await {
        return AuthContext::with_user(user);
    }

    // 3. Traditional session cookie → DB session lookup
    if let Some((user, session_id)) = extract_user_from_session(auth_repo, cookies).await {
        return AuthContext::with_session(user, session_id);
    }

    AuthContext::new()
}

/// Extract and verify a JWT stored in the `session_token` cookie.
async fn extract_user_from_cookie_jwt(jwt_service: &JwtService, cookies: &Cookies) -> Option<User> {
    let token = cookies.get("session_token")?.value().to_string();
    extract_user_from_jwt(jwt_service, &{
        // Re-use extract_user_from_jwt by synthesising an Authorization header value.
        let mut map = HeaderMap::new();
        if let Ok(val) = axum::http::HeaderValue::from_str(&format!("Bearer {}", token)) {
            map.insert(header::AUTHORIZATION, val);
        }
        map
    })
    .await
}

/// Extract user from JWT token in Authorization header
async fn extract_user_from_jwt(jwt_service: &JwtService, headers: &HeaderMap) -> Option<User> {
    let auth_header = headers.get(header::AUTHORIZATION)?.to_str().ok()?;
    let token = JwtService::extract_bearer_token(auth_header)?;

    match jwt_service.verify_access_token(token) {
        Ok(claims) => {
            let user_id = Uuid::parse_str(&claims.sub).ok()?;
            Some(User {
                id: user_id,
                email: claims.email,
                username: "".to_string(), // JWT doesn't include username
                display_name: None,
                avatar_url: None,
                roles: claims.roles,
                is_active: true,
                email_verified: true,
                created_at: chrono::Utc::now(),
                updated_at: chrono::Utc::now(),
                last_login: None,
                two_factor_enabled: false,
                profile: UserProfile::default(),
            })
        }
        Err(_) => None,
    }
}

/// Extract user from session cookie
async fn extract_user_from_session(
    auth_repo: &AuthRepository,
    cookies: &Cookies,
) -> Option<(User, String)> {
    let session_cookie = cookies.get("session_token")?;
    let session_id = session_cookie.value();

    let session = auth_repo.find_session(session_id).await.ok()??;
    let user = auth_repo.find_user_by_id(&session.user_id).await.ok()??;

    // Update session last accessed
    let _ = auth_repo.update_session_accessed(session_id).await;

    Some((user.into(), session_id.to_string()))
}

/// Middleware that requires authentication
#[allow(dead_code)]
pub async fn require_auth(request: Request, next: Next) -> Result<Response, StatusCode> {
    let auth_context = request
        .extensions()
        .get::<AuthContext>()
        .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;

    if !auth_context.is_authenticated() {
        return Err(StatusCode::UNAUTHORIZED);
    }

    Ok(next.run(request).await)
}

/// Middleware that requires specific permission
#[allow(dead_code)]
pub fn require_permission(
    permission: Permission,
) -> impl Fn(
    Request,
    Next,
) -> std::pin::Pin<
    Box<dyn std::future::Future<Output = Result<Response, StatusCode>> + Send>,
> + Clone {
    move |request: Request, next: Next| {
        let permission = permission.clone();
        Box::pin(async move {
            let auth_context = request
                .extensions()
                .get::<AuthContext>()
                .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;

            if !auth_context.has_permission(&permission) {
                return Err(StatusCode::FORBIDDEN);
            }

            Ok(next.run(request).await)
        })
    }
}

/// Middleware that requires specific role
#[allow(dead_code)]
pub fn require_role(
    role: Role,
) -> impl Fn(
    Request,
    Next,
) -> std::pin::Pin<
    Box<dyn std::future::Future<Output = Result<Response, StatusCode>> + Send>,
> + Clone {
    move |request: Request, next: Next| {
        let role = role.clone();
        Box::pin(async move {
            let auth_context = request
                .extensions()
                .get::<AuthContext>()
                .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;

            if !auth_context.has_role(&role) {
                return Err(StatusCode::FORBIDDEN);
            }

            Ok(next.run(request).await)
        })
    }
}

/// Middleware that requires admin role
#[allow(dead_code)]
pub async fn require_admin(request: Request, next: Next) -> Result<Response, StatusCode> {
    let auth_context = request
        .extensions()
        .get::<AuthContext>()
        .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;

    if !auth_context.is_admin() {
        return Err(StatusCode::FORBIDDEN);
    }

    Ok(next.run(request).await)
}

/// Middleware that requires moderator or admin role
#[allow(dead_code)]
pub async fn require_moderator(request: Request, next: Next) -> Result<Response, StatusCode> {
    let auth_context = request
        .extensions()
        .get::<AuthContext>()
        .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;

    if !auth_context.has_role(&Role::Admin) && !auth_context.has_role(&Role::Moderator) {
        return Err(StatusCode::FORBIDDEN);
    }

    Ok(next.run(request).await)
}

/// Extract authentication context from request extensions
#[allow(dead_code)]
pub fn extract_auth_context_from_request(request: &Request) -> Result<&AuthContext, AuthError> {
    request
        .extensions()
        .get::<AuthContext>()
        .ok_or(AuthError::InternalError)
}

/// Extract user from request extensions
#[allow(dead_code)]
pub fn extract_user_from_request(request: &Request) -> Result<&User, AuthError> {
    let auth_context = extract_auth_context_from_request(request)?;
    auth_context.user.as_ref().ok_or(AuthError::InvalidToken)
}

/// Extract optional user from request extensions
#[allow(dead_code)]
pub fn extract_optional_user_from_request(request: &Request) -> Option<&User> {
    let auth_context = extract_auth_context_from_request(request).ok()?;
    auth_context.user.as_ref()
}

/// Custom error response for authentication failures
/// This is implemented in the shared crate to avoid orphan rule issues
pub fn auth_error_response(error: AuthError) -> Response {
    let (status, message) = match error {
        AuthError::InvalidCredentials => (StatusCode::UNAUTHORIZED, "Invalid credentials"),
        AuthError::UserNotFound => (StatusCode::NOT_FOUND, "User not found"),
        AuthError::InvalidToken => (StatusCode::UNAUTHORIZED, "Invalid token"),
        AuthError::TokenExpired => (StatusCode::UNAUTHORIZED, "Token expired"),
        AuthError::InsufficientPermissions => (StatusCode::FORBIDDEN, "Insufficient permissions"),
        AuthError::AccountNotVerified => (StatusCode::FORBIDDEN, "Account not verified"),
        AuthError::AccountSuspended => (StatusCode::FORBIDDEN, "Account suspended"),
        AuthError::RateLimitExceeded => (StatusCode::TOO_MANY_REQUESTS, "Rate limit exceeded"),
        AuthError::EmailAlreadyExists => (StatusCode::CONFLICT, "Email already exists"),
        AuthError::UsernameAlreadyExists => (StatusCode::CONFLICT, "Username already exists"),
        AuthError::OAuthError(ref msg) => (StatusCode::BAD_REQUEST, msg.as_str()),
        AuthError::ValidationError(ref msg) => (StatusCode::BAD_REQUEST, msg.as_str()),
        AuthError::DatabaseError => (StatusCode::INTERNAL_SERVER_ERROR, "Database error"),
        AuthError::InternalError => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"),
        AuthError::TwoFactorRequired => (
            StatusCode::UNAUTHORIZED,
            "Two-factor authentication required",
        ),
        AuthError::Invalid2FACode => (StatusCode::UNAUTHORIZED, "Invalid 2FA code"),
        AuthError::TwoFactorAlreadyEnabled => (StatusCode::BAD_REQUEST, "2FA already enabled"),
        AuthError::TwoFactorNotEnabled => (StatusCode::BAD_REQUEST, "2FA not enabled"),
        AuthError::InvalidBackupCode => (StatusCode::UNAUTHORIZED, "Invalid backup code"),
        AuthError::TwoFactorSetupRequired => (StatusCode::BAD_REQUEST, "2FA setup required"),
        AuthError::TooMany2FAAttempts => (StatusCode::TOO_MANY_REQUESTS, "Too many 2FA attempts"),
    };

    let body = serde_json::json!({
        "error": message,
        "code": status.as_u16()
    });

    (status, axum::Json(body)).into_response()
}

/// Helper macro for creating authorization middleware
#[macro_export]
macro_rules! require_auth_with {
    ($permission:expr) => {
        axum::middleware::from_fn(require_permission($permission))
    };
    (role: $role:expr) => {
        axum::middleware::from_fn(require_role($role))
    };
    (admin) => {
        axum::middleware::from_fn(require_admin)
    };
    (moderator) => {
        axum::middleware::from_fn(require_moderator)
    };
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_auth_context_creation() {
        let ctx = AuthContext::new();
        assert!(!ctx.is_authenticated());
        assert!(ctx.user.is_none());
        assert!(ctx.session_id.is_none());
    }

    #[test]
    fn test_auth_context_with_user() {
        let user = create_test_user();
        let ctx = AuthContext::with_user(user.clone());

        assert!(ctx.is_authenticated());
        assert!(ctx.user.is_some());
        assert_eq!(
            ctx.user.as_ref().expect("user should be present").id,
            user.id
        );
    }

    #[test]
    fn test_auth_context_permissions() {
        let mut user = create_test_user();
        user.roles = vec![Role::Admin];
        let ctx = AuthContext::with_user(user);

        assert!(ctx.has_permission(&Permission::ReadUsers));
        assert!(ctx.has_role(&Role::Admin));
        assert!(ctx.is_admin());
    }

    fn create_test_user() -> User {
        User {
            id: Uuid::new_v4(),
            email: "test@example.com".to_string(),
            username: "testuser".to_string(),
            display_name: Some("Test User".to_string()),
            avatar_url: None,
            roles: vec![Role::User],
            is_active: true,
            email_verified: true,
            created_at: chrono::Utc::now(),
            updated_at: chrono::Utc::now(),
            last_login: None,
            two_factor_enabled: false,
            profile: rustelo_core_lib::auth::UserProfile::default(),
        }
    }
}
