//! Circuit breaker middleware (Phase 7D).
//!
//! This middleware checks if an agent's circuit is tripped before allowing requests.
//! It runs BEFORE admission control and metering, blocking requests from misbehaving
//! agents before they consume any resources.
//!
//! # Request Flow
//!
//! 1. Extract `X-Agent-Id` header (hex-encoded 32-byte public key)
//! 2. Check circuit breaker state
//! 3. If Open: return 503 with retry-after headers
//! 4. If HalfOpen: allow request (testing recovery)
//! 5. If Closed: allow request (normal operation)
//!
//! # Response Headers (on 503)
//!
//! | Header | Description |
//! |--------|-------------|
//! | `X-Circuit-Breaker-State` | Current state: "open" or "half_open" |
//! | `X-Circuit-Breaker-Retry-After` | Seconds until agent can retry |
//! | `X-Circuit-Breaker-Failures` | Number of failures that triggered the ban |
//! | `Retry-After` | Standard HTTP retry-after header (seconds) |

use axum::{
    body::Body,
    http::{Request, Response, StatusCode},
    response::IntoResponse,
    Json,
};
use futures::future::BoxFuture;
use serde::Serialize;
use std::sync::Arc;
use std::task::{Context, Poll};
use stemedb_storage::CircuitBreakerStore;
use tower::{Layer, Service};
use tracing::{debug, warn};

/// Header name for agent identification (shared with AdmissionLayer and MeterLayer).
pub const AGENT_ID_HEADER: &str = "x-agent-id";

/// Response header for circuit breaker state.
pub const CIRCUIT_STATE_HEADER: &str = "x-circuit-breaker-state";

/// Response header for retry-after seconds.
pub const CIRCUIT_RETRY_AFTER_HEADER: &str = "x-circuit-breaker-retry-after";

/// Response header for failure count.
pub const CIRCUIT_FAILURES_HEADER: &str = "x-circuit-breaker-failures";

/// Error response when circuit is open.
#[derive(Debug, Serialize)]
struct CircuitOpenError {
    /// Human-readable error message.
    error: String,
    /// Error code for programmatic handling.
    code: String,
    /// Current circuit state.
    state: String,
    /// Seconds until the agent can retry.
    retry_after_secs: u64,
    /// Number of failures that triggered the ban.
    failure_count: usize,
}

/// Tower Layer for circuit breaker.
///
/// Wrap your router with this layer to enable per-agent circuit breakers.
/// This layer should be applied OUTERMOST (runs first) so that banned
/// agents are rejected before any other processing.
///
/// # Example
///
/// ```ignore
/// let circuit_breaker_layer = CircuitBreakerLayer::new(cb_store);
/// let admission_layer = AdmissionLayer::new(admission_store);
/// let meter_layer = MeterLayer::new(quota_store);
///
/// let app = Router::new()
///     .route("/v1/assert", post(create_assertion))
///     .layer(meter_layer)           // Innermost: runs third
///     .layer(admission_layer)       // Middle: runs second
///     .layer(circuit_breaker_layer) // Outermost: runs FIRST
/// ```
#[derive(Clone)]
pub struct CircuitBreakerLayer<C> {
    circuit_breaker_store: Arc<C>,
    /// Paths that bypass circuit breaker check (e.g., health checks, admin endpoints).
    bypass_paths: Vec<String>,
}

impl<C> CircuitBreakerLayer<C> {
    /// Create a new CircuitBreakerLayer.
    pub fn new(circuit_breaker_store: Arc<C>) -> Self {
        Self {
            circuit_breaker_store,
            bypass_paths: vec![
                "/v1/health".to_string(),
                "/v1/admission/status".to_string(),
                "/v1/admin".to_string(), // All admin endpoints bypass
                "/swagger-ui".to_string(),
                "/api-docs".to_string(),
            ],
        }
    }

    /// Add a path to bypass circuit breaker check.
    pub fn bypass_path(mut self, path: impl Into<String>) -> Self {
        self.bypass_paths.push(path.into());
        self
    }
}

impl<S, C> Layer<S> for CircuitBreakerLayer<C>
where
    C: Clone,
{
    type Service = CircuitBreakerService<S, C>;

    fn layer(&self, inner: S) -> Self::Service {
        CircuitBreakerService {
            inner,
            circuit_breaker_store: Arc::clone(&self.circuit_breaker_store),
            bypass_paths: self.bypass_paths.clone(),
        }
    }
}

/// Tower Service for circuit breaker.
#[derive(Clone)]
pub struct CircuitBreakerService<S, C> {
    inner: S,
    circuit_breaker_store: Arc<C>,
    bypass_paths: Vec<String>,
}

impl<S, C> CircuitBreakerService<S, C> {
    /// Check if path should bypass circuit breaker check.
    #[allow(dead_code)] // Used in tests
    fn should_bypass(&self, path: &str) -> bool {
        self.bypass_paths.iter().any(|p| path.starts_with(p))
    }

    /// Extract agent ID from request headers.
    fn extract_agent_id(req: &Request<Body>) -> Option<[u8; 32]> {
        req.headers().get(AGENT_ID_HEADER).and_then(|v| v.to_str().ok()).and_then(|s| {
            let bytes = hex::decode(s).ok()?;
            if bytes.len() == 32 {
                let mut arr = [0u8; 32];
                arr.copy_from_slice(&bytes);
                Some(arr)
            } else {
                None
            }
        })
    }

