//! HTTP router for Database Gateway using Axum.

use crate::document::DocumentId;
use crate::gateway::auth::{ApiKey, AuthError, AuthMiddleware};
use crate::gateway::handlers::*;
use crate::gateway::{ApiResponse, UsageMetrics};
use crate::query::{Query, SortOrder};
use crate::timeseries::DataPoint;
use crate::vector::Embedding;
use crate::{DatabaseConfig, DatabaseManager};

use axum::{
    extract::{Path, State},
    http::{HeaderMap, StatusCode},
    response::IntoResponse,
    routing::{delete, get, post, put},
    Json, Router,
};
use parking_lot::RwLock;
use std::sync::Arc;

/// Application state shared across handlers.
pub struct AppState {
    /// Database manager.
    pub db_manager: Arc<DatabaseManager>,
    /// Auth middleware.
    pub auth: Arc<AuthMiddleware>,
    /// Usage metrics.
    pub metrics: Arc<RwLock<UsageMetrics>>,
}

impl AppState {
    /// Creates new app state.
    pub fn new(db_manager: Arc<DatabaseManager>, auth: Arc<AuthMiddleware>) -> Self {
        Self {
            db_manager,
            auth,
            metrics: Arc::new(RwLock::new(UsageMetrics::default())),
        }
    }

    /// Records a read operation.
    pub fn record_read(&self) {
        self.metrics.write().reads += 1;
    }

    /// Records a write operation.
    pub fn record_write(&self, bytes: u64) {
        let mut m = self.metrics.write();
        m.writes += 1;
        m.storage_bytes += bytes;
    }

    /// Records a vector search.
    pub fn record_vector_search(&self) {
        self.metrics.write().vector_searches += 1;
    }
}

/// Extracts and validates API key from headers.
fn extract_api_key(headers: &HeaderMap, auth: &AuthMiddleware) -> Result<ApiKey, AuthError> {
    let header_name = &auth.config().header_name;
    let key_str = headers
        .get(header_name)
        .and_then(|v| v.to_str().ok())
        .ok_or(AuthError::MissingKey)?;

    auth.validate(key_str)
}

/// Creates the main router.
pub fn create_router(state: Arc<AppState>) -> Router {
    Router::new()
        // Health check
        .route("/health", get(health_check))
        // Key-Value routes
        .route("/kv/:key", get(kv_get))
        .route("/kv/:key", put(kv_set))
        .route("/kv/:key", delete(kv_delete))
        .route("/kv/batch", post(kv_batch))
        // Database management
        .route("/databases", get(list_databases))
        .route("/databases", post(create_database))
        .route("/databases/:db", get(get_database_info))
        .route("/databases/:db", delete(delete_database))
        // Collection routes
        .route("/db/:db/collections", post(create_collection))
        .route("/db/:db/collections/:coll", get(list_documents))
        .route("/db/:db/collections/:coll", delete(drop_collection))
        // Document routes
        .route("/db/:db/:coll", get(query_documents))
        .route("/db/:db/:coll", post(insert_document))
        .route("/db/:db/:coll/many", post(insert_many_documents))
        .route("/db/:db/:coll/:id", get(get_document))
        .route("/db/:db/:coll/:id", put(update_document))
        .route("/db/:db/:coll/:id", delete(delete_document))
        // Vector routes
        .route("/db/:db/vectors", post(insert_embeddings))
        .route("/db/:db/vectors/search", post(vector_search))
        .route("/db/:db/vectors/:id", get(get_embedding))
        .route("/db/:db/vectors/:id", delete(delete_embedding))
        // Time-series routes
        .route("/db/:db/metrics", get(list_metrics))
        .route("/db/:db/metrics/:name", post(record_metric))
        .route("/db/:db/metrics/:name", get(query_metrics))
        // Stats
        .route("/stats", get(get_stats))
        .with_state(state)
}

// ============================================================================
// Health & Stats
// ============================================================================

async fn health_check() -> impl IntoResponse {
    Json(serde_json::json!({
        "status": "healthy",
        "service": "synor-database",
        "version": env!("CARGO_PKG_VERSION")
    }))
}

