pub(crate) mod actions;

use crate::collection_mutexes::{Action, CollectionMutexe, CollectionMutexeDbImpl};
use crate::error::{validate_collection_name, QdrantError, Result};
use crate::metrics;
use crate::protocol::{
    index::CreateFieldIndex, ApiResponse, CollectionDetail, CollectionExists, CollectionResponse,
    CollectionsApiQueryParams, CreateCollection, UpdateResult,
};
use crate::server::ExtractApiKey;
use crate::state::ApiState;
use axum::extract::{Path, Query, State};
use axum::Json;
use lazy_static::lazy_static;
use log::*;
use moka::sync::Cache;
use std::time::{SystemTime, UNIX_EPOCH};

lazy_static! {
    static ref TENANT_TOKENS_VALIDATED_CACHE: Cache<String, u64> = Cache::new(1000);
}

const VALIDATION_TTL: u64 = 1800;

async fn valid_api_key(ctx: &context::Context, state: ApiState, api_key: &str) -> Result<()> {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs();
    if let Some(validated) = TENANT_TOKENS_VALIDATED_CACHE.get(api_key) {
        if now - validated <= VALIDATION_TTL {
            ctx.push_phase("valid_api_key_from_cache", None);
            return Ok(());
        }
    }

    let _ = state.get_tenant_db(ctx, api_key).await?;
    TENANT_TOKENS_VALIDATED_CACHE.insert(api_key.into(), now);
    ctx.push_phase("valid_api_key", None);

    Ok(())
}

pub(crate) async fn get_collections(
    State(state): State<ApiState>,
    ExtractApiKey(api_key): ExtractApiKey,
    Query(_collections_api_query_params): Query<CollectionsApiQueryParams>,
) -> Result<Json<ApiResponse<CollectionResponse>>> {
    let ctx = context::with_metrics(metrics::INTERFACE_PERFORMANCE_HISTOGRAM.clone());
    valid_api_key(&ctx, state.clone(), &api_key).await?;

    metrics::ROUTER_COLLECTION_WITH_PATH
        .with_label_values(&["", "/", "get"])
        .inc();

    let (meta_db, tenant_token) = state.get_meta_db_and_token(&ctx, &api_key).await?;

    info!(
        "[{}] GET /collections with token[{}] with: query[ {:?} ]",
        ctx.id(),
        tenant_token,
        &_collections_api_query_params
    );

    let collections = actions::get_collections(ctx.clone(), meta_db, tenant_token).await?;

    metrics::ROUTER_COLLECTION_WITH_PATH_HISTOGRAM
        .with_label_values(&["", "/", "get"])
        .observe(metrics::since(&ctx.started()));

    Ok(Json(ApiResponse::ok(
        ctx.started(),
        CollectionResponse::new(collections),
    )))
}

pub(crate) async fn get_collection(
    State(state): State<ApiState>,
    ExtractApiKey(api_key): ExtractApiKey,
    Path(collection_name): Path<String>,
) -> Result<Json<ApiResponse<CollectionDetail>>> {
    let ctx = context::with_metrics(metrics::INTERFACE_PERFORMANCE_HISTOGRAM.clone());
    info!(
        "[{}] GET /collections/:collection_name with: collection_name:[ {} ]",
        ctx.id(),
        &collection_name
    );
    let collection_name = validate_collection_name(collection_name)?;
    metrics::ROUTER_COLLECTION_WITH_PATH
        .with_label_values(&[&collection_name, "/", "get"])
        .inc();

    let (meta_db, tenant_db, tenant_token) = state.get_meta_and_tenant_db(&ctx, &api_key).await?;
    let meta_db = meta_db.ok_or(QdrantError::InternalServerErrorWithMsg(
        "meta-db not configured".to_string(),
    ))?;
    let detail = match actions::get_collection(
        ctx.clone(),
        meta_db,
        tenant_db,
        tenant_token,
        &collection_name,
    )
    .await?
    {
        Some(detail) => detail,
        None => return Err(QdrantError::CollectionNotFound),
    };

    metrics::ROUTER_COLLECTION_WITH_PATH_HISTOGRAM
        .with_label_values(&[&collection_name, "/", "get"])
        .observe(metrics::since(&ctx.started()));

    Ok(Json(ApiResponse::ok(ctx.started(), detail)))
}

