//! JWT Revocation Admin Endpoints
//!
//! Migrated from trading_service to api for centralized JWT management.
//! This module provides HTTP/gRPC endpoints for JWT token revocation management.
//!
//! ## Security
//! - All endpoints require admin permissions
//! - Audit logging for all revocation operations
//! - Rate limiting to prevent abuse

use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tonic::Status;
use tracing::{error, info};

use super::revocation::{Jti, JwtRevocationService, RevocationReason, RevocationStatistics};

/// Request to revoke the current user's token
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RevokeCurrentTokenRequest {
    /// Optional reason for revocation
    pub reason: Option<String>,
}

/// Request to revoke a specific token by JTI
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RevokeTokenRequest {
    /// JWT ID to revoke
    pub jti: String,
    /// Reason for revocation
    pub reason: String,
    /// User ID who owned the token
    pub user_id: String,
}

/// Request to revoke all tokens for a user
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RevokeUserTokensRequest {
    /// User ID whose tokens should be revoked
    pub user_id: String,
    /// Reason for revocation
    pub reason: String,
}

/// Response for token revocation operations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RevocationResponse {
    /// Whether the operation succeeded
    pub success: bool,
    /// Number of tokens revoked
    pub tokens_revoked: usize,
    /// Message describing the result
    pub message: String,
}

/// Authentication context for endpoints
#[derive(Debug, Clone)]
pub struct AuthContext {
    /// User ID
    pub user_id: String,
    /// JWT claims if authenticated via JWT
    pub jwt_claims: Option<super::service::JwtClaims>,
    /// User permissions
    pub permissions: Vec<String>,
    /// Client IP address
    pub client_ip: Option<String>,
}

impl AuthContext {
    /// Check if user has specific permission
    pub fn has_permission(&self, permission: &str) -> bool {
        self.permissions.contains(&permission.to_string())
    }
}

/// Revocation endpoints handler
#[derive(Clone)]
pub struct RevocationEndpoints {
    revocation_service: Arc<JwtRevocationService>,
}

impl RevocationEndpoints {
    /// Create new revocation endpoints handler
    pub fn new(revocation_service: Arc<JwtRevocationService>) -> Self {
        Self { revocation_service }
    }

    /// Revoke the current user's token (user self-service)
    pub async fn revoke_current_token(
        &self,
        auth_context: &AuthContext,
        request: RevokeCurrentTokenRequest,
    ) -> Result<RevocationResponse, Status> {
        // Extract JTI from JWT claims
        let jwt_claims = auth_context.jwt_claims.as_ref().ok_or_else(|| {
            Status::invalid_argument("Token revocation only supported for JWT authentication")
        })?;

        let jti = Jti::from_string(jwt_claims.jti.clone());

        // Calculate remaining TTL
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|e| Status::internal(format!("System time error: {}", e)))?
            .as_secs();

        let ttl = jwt_claims.exp.saturating_sub(now);

        if ttl == 0 {
            return Ok(RevocationResponse {
                success: false,
                tokens_revoked: 0,
                message: "Token already expired".to_string(),
            });
        }

        let reason = if let Some(user_reason) = request.reason {
            RevocationReason::Other(user_reason)
        } else {
            RevocationReason::UserLogout
        };

        self.revocation_service
            .revoke_token(
                &jti,
                &auth_context.user_id,
                ttl,
                reason,
                &auth_context.user_id,
                auth_context.client_ip.clone(),
            )
            .await
            .map_err(|e| Status::internal(format!("Failed to revoke token: {}", e)))?;

        info!(
            "User {} revoked their own token (jti={})",
            auth_context.user_id, jti
        );