async fn get_stats(State(state): State<Arc<AppState>>) -> impl IntoResponse {
    let metrics = state.metrics.read().clone();
    Json(ApiResponse::ok(metrics))
}

// ============================================================================
// Key-Value Handlers
// ============================================================================

async fn kv_get(
    State(state): State<Arc<AppState>>,
    _headers: HeaderMap,
    Path(key): Path<String>,
) -> impl IntoResponse {
    // For demo, use a default database
    let db = match get_default_database(&state) {
        Some(db) => db,
        None => return (StatusCode::NOT_FOUND, Json(ApiResponse::<KvGetResponse>::error("No database"))),
    };

    state.record_read();
    let response = handle_kv_get(db.kv(), &key);
    (StatusCode::OK, Json(response))
}

async fn kv_set(
    State(state): State<Arc<AppState>>,
    _headers: HeaderMap,
    Path(key): Path<String>,
    Json(req): Json<KvSetRequest>,
) -> impl IntoResponse {
    let db = match get_default_database(&state) {
        Some(db) => db,
        None => return (StatusCode::NOT_FOUND, Json(ApiResponse::<KvGetResponse>::error("No database"))),
    };

    state.record_write(req.value.len() as u64);
    let response = handle_kv_set(db.kv(), &key, req);
    (StatusCode::OK, Json(response))
}

async fn kv_delete(
    State(state): State<Arc<AppState>>,
    _headers: HeaderMap,
    Path(key): Path<String>,
) -> impl IntoResponse {
    let db = match get_default_database(&state) {
        Some(db) => db,
        None => return (StatusCode::NOT_FOUND, Json(ApiResponse::<bool>::error("No database"))),
    };

    let response = handle_kv_delete(db.kv(), &key);
    (StatusCode::OK, Json(response))
}

async fn kv_batch(
    State(state): State<Arc<AppState>>,
    _headers: HeaderMap,
    Json(req): Json<KvBatchRequest>,
) -> impl IntoResponse {
    let db = match get_default_database(&state) {
        Some(db) => db,
        None => return (StatusCode::NOT_FOUND, Json(ApiResponse::<KvBatchResponse>::error("No database"))),
    };

    let response = handle_kv_batch(db.kv(), req);
    (StatusCode::OK, Json(response))
}

// ============================================================================
// Database Management Handlers
// ============================================================================

async fn list_databases(
    State(state): State<Arc<AppState>>,
    _headers: HeaderMap,
) -> impl IntoResponse {
    // List all databases (would filter by owner in production)
    let owner = [0u8; 32]; // Default owner
    let dbs = state.db_manager.list(&owner);

    let response: Vec<DatabaseInfoResponse> = dbs
        .iter()
        .map(|db| {
            let stats = db.stats();
            DatabaseInfoResponse {
                id: db.id.to_string(),
                name: db.config.name.clone(),
                entry_count: stats.entry_count,
                storage_used: stats.storage_used,
                reads: stats.reads,
                writes: stats.writes,
                vector_searches: stats.vector_searches,
            }
        })
        .collect();

    Json(ApiResponse::ok(ListDatabasesResponse { databases: response }))
}

async fn create_database(
    State(state): State<Arc<AppState>>,
    _headers: HeaderMap,
    Json(req): Json<CreateDatabaseRequest>,
) -> impl IntoResponse {
    let config = DatabaseConfig {
        name: req.name,
        owner: [0u8; 32], // Would come from auth
        vector_dimensions: req.vector_dimensions.unwrap_or(0),
        vector_enabled: req.vector_dimensions.map(|d| d > 0).unwrap_or(false),
        replication: req.replication.unwrap_or(3),
        ..Default::default()
    };

    match state.db_manager.create(config) {
        Ok(db) => {
            let stats = db.stats();
            (
                StatusCode::CREATED,
                Json(ApiResponse::ok(DatabaseInfoResponse {
                    id: db.id.to_string(),
                    name: db.config.name.clone(),
                    entry_count: stats.entry_count,
                    storage_used: stats.storage_used,
                    reads: stats.reads,
                    writes: stats.writes,
                    vector_searches: stats.vector_searches,
                })),
            )
        }
        Err(e) => (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))),
    }
}