pub(crate) async fn does_collection_exist(
    State(state): State<ApiState>,
    ExtractApiKey(api_key): ExtractApiKey,
    Path(collection_name): Path<String>,
) -> Result<Json<ApiResponse<CollectionExists>>> {
    let ctx = context::with_metrics(metrics::INTERFACE_PERFORMANCE_HISTOGRAM.clone());
    info!(
        "[{}] GET /collections/:collection_name/exists with: collection_name:[ {} ]",
        ctx.id(),
        &collection_name
    );
    metrics::ROUTER_COLLECTION_WITH_PATH
        .with_label_values(&[&collection_name, "/exists", "get"])
        .inc();

    let (tenant_db, _) = state.get_tenant_db(&ctx, &api_key).await?;
    let exists: CollectionExists =
        actions::does_collection_exist(ctx.clone(), tenant_db, &collection_name).await?;

    metrics::ROUTER_COLLECTION_WITH_PATH_HISTOGRAM
        .with_label_values(&[&collection_name, "/exists", "get"])
        .observe(metrics::since(&ctx.started()));

    Ok(Json(ApiResponse::ok(ctx.started(), exists)))
}

pub(crate) async fn delete_collection(
    State(state): State<ApiState>,
    ExtractApiKey(api_key): ExtractApiKey,
    Path(collection_name): Path<String>,
    Query(_collections_api_query_params): Query<CollectionsApiQueryParams>,
) -> Result<Json<ApiResponse<bool>>> {
    let ctx = context::with_metrics(metrics::INTERFACE_PERFORMANCE_HISTOGRAM.clone());
    info!(
        "[{}] DELETE /collections/:collection_name with: collection_name:[ {} ]",
        ctx.id(),
        &collection_name
    );
    let collection_name = validate_collection_name(collection_name)?;
    metrics::ROUTER_COLLECTION_WITH_PATH
        .with_label_values(&[&collection_name, "/", "delete"])
        .inc();

    let (meta_db, tenant_db, tenant_token) = state.get_meta_and_tenant_db(&ctx, &api_key).await?;
    let meta_db = meta_db.ok_or(QdrantError::InternalServerErrorWithMsg(
        "meta-db not configured".to_string(),
    ))?;
    // Check if collection creating
    if CollectionMutexeDbImpl::new(
        meta_db.clone(),
        tenant_token.into(),
        collection_name.clone(),
        Action::Create,
        std::time::Duration::from_secs(60),
    )
    .is_locked()
    .await
    .map_err(|e| e.ctx(ctx.clone()))?
    {
        return Err(QdrantError::ConflictWithMessage(format!(
            "[{}] There is a collection creation in progress",
            ctx.id()
        )));
    };

    let _ = actions::delete_collection(
        ctx.clone(),
        meta_db,
        tenant_db,
        tenant_token,
        &collection_name,
    )
    .await?;

    metrics::ROUTER_COLLECTION_WITH_PATH_HISTOGRAM
        .with_label_values(&[&collection_name, "/", "delete"])
        .observe(metrics::since(&ctx.started()));

    Ok(Json(ApiResponse::ok(ctx.started(), true)))
}

pub(crate) async fn create_collection(
    State(state): State<ApiState>,
    ExtractApiKey(api_key): ExtractApiKey,
    Path(collection_name): Path<String>,
    Query(_collections_api_query_params): Query<CollectionsApiQueryParams>,
    Json(create_collection): Json<CreateCollection>,
) -> Result<Json<ApiResponse<bool>>> {
    let ctx = context::with_metrics(metrics::INTERFACE_PERFORMANCE_HISTOGRAM.clone());
    info!("[{}] PUT /collections/:collection_name with: collection_name:[ {} ] create_collection:[ {:?} ]", ctx.id(), &collection_name, &create_collection);
    let collection_name = validate_collection_name(collection_name)?;
    metrics::ROUTER_COLLECTION_WITH_PATH
        .with_label_values(&[&collection_name, "/", "put"])
        .inc();

    let (meta_db, tenant_db, tenant_token) = state.get_meta_and_tenant_db(&ctx, &api_key).await?;
    let meta_db = meta_db.ok_or(QdrantError::InternalServerErrorWithMsg(
        "meta-db not configured".to_string(),
    ))?;
    // Check if collection creating
    let mut mutex = CollectionMutexeDbImpl::new(
        meta_db.clone(),
        tenant_token.into(),
        collection_name.clone(),
        Action::Create,
        std::time::Duration::from_secs(60),
    );
    mutex.lock().await.map_err(|e| e.ctx(ctx.clone()))?;

    let _ = actions::create_collection(
        ctx.clone(),
        meta_db,
        tenant_db,
        tenant_token,
        &collection_name,
        create_collection,
    )
    .await?;

    metrics::ROUTER_COLLECTION_WITH_PATH_HISTOGRAM
        .with_label_values(&[&collection_name, "/", "put"])
        .observe(metrics::since(&ctx.started()));

    mutex.unlock().await.map_err(|e| e.ctx(ctx.clone()))?;
    Ok(Json(ApiResponse::ok(ctx.started(), true)))
}

