// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Keystone authentication middleware
//!
//! This middleware intercepts HTTP requests and checks for OpenStack Keystone
//! authentication headers (X-Auth-Token or Swift X-Storage-Token). If found, it validates the token
//! with Keystone and stores the authenticated credentials in task-local storage
//! for use by downstream authentication handlers.
//!
//! ## Authentication Flow
//!
//! 1. Check if Keystone is enabled (via global provider)
//! 2. Extract X-Auth-Token or X-Storage-Token header from request
//! 3. If token present:
//!    - Validate with Keystone service
//!    - On success: Store credentials in task-local, continue processing
//!    - On failure: Return 401 Unauthorized immediately
//! 4. If no token: Pass through to standard S3 authentication
//!
//! ## Task-Local Storage
//!
//! Uses tokio task-local storage to pass credentials from middleware to
//! auth handlers without modifying request/response types. This is async-safe
//! and properly scoped to the request lifetime.

use bytes::Bytes;
use futures::Future;
use http::{HeaderMap, Request, Response, StatusCode};
use http_body::Body;
use http_body_util::{BodyExt, Full};
use hyper::body::Incoming;
use rustfs_credentials::Credentials;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tower::{Layer, Service};
use tracing::{debug, info, warn};

use crate::KeystoneAuthProvider;

const HEADER_X_AUTH_TOKEN: &str = "X-Auth-Token";
const HEADER_X_STORAGE_TOKEN: &str = "X-Storage-Token";

// Task-local storage for Keystone credentials
// This allows passing credentials from middleware to auth handlers
// without modifying the request/response types
tokio::task_local! {
    pub static KEYSTONE_CREDENTIALS: Option<Credentials>;
}

/// Tower Layer for Keystone authentication
///
/// This layer wraps services with Keystone authentication middleware.
/// It checks for X-Auth-Token headers and validates them with OpenStack Keystone.
#[derive(Clone)]
pub struct KeystoneAuthLayer {
    keystone_auth: Option<Arc<KeystoneAuthProvider>>,
}

impl KeystoneAuthLayer {
    /// Create a new Keystone authentication layer
    ///
    /// # Arguments
    ///
    /// * `keystone_auth` - Optional Keystone auth provider. If None, middleware is disabled.
    pub fn new(keystone_auth: Option<Arc<KeystoneAuthProvider>>) -> Self {
        if keystone_auth.is_some() {
            info!("Keystone authentication middleware enabled");
        } else {
            debug!("Keystone authentication middleware disabled (no provider)");
        }
        Self { keystone_auth }
    }
}

impl<S> Layer<S> for KeystoneAuthLayer {
    type Service = KeystoneAuthMiddleware<S>;

    fn layer(&self, inner: S) -> Self::Service {
        KeystoneAuthMiddleware {
            inner,
            keystone_auth: self.keystone_auth.clone(),
        }
    }
}

/// Keystone authentication middleware service
///
/// This service intercepts requests, validates Keystone tokens if present,
/// and stores authenticated credentials in task-local storage.
#[derive(Clone)]
pub struct KeystoneAuthMiddleware<S> {
    inner: S,
    keystone_auth: Option<Arc<KeystoneAuthProvider>>,
}

type BoxError = Box<dyn std::error::Error + Send + Sync>;
type BoxBody = http_body_util::combinators::UnsyncBoxBody<Bytes, BoxError>;

impl<S, B> Service<Request<Incoming>> for KeystoneAuthMiddleware<S>
where
    S: Service<Request<Incoming>, Response = Response<B>> + Clone + Send + 'static,
    S::Future: Send + 'static,
    S::Error: Send + 'static,
    B: Body<Data = Bytes> + Send + 'static,
    B::Error: Into<BoxError> + Send + 'static,
{
    type Response = Response<BoxBody>;
    type Error = S::Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: Request<Incoming>) -> Self::Future {
        let keystone_auth = self.keystone_auth.clone();
        let mut inner = self.inner.clone();

        Box::pin(async move {
            // Check if Keystone is enabled
            let keystone_auth = match keystone_auth {
                Some(auth) => auth,
                None => {
                    // No Keystone configured, pass through to normal authentication
                    debug!("Keystone middleware: No provider configured, passing through");
                    let resp = inner.call(req).await?;
                    let (parts, body) = resp.into_parts();
                    let body: BoxBody = body.map_err(Into::into).boxed_unsync();
                    return Ok(Response::from_parts(parts, body));
                }
            };

            let token = extract_keystone_token(req.headers());

            if let Some(token) = token {
                debug!("Keystone middleware: Found Keystone token header, validating");

                // Validate token with Keystone
                match keystone_auth.authenticate_with_token(token).await {
                    Ok(credentials) => {
                        // Authentication successful!
                        info!("Keystone middleware: Authentication successful for user: {}", credentials.parent_user);

                        // Store credentials in task-local storage and continue processing
                        // The auth handlers will retrieve these credentials when needed
                        let resp = KEYSTONE_CREDENTIALS.scope(Some(credentials), inner.call(req)).await?;
                        let (parts, body) = resp.into_parts();
                        let body: BoxBody = body.map_err(Into::into).boxed_unsync();
                        return Ok(Response::from_parts(parts, body));
                    }
                    Err(e) => {
                        // Authentication failed - return 401 Unauthorized immediately
                        // Per Q5.A: Return 401 immediately, no fallback to local auth
                        warn!("Keystone middleware: Authentication failed: {}", e);

                        let error_xml = format!(
                            r#"<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>InvalidToken</Code>
    <Message>Invalid Keystone token</Message>
    <Details>{}</Details>
</Error>"#,
                            xml_escape(&e.to_string())
                        );

                        let body: BoxBody = Full::new(Bytes::from(error_xml))
                            .map_err(|e| -> BoxError { Box::new(e) })
                            .boxed_unsync();

                        let response = Response::builder()
                            .status(StatusCode::UNAUTHORIZED)
                            .header("Content-Type", "application/xml")
                            .header("WWW-Authenticate", "Keystone")
                            .body(body)
                            .unwrap();

                        return Ok(response);
                    }
                }
            }

            // No Keystone token header present, pass through to normal S3 authentication
            debug!("Keystone middleware: No Keystone token header, passing through to S3 auth");
            let resp = inner.call(req).await?;
            let (parts, body) = resp.into_parts();
            let body: BoxBody = body.map_err(Into::into).boxed_unsync();
            Ok(Response::from_parts(parts, body))
        })
    }
}

