use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;

use axum::Json;
use axum::Router;
use axum::extract::{Query, Request, State};
use axum::http::StatusCode;
use axum::http::header::AUTHORIZATION;
use axum::middleware::{self, Next};
use axum::response::{IntoResponse, Response};
use axum::routing::{get, post};
use serde::{Deserialize, Serialize};
use subtle::ConstantTimeEq;
use tidaldb::query::retrieve::Retrieve;
use tidaldb::query::search::Search;
use tidaldb::schema::EntityId;
use tower::ServiceBuilder;
use tower::limit::ConcurrencyLimitLayer;
use tower_http::request_id::{
    MakeRequestId, PropagateRequestIdLayer, RequestId, SetRequestIdLayer,
};
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;

use crate::error::{Result, ServerError};
use crate::state::ServerState;

/// Maximum request body size. Requests exceeding this are rejected with 413
/// before any deserialization occurs.
const BODY_LIMIT_BYTES: usize = 2 * 1024 * 1024;

/// Maximum wall-clock time a single request may occupy. Exceeded requests
/// receive 408 Request Timeout.
const REQUEST_TIMEOUT_SECS: u64 = 30;

/// Maximum number of requests processed concurrently. Additional requests
/// are queued by the concurrency layer until a slot opens.
const MAX_CONCURRENCY: usize = 100;

/// Sequential request ID generator — assigns monotonically increasing IDs.
#[derive(Clone, Default)]
struct SequentialRequestId(Arc<AtomicU64>);

impl MakeRequestId for SequentialRequestId {
    fn make_request_id<B>(&mut self, _: &Request<B>) -> Option<RequestId> {
        // Relaxed ordering is sufficient: we only need unique IDs, not
        // cross-thread happens-before ordering.
        let id = self.0.fetch_add(1, Ordering::Relaxed);
        axum::http::HeaderValue::from_str(&id.to_string())
            .ok()
            .map(RequestId::new)
    }
}

/// Build the application router.
///
/// Routes are split into two groups:
/// - **Public** (`/health`) — never requires auth; safe for liveness/readiness probes.
/// - **Protected** — require `Authorization: Bearer <key>` when `api_key` is `Some`.
///
/// Global middleware stack (applied to all routes, outermost → innermost):
/// 1. `SetRequestIdLayer` — assigns sequential `x-request-id` before the span is created
/// 2. `PropagateRequestIdLayer` — echoes `x-request-id` into the response
/// 3. `TraceLayer` — creates a structured span; reads the ID set in step 1
///
/// Protected-only middleware (not applied to `/health`):
/// 4. `TimeoutLayer` — returns 408 for requests exceeding [`REQUEST_TIMEOUT_SECS`]
/// 5. `ConcurrencyLimitLayer` — queues beyond [`MAX_CONCURRENCY`] in-flight requests
///
/// Keeping `/health` outside the timeout/concurrency layers means health probes
/// are never queued or timed out under saturation, preventing false liveness failures.
pub fn build_router(state: Arc<ServerState>, api_key: Option<Arc<str>>) -> Router {
    // Public routes — exempt from auth so health probes always work.
    let public = Router::new()
        .route("/health", get(health))
        .route("/health/startup", get(health_startup))
        .route("/health/live", get(health_live))
        .with_state(Arc::clone(&state));

    // Protected routes — gated by Bearer token when a key is configured.
    let protected = Router::new()
        .route("/items", post(create_item))
        .route("/embeddings", post(write_embedding))
        .route("/signals", post(write_signal))
        .route("/feed", get(feed))
        .route("/search", get(search))
        .layer(axum::extract::DefaultBodyLimit::max(BODY_LIMIT_BYTES))
        .with_state(state);

    let protected = match api_key {
        Some(key) => protected.layer(middleware::from_fn(move |req: Request, next: Next| {
            let key = key.clone();
            async move { bearer_auth(req, next, &key).await }
        })),
        None => protected,
    };

    // Timeout and concurrency apply only to protected routes so health probes
    // are never queued or dropped during overload.
    let protected = protected.layer(
        ServiceBuilder::new()
            .layer(TimeoutLayer::with_status_code(
                StatusCode::REQUEST_TIMEOUT,
                Duration::from_secs(REQUEST_TIMEOUT_SECS),
            ))
            .layer(ConcurrencyLimitLayer::new(MAX_CONCURRENCY)),
    );

    // SetRequestId must be outermost so the ID is in headers when TraceLayer
    // creates its span. In ServiceBuilder the first .layer() is outermost.
    public.merge(protected).layer(
        ServiceBuilder::new()
            .layer(SetRequestIdLayer::x_request_id(
                SequentialRequestId::default(),
            ))
            .layer(PropagateRequestIdLayer::x_request_id())
            .layer(
                TraceLayer::new_for_http().make_span_with(|req: &Request<_>| {
                    // x-request-id is guaranteed present: SetRequestIdLayer runs first.
                    let id = req
                        .headers()
                        .get("x-request-id")
                        .and_then(|v| v.to_str().ok())
                        .unwrap_or("-");
                    tracing::info_span!(
                        "request",
                        method = %req.method(),
                        uri = %req.uri(),
                        request_id = %id,
                    )
                }),
            ),
    )
}