async fn get_database_info(
    State(state): State<Arc<AppState>>,
    Path(db_name): Path<String>,
) -> impl IntoResponse {
    let owner = [0u8; 32];
    match state.db_manager.get_by_name(&owner, &db_name) {
        Some(db) => {
            let stats = db.stats();
            Json(ApiResponse::ok(DatabaseInfoResponse {
                id: db.id.to_string(),
                name: db.config.name.clone(),
                entry_count: stats.entry_count,
                storage_used: stats.storage_used,
                reads: stats.reads,
                writes: stats.writes,
                vector_searches: stats.vector_searches,
            }))
        }
        None => Json(ApiResponse::error("Database not found")),
    }
}

async fn delete_database(
    State(state): State<Arc<AppState>>,
    Path(db_name): Path<String>,
) -> impl IntoResponse {
    let owner = [0u8; 32];
    if let Some(db) = state.db_manager.get_by_name(&owner, &db_name) {
        match state.db_manager.delete(&db.id) {
            Ok(_) => Json(ApiResponse::ok(true)),
            Err(e) => Json(ApiResponse::error(e.to_string())),
        }
    } else {
        Json(ApiResponse::error("Database not found"))
    }
}

// ============================================================================
// Collection Handlers
// ============================================================================

async fn create_collection(
    State(state): State<Arc<AppState>>,
    Path(db_name): Path<String>,
    Json(req): Json<CreateCollectionRequest>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return (StatusCode::NOT_FOUND, Json(ApiResponse::<bool>::error("Database not found"))),
    };

    match db.documents().create_collection(&req.name) {
        Ok(_) => (StatusCode::CREATED, Json(ApiResponse::ok(true))),
        Err(e) => (StatusCode::BAD_REQUEST, Json(ApiResponse::<bool>::error(e.to_string()))),
    }
}

async fn drop_collection(
    State(state): State<Arc<AppState>>,
    Path((db_name, coll_name)): Path<(String, String)>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return Json(ApiResponse::<bool>::error("Database not found")),
    };

    match db.documents().drop_collection(&coll_name) {
        Ok(dropped) => Json(ApiResponse::ok(dropped)),
        Err(e) => Json(ApiResponse::error(e.to_string())),
    }
}

async fn list_documents(
    State(state): State<Arc<AppState>>,
    Path((db_name, _coll_name)): Path<(String, String)>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return Json(ApiResponse::<Vec<String>>::error("Database not found")),
    };

    Json(ApiResponse::ok(db.documents().list_collections()))
}

// ============================================================================
// Document Handlers
// ============================================================================

async fn query_documents(
    State(state): State<Arc<AppState>>,
    Path((db_name, coll_name)): Path<(String, String)>,
    Json(req): Json<QueryDocumentsRequest>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return (StatusCode::NOT_FOUND, Json(ApiResponse::<QueryDocumentsResponse>::error("Database not found"))),
    };

    state.record_read();

    // Build query
    let mut query = Query::new(&coll_name)
        .skip(req.skip)
        .limit(req.limit);

    // Add filter
    if let Some(filter_json) = &req.filter {
        if let Some(filter) = json_to_filter(filter_json) {
            query = query.filter(filter);
        }
    }

    // Add sorting
    if let Some(sorts) = &req.sort {
        for sort in sorts {
            let order = if sort.order == "desc" {
                SortOrder::Descending
            } else {
                SortOrder::Ascending
            };
            query = query.sort(&sort.field, order);
        }
    }

    // Add field selection
    if let Some(fields) = req.select {
        query = query.select(fields);
    }

    match db.query().execute(&query) {
        Ok(result) => (StatusCode::OK, Json(ApiResponse::ok(QueryDocumentsResponse::from(result)))),
        Err(e) => (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))),
    }
}

async fn insert_document(
    State(state): State<Arc<AppState>>,
    Path((db_name, coll_name)): Path<(String, String)>,
    Json(req): Json<InsertDocumentRequest>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return (StatusCode::NOT_FOUND, Json(ApiResponse::<String>::error("Database not found"))),
    };

    let size = serde_json::to_vec(&req.document).map(|v| v.len()).unwrap_or(0);
    state.record_write(size as u64);

    match db.documents().insert(&coll_name, req.document) {
        Ok(id) => (StatusCode::CREATED, Json(ApiResponse::ok(id.to_hex()))),
        Err(e) => (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))),
    }
}