fn extract_keystone_token(headers: &HeaderMap) -> Option<&str> {
    headers
        .get(HEADER_X_AUTH_TOKEN)
        .and_then(|v| v.to_str().ok())
        .or_else(|| headers.get(HEADER_X_STORAGE_TOKEN).and_then(|v| v.to_str().ok()))
}

/// Escape XML special characters to prevent injection
fn xml_escape(s: &str) -> String {
    s.replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
        .replace('\'', "&apos;")
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{KeystoneClient, KeystoneVersion};
    use std::time::Duration;

    #[test]
    fn test_layer_creation_no_keystone() {
        // Test that layer can be created without Keystone provider
        let layer = KeystoneAuthLayer::new(None);
        assert!(layer.keystone_auth.is_none());
    }

    #[test]
    fn test_layer_creation_with_keystone() {
        // Test that layer can be created with Keystone provider
        let client = KeystoneClient::new(
            "http://localhost:5000".to_string(),
            KeystoneVersion::V3,
            None,
            None,
            None,
            "Default".to_string(),
            true,
            Duration::from_secs(30),
        );
        let provider = KeystoneAuthProvider::new(client, 100, Duration::from_secs(60), true);
        let layer = KeystoneAuthLayer::new(Some(Arc::new(provider)));
        assert!(layer.keystone_auth.is_some());
    }

    #[tokio::test]
    async fn test_extract_keystone_token() {
        let mut headers = HeaderMap::new();
        assert!(extract_keystone_token(&headers).is_none());

        headers.insert(HEADER_X_AUTH_TOKEN, "test-token-123".parse().expect("auth token header should parse"));
        assert_eq!(extract_keystone_token(&headers), Some("test-token-123"));

        headers.clear();
        headers.insert(
            HEADER_X_STORAGE_TOKEN,
            "swift-token-456".parse().expect("storage token header should parse"),
        );
        assert_eq!(extract_keystone_token(&headers), Some("swift-token-456"));

        headers.insert(HEADER_X_AUTH_TOKEN, "auth-token-789".parse().expect("auth token header should parse"));
        assert_eq!(extract_keystone_token(&headers), Some("auth-token-789"));
    }

    #[tokio::test]
    async fn test_xml_escape() {
        assert_eq!(xml_escape("normal text"), "normal text");
        assert_eq!(xml_escape("<tag>"), "&lt;tag&gt;");
        assert_eq!(xml_escape("a&b"), "a&amp;b");
        assert_eq!(xml_escape("it's \"quoted\""), "it&apos;s &quot;quoted&quot;");
    }

    #[tokio::test]
    async fn test_task_local_scope() {
        // Verify that task-local storage works correctly
        use rustfs_credentials::Credentials;

        let creds = Credentials {
            access_key: "test-key".to_string(),
            parent_user: "test-user".to_string(),
            ..Default::default()
        };

        // Should be None outside of scope
        assert!(KEYSTONE_CREDENTIALS.try_with(|c| c.clone()).is_err());

        // Should be Some inside scope
        KEYSTONE_CREDENTIALS
            .scope(Some(creds.clone()), async {
                let stored = KEYSTONE_CREDENTIALS.try_with(|c| c.clone()).unwrap();
                assert!(stored.is_some());
                assert_eq!(stored.unwrap().access_key, "test-key");
            })
            .await;

        // Should be None again after scope
        assert!(KEYSTONE_CREDENTIALS.try_with(|c| c.clone()).is_err());
    }

    // Note: test_valid_token and test_invalid_token require mock Keystone server
    // These will be added in Task 3.3 (Integration Testing)
}