/// Validate an `Authorization: Bearer <token>` header.
///
/// Returns `401 Unauthorized` with a `WWW-Authenticate: Bearer` header if the
/// token is absent or does not match the expected key. The comparison uses
/// constant-time equality to prevent timing-based token reconstruction.
async fn bearer_auth(request: Request, next: Next, expected_key: &str) -> Response {
    // RFC 7235 §2.1: auth-scheme tokens are case-insensitive.
    let token = request
        .headers()
        .get(AUTHORIZATION)
        .and_then(|v| v.to_str().ok())
        .and_then(|s| {
            // "Bearer " is 7 bytes; check case-insensitively then slice the token.
            if s.len() > 7 && s[..7].eq_ignore_ascii_case("bearer ") {
                Some(&s[7..])
            } else {
                None
            }
        });

    let authorized = match token {
        Some(t) => {
            let a = t.as_bytes();
            let b = expected_key.as_bytes();
            // Length mismatch does not leak token content; short-circuit is safe.
            a.len() == b.len() && bool::from(a.ct_eq(b))
        }
        None => false,
    };

    if authorized {
        next.run(request).await
    } else {
        (
            StatusCode::UNAUTHORIZED,
            [("www-authenticate", "Bearer")],
            Json(serde_json::json!({"error": "missing or invalid api key"})),
        )
            .into_response()
    }
}

#[derive(Deserialize)]
struct ItemRequest {
    entity_id: u64,
    metadata: HashMap<String, String>,
}

async fn create_item(
    State(state): State<Arc<ServerState>>,
    Json(req): Json<ItemRequest>,
) -> Result<StatusCode, AppError> {
    state
        .write_item(EntityId::new(req.entity_id), &req.metadata)
        .map_err(AppError)?;
    Ok(StatusCode::CREATED)
}

#[derive(Deserialize)]
struct EmbeddingRequest {
    entity_id: u64,
    values: Vec<f32>,
}

async fn write_embedding(
    State(state): State<Arc<ServerState>>,
    Json(req): Json<EmbeddingRequest>,
) -> Result<StatusCode, AppError> {
    state
        .write_embedding(EntityId::new(req.entity_id), &req.values)
        .map_err(AppError)?;
    Ok(StatusCode::NO_CONTENT)
}

#[derive(Deserialize)]
struct SignalRequest {
    entity_id: u64,
    signal: String,
    weight: f64,
    #[serde(default)]
    user_id: Option<u64>,
    #[serde(default)]
    creator_id: Option<u64>,
}

async fn write_signal(
    State(state): State<Arc<ServerState>>,
    Json(req): Json<SignalRequest>,
) -> Result<StatusCode, AppError> {
    state
        .signal(
            &req.signal,
            EntityId::new(req.entity_id),
            req.weight,
            req.user_id,
            req.creator_id,
        )
        .map_err(AppError)?;
    Ok(StatusCode::NO_CONTENT)
}

#[derive(Deserialize)]
struct FeedQuery {
    #[serde(default)]
    user_id: Option<u64>,
    #[serde(default = "default_profile")]
    profile: String,
    #[serde(default = "default_limit")]
    limit: u32,
    #[serde(default)]
    region: Option<String>,
}

fn default_profile() -> String {
    "for_you".into()
}

fn default_limit() -> u32 {
    20
}

#[derive(Serialize)]
struct FeedResponse {
    items: Vec<FeedItem>,
    total_candidates: usize,
    region: Option<String>,
}

#[derive(Serialize)]
struct FeedItem {
    entity_id: u64,
    score: f64,
    rank: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    signals: Option<Vec<SignalValue>>,
}

#[derive(Serialize)]
struct SignalValue {
    name: String,
    value: f64,
}

async fn feed(
    State(state): State<Arc<ServerState>>,
    Query(query): Query<FeedQuery>,
) -> Result<Json<FeedResponse>, AppError> {
    let mut builder = Retrieve::builder()
        .profile(&query.profile)
        .limit(query.limit as usize);

    if let Some(user_id) = query.user_id {
        builder = builder.for_user(user_id);
    }
    let retrieve = builder.build().map_err(|e| TidalErrorWrapper(e.into()))?;

    let result = state
        .retrieve(query.region.as_deref(), &retrieve)
        .map_err(AppError)?;

    let mut items = Vec::with_capacity(result.items.len());
    for item in result.items {
        let signals = if item.signals.is_empty() {
            None
        } else {
            Some(
                item.signals
                    .iter()
                    .map(|s| SignalValue {
                        name: s.name.clone(),
                        value: s.value,
                    })
                    .collect(),
            )
        };
        items.push(FeedItem {
            entity_id: item.entity_id.as_u64(),
            score: item.score,
            rank: item.rank,
            signals,
        });
    }

    Ok(Json(FeedResponse {
        items,
        total_candidates: result.total_candidates,
        region: query.region,
    }))
}