async fn insert_many_documents(
    State(state): State<Arc<AppState>>,
    Path((db_name, coll_name)): Path<(String, String)>,
    Json(req): Json<InsertManyRequest>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return (StatusCode::NOT_FOUND, Json(ApiResponse::<Vec<String>>::error("Database not found"))),
    };

    let mut ids = Vec::with_capacity(req.documents.len());
    let mut total_size = 0u64;

    for doc in req.documents {
        total_size += serde_json::to_vec(&doc).map(|v| v.len()).unwrap_or(0) as u64;
        match db.documents().insert(&coll_name, doc) {
            Ok(id) => ids.push(id.to_hex()),
            Err(e) => return (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))),
        }
    }

    state.record_write(total_size);
    (StatusCode::CREATED, Json(ApiResponse::ok(ids)))
}

async fn get_document(
    State(state): State<Arc<AppState>>,
    Path((db_name, coll_name, doc_id)): Path<(String, String, String)>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return Json(ApiResponse::<DocumentResponse>::error("Database not found")),
    };

    state.record_read();

    let id = match DocumentId::from_hex(&doc_id) {
        Ok(id) => id,
        Err(e) => return Json(ApiResponse::error(e.to_string())),
    };

    match db.documents().find_by_id(&coll_name, &id) {
        Ok(Some(doc)) => Json(ApiResponse::ok(DocumentResponse {
            id: doc.id.to_hex(),
            data: doc.data,
            created_at: doc.created_at,
            updated_at: doc.updated_at,
            version: doc.version,
        })),
        Ok(None) => Json(ApiResponse::error("Document not found")),
        Err(e) => Json(ApiResponse::error(e.to_string())),
    }
}

async fn update_document(
    State(state): State<Arc<AppState>>,
    Path((db_name, coll_name, doc_id)): Path<(String, String, String)>,
    Json(req): Json<UpdateDocumentRequest>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return Json(ApiResponse::<bool>::error("Database not found")),
    };

    let id = match DocumentId::from_hex(&doc_id) {
        Ok(id) => id,
        Err(e) => return Json(ApiResponse::error(e.to_string())),
    };

    let update_size = serde_json::to_vec(&req.update).map(|v| v.len()).unwrap_or(0);
    state.record_write(update_size as u64);

    match db.documents().update_by_id(&coll_name, &id, req.update) {
        Ok(true) => Json(ApiResponse::ok(true)),
        Ok(false) => Json(ApiResponse::error("Document not found")),
        Err(e) => Json(ApiResponse::error(e.to_string())),
    }
}

async fn delete_document(
    State(state): State<Arc<AppState>>,
    Path((db_name, coll_name, doc_id)): Path<(String, String, String)>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return Json(ApiResponse::<bool>::error("Database not found")),
    };

    let id = match DocumentId::from_hex(&doc_id) {
        Ok(id) => id,
        Err(e) => return Json(ApiResponse::error(e.to_string())),
    };

    match db.documents().delete_by_id(&coll_name, &id) {
        Ok(true) => Json(ApiResponse::ok(true)),
        Ok(false) => Json(ApiResponse::error("Document not found")),
        Err(e) => Json(ApiResponse::error(e.to_string())),
    }
}

// ============================================================================
// Vector Handlers
// ============================================================================

async fn insert_embeddings(
    State(state): State<Arc<AppState>>,
    Path(db_name): Path<String>,
    Json(req): Json<InsertEmbeddingsRequest>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return (StatusCode::NOT_FOUND, Json(ApiResponse::<usize>::error("Database not found"))),
    };

    let mut count = 0;
    for input in req.embeddings {
        let mut embedding = Embedding::new(&input.id, input.vector);
        if let Some(meta) = input.metadata {
            embedding = embedding.with_metadata(meta);
        }
        if let Some(ns) = input.namespace {
            embedding = embedding.with_namespace(ns);
        }

        if let Err(e) = db.vectors().insert(embedding) {
            return (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string())));
        }
        count += 1;
    }

    state.record_write(count as u64 * 4 * db.config.vector_dimensions as u64);
    (StatusCode::CREATED, Json(ApiResponse::ok(count)))
}