pub(crate) async fn create_field_index(
    State(state): State<ApiState>,
    ExtractApiKey(api_key): ExtractApiKey,
    Path(collection_name): Path<String>,
    Json(create_field_index): Json<CreateFieldIndex>,
) -> Result<Json<ApiResponse<UpdateResult>>> {
    let ctx = context::with_metrics(metrics::INTERFACE_PERFORMANCE_HISTOGRAM.clone());
    info!("[{}] PUT /collections/:collection_name/index with: collection_name:[ {} ] create_field_index:[ {:?} ]", ctx.id(), &collection_name, &create_field_index);
    metrics::ROUTER_COLLECTION_WITH_PATH
        .with_label_values(&[&collection_name, "/index", "put"])
        .inc();

    let (tenant_db, _) = state.get_tenant_db(&ctx, &api_key).await?;
    let status =
        actions::create_field_index(ctx.clone(), tenant_db, &collection_name, create_field_index)
            .await?;

    Ok(Json(ApiResponse::ok(
        ctx.started(),
        UpdateResult {
            operation_id: Some(0),
            status,
        },
    )))
}

pub(crate) async fn delete_field_index(
    State(state): State<ApiState>,
    ExtractApiKey(api_key): ExtractApiKey,
    Path((collection_name, field_name)): Path<(String, String)>,
) -> Result<Json<ApiResponse<UpdateResult>>> {
    let ctx = context::with_metrics(metrics::INTERFACE_PERFORMANCE_HISTOGRAM.clone());
    info!("[{}] DELETE /collections/:collection_name/index/:field_name with: collection_name:[ {} ] field_name:[ {} ]", ctx.id(), &collection_name, &field_name);
    metrics::ROUTER_COLLECTION_WITH_PATH
        .with_label_values(&[&collection_name, "/index/field_name", "delete"])
        .inc();

    let (tenant_db, _) = state.get_tenant_db(&ctx, &api_key).await?;
    let status =
        actions::delete_field_index(ctx.clone(), tenant_db, &collection_name, &field_name).await?;

    Ok(Json(ApiResponse::ok(
        ctx.started(),
        UpdateResult {
            operation_id: Some(0),
            status,
        },
    )))
}

pub(crate) async fn update_collection(
    State(_state): State<ApiState>,
    ExtractApiKey(_api_key): ExtractApiKey,
    Path(collection_name): Path<String>,
    Query(_collections_api_query_params): Query<CollectionsApiQueryParams>,
    Json(create_collection): Json<CreateCollection>,
) -> Result<Json<ApiResponse<bool>>> {
    let ctx = context::with_metrics(metrics::INTERFACE_PERFORMANCE_HISTOGRAM.clone());
    info!("[{}] PATCH /collections/:collection_name with: collection_name:[ {} ] create_collection:[ {:?} ]", ctx.id(), &collection_name, &create_collection);
    let _ = validate_collection_name(collection_name)?;

    // no ops on TiDB

    Ok(Json(ApiResponse::ok(ctx.started(), true)))
}

pub(crate) async fn facet(
    State(_state): State<ApiState>,
    ExtractApiKey(_api_key): ExtractApiKey,
    Path(collection_name): Path<String>,
) -> Result<Json<ApiResponse<bool>>> {
    let ctx = context::with_metrics(metrics::INTERFACE_PERFORMANCE_HISTOGRAM.clone());
    info!(
        "[{}] POST /collections/:collection_name/facet with: collection_name:[ {} ]",
        ctx.id(),
        &collection_name
    );
    let _ = validate_collection_name(collection_name)?;

    Err(QdrantError::NotImplemented)
}
