use axum::{
    extract::Request,
    http::{header::HeaderMap, StatusCode},
    middleware::Next,
    response::Response,
};
use uuid::Uuid;

/// Profile type extracted from Ory headers
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProfileType {
    Team,
    Customer,
}

impl ProfileType {
    fn from_str(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "team" => Some(Self::Team),
            "customer" => Some(Self::Customer),
            _ => None,
        }
    }
}

/// User context extracted from Ory Oathkeeper headers
#[derive(Debug, Clone)]
pub struct UserContext {
    /// Kratos identity UUID - also serves as profile primary key
    pub user_id: Uuid,
    pub profile_type: ProfileType,
    pub email: Option<String>,
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub phone: Option<String>,
}

impl UserContext {
    /// Extract user context from Ory Oathkeeper headers
    pub fn from_headers(headers: &HeaderMap) -> Option<Self> {
        let user_id = headers
            .get("X-User-ID")
            .and_then(|v| v.to_str().ok())
            .and_then(|s| Uuid::parse_str(s).ok())?;

        let profile_type = headers
            .get("X-User-Profile-Type")
            .and_then(|v| v.to_str().ok())
            .and_then(ProfileType::from_str)?;

        let email = headers
            .get("X-User-Email")
            .and_then(|v| v.to_str().ok())
            .map(String::from);

        let first_name = headers
            .get("X-User-First-Name")
            .and_then(|v| v.to_str().ok())
            .map(String::from);

        let last_name = headers
            .get("X-User-Last-Name")
            .and_then(|v| v.to_str().ok())
            .map(String::from);

        let phone = headers
            .get("X-User-Phone")
            .and_then(|v| v.to_str().ok())
            .map(String::from);

        Some(Self {
            user_id,
            profile_type,
            email,
            first_name,
            last_name,
            phone,
        })
    }
}

/// Middleware to validate Ory Oathkeeper secret and extract user context
pub async fn ory_auth_middleware(
    mut request: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let headers = request.headers();

    // Get expected secret from environment
    let expected_secret = std::env::var("OATHKEEPER_SECRET").unwrap_or_default();

    // Validate Oathkeeper secret
    let provided_secret = headers
        .get("X-Oathkeeper-Secret")
        .and_then(|v| v.to_str().ok())
        .unwrap_or_default();

    // Debug logging for auth troubleshooting
    let has_expected = !expected_secret.is_empty();
    let has_provided = !provided_secret.is_empty();
    let secrets_match = provided_secret == expected_secret;

    tracing::debug!(
        has_expected_secret = has_expected,
        has_provided_secret = has_provided,
        secrets_match = secrets_match,
        user_id = ?headers.get("X-User-ID").and_then(|v| v.to_str().ok()),
        "Auth middleware validation"
    );

    if !expected_secret.is_empty() && provided_secret != expected_secret {
        tracing::warn!(
            expected_len = expected_secret.len(),
            provided_len = provided_secret.len(),
            "Invalid or missing Oathkeeper secret"
        );
        return Err(StatusCode::UNAUTHORIZED);
    }

    // Extract user context if present
    if let Some(user_ctx) = UserContext::from_headers(headers) {
        request.extensions_mut().insert(user_ctx);
    }

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

/// Extractor for optional user context
#[derive(Debug, Clone)]
pub struct OptionalUser(pub Option<UserContext>);

impl<S> axum::extract::FromRequestParts<S> for OptionalUser
where
    S: Send + Sync,
{
    type Rejection = std::convert::Infallible;

    async fn from_request_parts(
        parts: &mut axum::http::request::Parts,
        _state: &S,
    ) -> Result<Self, Self::Rejection> {
        Ok(Self(parts.extensions.get::<UserContext>().cloned()))
    }
}

/// Extractor for required user context
#[derive(Debug, Clone)]
pub struct RequiredUser(pub UserContext);

impl<S> axum::extract::FromRequestParts<S> for RequiredUser
where
    S: Send + Sync,
{
    type Rejection = StatusCode;

    async fn from_request_parts(
        parts: &mut axum::http::request::Parts,
        _state: &S,
    ) -> Result<Self, Self::Rejection> {
        parts
            .extensions
            .get::<UserContext>()
            .cloned()
            .map(Self)
            .ok_or(StatusCode::UNAUTHORIZED)
    }
}