async fn vector_search(
    State(state): State<Arc<AppState>>,
    Path(db_name): Path<String>,
    Json(req): Json<VectorSearchRequest>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return (StatusCode::NOT_FOUND, Json(ApiResponse::<VectorSearchResponse>::error("Database not found"))),
    };

    state.record_vector_search();

    match db.vectors().search(
        &req.vector,
        req.limit,
        req.namespace.as_deref(),
        req.threshold,
    ) {
        Ok(results) => {
            let count = results.len();
            let matches: Vec<VectorMatch> = results.into_iter().map(Into::into).collect();
            (StatusCode::OK, Json(ApiResponse::ok(VectorSearchResponse { results: matches, count })))
        }
        Err(e) => (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))),
    }
}

async fn get_embedding(
    State(state): State<Arc<AppState>>,
    Path((db_name, id)): Path<(String, String)>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return Json(ApiResponse::<VectorMatch>::error("Database not found")),
    };

    state.record_read();

    match db.vectors().get(&id) {
        Some(embedding) => Json(ApiResponse::ok(VectorMatch {
            id: embedding.id,
            score: 1.0,
            metadata: embedding.metadata,
        })),
        None => Json(ApiResponse::error("Embedding not found")),
    }
}

async fn delete_embedding(
    State(state): State<Arc<AppState>>,
    Path((db_name, id)): Path<(String, String)>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return Json(ApiResponse::<bool>::error("Database not found")),
    };

    match db.vectors().delete(&id) {
        Ok(deleted) => Json(ApiResponse::ok(deleted)),
        Err(e) => Json(ApiResponse::error(e.to_string())),
    }
}

// ============================================================================
// Time-Series Handlers
// ============================================================================

async fn list_metrics(
    State(state): State<Arc<AppState>>,
    Path(db_name): Path<String>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return Json(ApiResponse::<Vec<String>>::error("Database not found")),
    };

    Json(ApiResponse::ok(db.timeseries().list_metrics()))
}

async fn record_metric(
    State(state): State<Arc<AppState>>,
    Path((db_name, metric_name)): Path<(String, String)>,
    Json(req): Json<RecordMetricRequest>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return Json(ApiResponse::<bool>::error("Database not found")),
    };

    state.record_write(16); // timestamp + value

    let point = if let Some(ts) = req.timestamp {
        DataPoint::new(ts, req.value)
    } else {
        DataPoint::now(req.value)
    };

    match db.timeseries().record_point(&metric_name, point) {
        Ok(_) => Json(ApiResponse::ok(true)),
        Err(e) => Json(ApiResponse::error(e.to_string())),
    }
}

async fn query_metrics(
    State(state): State<Arc<AppState>>,
    Path((db_name, metric_name)): Path<(String, String)>,
    Json(req): Json<QueryMetricsRequest>,
) -> impl IntoResponse {
    let db = match get_database(&state, &db_name) {
        Some(db) => db,
        None => return Json(ApiResponse::<MetricsResponse>::error("Database not found")),
    };

    state.record_read();

    match db.timeseries().query(&metric_name, req.start, req.end) {
        Ok(points) => {
            let data_points: Vec<DataPointResponse> = points.into_iter().map(Into::into).collect();
            Json(ApiResponse::ok(MetricsResponse {
                name: metric_name,
                points: data_points,
                aggregation: None,
            }))
        }
        Err(e) => Json(ApiResponse::error(e.to_string())),
    }
}

// ============================================================================
// Helper Functions
// ============================================================================

fn get_default_database(state: &AppState) -> Option<Arc<crate::Database>> {
    let owner = [0u8; 32];
    state.db_manager.get_by_name(&owner, "default")
}

fn get_database(state: &AppState, name: &str) -> Option<Arc<crate::Database>> {
    let owner = [0u8; 32];
    state.db_manager.get_by_name(&owner, name)
}