    /// Build a 503 response for circuit open.
    fn circuit_open_response(retry_after: u64, failure_count: usize) -> Response<Body> {
        let error = CircuitOpenError {
            error: "Service temporarily unavailable - circuit breaker is open".to_string(),
            code: "CIRCUIT_BREAKER_OPEN".to_string(),
            state: "open".to_string(),
            retry_after_secs: retry_after,
            failure_count,
        };

        let mut response = (StatusCode::SERVICE_UNAVAILABLE, Json(error)).into_response();

        let headers = response.headers_mut();
        if let Ok(v) = "open".parse() {
            headers.insert(CIRCUIT_STATE_HEADER, v);
        }
        if let Ok(v) = retry_after.to_string().parse() {
            headers.insert(CIRCUIT_RETRY_AFTER_HEADER, v);
            // Also set standard Retry-After header
            if let Ok(v2) = retry_after.to_string().parse() {
                headers.insert("retry-after", v2);
            }
        }
        if let Ok(v) = failure_count.to_string().parse() {
            headers.insert(CIRCUIT_FAILURES_HEADER, v);
        }

        response
    }
}

impl<S, C> Service<Request<Body>> for CircuitBreakerService<S, C>
where
    S: Service<Request<Body>, Response = Response<Body>> + Clone + Send + 'static,
    S::Future: Send,
    C: CircuitBreakerStore + 'static,
{
    type Response = Response<Body>;
    type Error = S::Error;
    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

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

    fn call(&mut self, req: Request<Body>) -> Self::Future {
        let path = req.uri().path().to_string();
        let circuit_breaker_store = Arc::clone(&self.circuit_breaker_store);
        let bypass_paths = self.bypass_paths.clone();

        // Clone the inner service for the async block
        let mut inner = self.inner.clone();

        Box::pin(async move {
            // Check if this path should bypass circuit breaker
            if bypass_paths.iter().any(|p| path.starts_with(p)) {
                debug!(path = %path, "Bypassing circuit breaker for path");
                return inner.call(req).await;
            }

            // Only check circuit breaker for write paths
            let is_write_path = path.starts_with("/v1/assert")
                || path.starts_with("/v1/vote")
                || path.starts_with("/v1/supersede");

            if !is_write_path {
                // Read-only paths don't trigger circuit breaker
                debug!(path = %path, "Skipping circuit breaker for read-only path");
                return inner.call(req).await;
            }

            // Extract agent ID
            let agent_id = match Self::extract_agent_id(&req) {
                Some(id) => id,
                None => {
                    // No agent ID provided, pass through (will fail auth later)
                    debug!(path = %path, "No agent ID, skipping circuit breaker");
                    return inner.call(req).await;
                }
            };

            // Get current timestamp
            let current_time = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .map(|d| d.as_secs())
                .unwrap_or(0);

            // Check if agent is allowed
            let allowed = match circuit_breaker_store.check_allowed(&agent_id, current_time).await {
                Ok(allowed) => allowed,
                Err(e) => {
                    // On error, allow the request (fail open for availability)
                    warn!(error = %e, "Circuit breaker check failed, allowing request");
                    return inner.call(req).await;
                }
            };

            if !allowed {
                // Get retry-after and failure info
                let retry_after = circuit_breaker_store
                    .retry_after(&agent_id, current_time)
                    .await
                    .ok()
                    .flatten()
                    .unwrap_or(0);

                let failure_count = circuit_breaker_store
                    .get_circuit(&agent_id)
                    .await
                    .ok()
                    .flatten()
                    .map(|r| r.failure_count())
                    .unwrap_or(0);

                warn!(
                    agent = %hex::encode(agent_id),
                    retry_after = retry_after,
                    failure_count = failure_count,
                    "Request blocked by circuit breaker"
                );

                return Ok(Self::circuit_open_response(retry_after, failure_count));
            }

            // Circuit is Closed or HalfOpen - allow the request
            debug!(agent = %hex::encode(agent_id), "Circuit breaker allowing request");
            inner.call(req).await
        })
    }
}

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

    #[test]
    fn test_bypass_paths() {
        let service = CircuitBreakerService::<(), ()> {
            inner: (),
            circuit_breaker_store: Arc::new(()),
            bypass_paths: vec![
                "/v1/health".to_string(),
                "/v1/admin".to_string(),
                "/swagger-ui".to_string(),
            ],
        };

        assert!(service.should_bypass("/v1/health"));
        assert!(service.should_bypass("/v1/admin/circuit-breaker"));
        assert!(service.should_bypass("/swagger-ui/index.html"));
        assert!(!service.should_bypass("/v1/assert"));
        assert!(!service.should_bypass("/v1/vote"));
    }

    #[test]
    fn test_extract_agent_id() {
        let req = Request::builder()
            .header(
                AGENT_ID_HEADER,
                "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
            )
            .body(Body::empty())
            .expect("build request");

        let agent_id = CircuitBreakerService::<(), ()>::extract_agent_id(&req);
        assert!(agent_id.is_some());
        let id = agent_id.expect("id");
        assert_eq!(id[0], 0x01);
        assert_eq!(id[1], 0x23);
    }

    #[test]
    fn test_extract_agent_id_invalid_length() {
        let req = Request::builder()
            .header(AGENT_ID_HEADER, "0123456789abcdef") // Too short
            .body(Body::empty())
            .expect("build request");

        let agent_id = CircuitBreakerService::<(), ()>::extract_agent_id(&req);
        assert!(agent_id.is_none());
    }

    #[test]
    fn test_extract_agent_id_missing() {
        let req = Request::builder().body(Body::empty()).expect("build request");

        let agent_id = CircuitBreakerService::<(), ()>::extract_agent_id(&req);
        assert!(agent_id.is_none());
    }
}