#[derive(Deserialize)]
struct SearchQueryParams {
    query: String,
    #[serde(default)]
    user_id: Option<u64>,
    #[serde(default = "default_limit")]
    limit: u32,
    #[serde(default)]
    region: Option<String>,
}

#[derive(Serialize)]
struct SearchResponse {
    items: Vec<SearchItem>,
    total_candidates: usize,
    region: Option<String>,
}

#[derive(Serialize)]
struct SearchItem {
    entity_id: u64,
    score: f64,
    rank: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    bm25_score: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    semantic_score: Option<f64>,
}

async fn search(
    State(state): State<Arc<ServerState>>,
    Query(query): Query<SearchQueryParams>,
) -> Result<Json<SearchResponse>, AppError> {
    let mut builder = Search::builder().query(&query.query).limit(query.limit);
    if let Some(user_id) = query.user_id {
        builder = builder.for_user(user_id);
    }
    let search = builder.build().map_err(|e| TidalErrorWrapper(e.into()))?;
    state
        .reload_text_index(query.region.as_deref())
        .map_err(AppError)?;
    let result = state
        .search(query.region.as_deref(), &search)
        .map_err(AppError)?;

    let items = result
        .items
        .into_iter()
        .map(|item| SearchItem {
            entity_id: item.entity_id.as_u64(),
            score: item.score,
            rank: item.rank,
            bm25_score: item.bm25_score.map(f64::from),
            semantic_score: item.semantic_score.map(f64::from),
        })
        .collect();

    Ok(Json(SearchResponse {
        items,
        total_candidates: result.total_candidates,
        region: query.region,
    }))
}

/// Startup probe: always 200 (no migrations to check).
async fn health_startup() -> Json<serde_json::Value> {
    Json(serde_json::json!({ "ok": true, "service": "tidaldb" }))
}

/// Liveness probe: always 200 (process is alive).
async fn health_live() -> Json<serde_json::Value> {
    Json(serde_json::json!({ "ok": true, "service": "tidaldb" }))
}

/// Readiness probe: 200 when ready, 503 when shutting down.
async fn health(
    State(state): State<Arc<ServerState>>,
    Query(query): Query<HashMap<String, String>>,
) -> std::result::Result<(StatusCode, Json<serde_json::Value>), AppError> {
    if state.is_shutting_down() {
        return Ok((
            StatusCode::SERVICE_UNAVAILABLE,
            Json(serde_json::json!({
                "ok": false,
                "service": "tidaldb",
                "cause": "shutting down"
            })),
        ));
    }

    let region = query.get("region").map(|s| s.as_str());
    let items = state.item_count(region).map_err(AppError)?;

    Ok((
        StatusCode::OK,
        Json(serde_json::json!({
            "status": "ok",
            "mode": "standalone",
            "items": items,
        })),
    ))
}

struct TidalErrorWrapper(tidaldb::TidalError);

impl From<TidalErrorWrapper> for AppError {
    fn from(value: TidalErrorWrapper) -> Self {
        AppError(ServerError::Tidal(value.0))
    }
}

struct AppError(ServerError);

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let status = status_from_error(&self.0);
        let body = serde_json::json!({
            "error": self.0.to_string()
        });
        (status, Json(body)).into_response()
    }
}

fn status_from_error(err: &ServerError) -> StatusCode {
    match err {
        ServerError::BadRequest(_) | ServerError::SchemaConfig(_) => StatusCode::BAD_REQUEST,
        ServerError::Tidal(tidal_err) => match tidal_err {
            tidaldb::TidalError::NotFound { .. } => StatusCode::NOT_FOUND,
            tidaldb::TidalError::Schema(_) | tidaldb::TidalError::InvalidInput(_) => {
                StatusCode::BAD_REQUEST
            }
            tidaldb::TidalError::Backpressure { .. } | tidaldb::TidalError::RateLimited { .. } => {
                StatusCode::TOO_MANY_REQUESTS
            }
            tidaldb::TidalError::PolicyViolation { .. }
            | tidaldb::TidalError::SessionExpired { .. } => StatusCode::FORBIDDEN,
            _ => StatusCode::INTERNAL_SERVER_ERROR,
        },
        _ => StatusCode::INTERNAL_SERVER_ERROR,
    }
}