        Ok(RevocationResponse {
            success: true,
            tokens_revoked: 1,
            message: "Token revoked successfully".to_string(),
        })
    }

    /// Revoke a specific token by `JTI` (admin only)
    pub async fn revoke_token_by_jti(
        &self,
        auth_context: &AuthContext,
        request: RevokeTokenRequest,
    ) -> Result<RevocationResponse, Status> {
        // Check admin permission
        if !auth_context.has_permission("admin.revoke_tokens") {
            return Err(Status::permission_denied(
                "Admin permission required to revoke tokens",
            ));
        }

        let jti = Jti::from_string(request.jti.clone());

        // Use standard 1 hour TTL for admin-revoked tokens
        // This is reasonable since admin revocations are typically for active threats
        // and tokens should expire within a reasonable timeframe
        let ttl = 3600u64; // 1 hour (standard JWT access token TTL)

        let reason = match request.reason.as_str() {
            "compromised" => RevocationReason::TokenCompromised,
            "suspicious" => RevocationReason::SuspiciousActivity,
            "admin" => RevocationReason::AdminRevocation,
            other => RevocationReason::Other(other.to_string()),
        };

        self.revocation_service
            .revoke_token(
                &jti,
                &request.user_id,
                ttl,
                reason,
                &auth_context.user_id,
                auth_context.client_ip.clone(),
            )
            .await
            .map_err(|e| Status::internal(format!("Failed to revoke token: {}", e)))?;

        info!(
            "Admin {} revoked token {} for user {}",
            auth_context.user_id, jti, request.user_id
        );

        Ok(RevocationResponse {
            success: true,
            tokens_revoked: 1,
            message: format!("Token {} revoked successfully", jti),
        })
    }

    /// Revoke all tokens for a specific user (admin only)
    pub async fn revoke_all_user_tokens(
        &self,
        auth_context: &AuthContext,
        request: RevokeUserTokensRequest,
    ) -> Result<RevocationResponse, Status> {
        // Check admin permission
        if !auth_context.has_permission("admin.revoke_tokens") {
            return Err(Status::permission_denied(
                "Admin permission required to revoke user tokens",
            ));
        }

        let reason = match request.reason.as_str() {
            "account_locked" => RevocationReason::AccountLocked,
            "password_change" => RevocationReason::PasswordChange,
            "compromised" => RevocationReason::TokenCompromised,
            "suspicious" => RevocationReason::SuspiciousActivity,
            "admin" => RevocationReason::AdminRevocation,
            other => RevocationReason::Other(other.to_string()),
        };

        let revoked_count = self
            .revocation_service
            .revoke_all_user_tokens(&request.user_id, reason, &auth_context.user_id)
            .await
            .map_err(|e| Status::internal(format!("Failed to revoke user tokens: {}", e)))?;

        info!(
            "Admin {} revoked {} tokens for user {}",
            auth_context.user_id, revoked_count, request.user_id
        );

        Ok(RevocationResponse {
            success: true,
            tokens_revoked: revoked_count,
            message: format!(
                "Revoked {} tokens for user {}",
                revoked_count, request.user_id
            ),
        })
    }

    /// Get revocation statistics (admin only)
    pub async fn get_revocation_stats(
        &self,
        auth_context: &AuthContext,
    ) -> Result<RevocationStatistics, Status> {
        // Check admin permission
        if !auth_context.has_permission("admin.view_stats") {
            return Err(Status::permission_denied(
                "Admin permission required to view revocation statistics",
            ));
        }

        let stats = self
            .revocation_service
            .get_statistics()
            .await
            .map_err(|e| Status::internal(format!("Failed to get statistics: {}", e)))?;

        Ok(stats)
    }

    /// Health check for revocation service
    pub async fn health_check(&self) -> Result<bool, Status> {
        match self.revocation_service.get_statistics().await {
            Ok(_) => Ok(true),
            Err(e) => {
                error!("Revocation service health check failed: {}", e);
                Ok(false)
            },
        }
    }
}

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

    async fn create_test_revocation_service() -> Result<Arc<JwtRevocationService>> {
        let redis_url = std::env::var("TEST_REDIS_URL")
            .unwrap_or_else(|_| "redis://localhost:6379/15".to_string());
        let config = RevocationConfig::default();
        let service = JwtRevocationService::new(&redis_url, config).await?;
        Ok(Arc::new(service))
    }

    #[tokio::test]
    #[ignore = "requires Redis instance (TEST_REDIS_URL)"]
    async fn test_revoke_user_tokens_requires_admin() {
        let service = match create_test_revocation_service().await {
            Ok(s) => s,
            Err(e) => {
                tracing::warn!("Redis unavailable, skipping test: {:?}", e);
                return; // Skip test gracefully
            },
        };
        let endpoints = RevocationEndpoints::new(service);

        let auth_context = AuthContext {
            user_id: "test_user".to_string(),
            jwt_claims: None,
            permissions: vec![], // No admin permission
            client_ip: None,
        };

        let request = RevokeUserTokensRequest {
            user_id: "other_user".to_string(),
            reason: "test".to_string(),
        };

        let result = endpoints
            .revoke_all_user_tokens(&auth_context, request)
            .await;
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().code(), tonic::Code::PermissionDenied);
    }
}
