use std::collections::HashMap;

use crate::error::Result;
use crate::metrics;
use crate::state::ApiState;
use anyhow::anyhow;
use axum::extract::{Path, State};
use axum::Json;
use fts_index::protocol::{
    ApiResponse, BooleanQuery, BooleanQueryOccur, Field, IndexDescription, Query, SearchRequest,
    SearchResponse, TermQuery, UpdateIndexRequest,
};
use fts_index::storage::meta::StorageMeta;
use log::info;
use tokio::time::Instant;

pub(crate) async fn get_indexes(
    State(state): State<ApiState>,
) -> Result<Json<ApiResponse<Vec<IndexDescription>>>> {
    let instant = Instant::now();

    let storage = if let Some(storage) = &state.storage {
        storage
    } else {
        return Err(anyhow!("Server not start with cloud storage").into());
    };

    let versions =
        fts_index::storage::meta::StorageMeta::get_cloud_versions(storage.clone()).await?;
    let mut index_descriptions = Vec::new();
    for version in versions {
        let meta = fts_index::storage::meta::StorageMeta::load_from_cloud_by_version(
            storage.clone(),
            version,
        )
        .await?;
        index_descriptions.push(meta);
    }

    let mut result = Vec::new();
    for meta in index_descriptions {
        let mut indexes = HashMap::new();
        for (index_name, _) in meta.indexes.clone() {
            indexes.insert(
                index_name.clone(),
                (
                    meta.first_start_version(&index_name),
                    meta.latest_end_version(&index_name),
                ),
            );
        }
        result.push(IndexDescription {
            version: meta.version,
            indexes,
            current: meta.version == state.searcher.version().await,
        });
    }
    metrics::ROUTER_INDEX_WITH_PATH_HISTOGRAM
        .with_label_values(&["", "/indexes", "get"])
        .observe(metrics::since(&instant));
    Ok(Json(ApiResponse::new(instant, result)))
}

const DEFAULT_FTS_RECALL_SIZE: u32 = 1000;
pub(crate) async fn search_index(
    State(state): State<ApiState>,
    Path(index_name): Path<String>,
    Json(search): Json<SearchRequest>,
) -> Result<Json<ApiResponse<SearchResponse>>> {
    let instant = Instant::now();
    let real_query = Query::Boolean(BooleanQuery {
        occur: BooleanQueryOccur::Must,
        queries: vec![
            Query::Term(TermQuery::string(
                Field::TenantToken,
                search.tenant_token.clone(),
            )),
            Query::Term(TermQuery::u64(Field::CollectionId, search.collection_id)),
            search.query,
        ],
    });

    let result = state
        .searcher
        .search(
            &index_name,
            &real_query,
            search.limit.unwrap_or(DEFAULT_FTS_RECALL_SIZE),
        )
        .await?;

    metrics::ROUTER_INDEX_WITH_PATH_HISTOGRAM
        .with_label_values(&[&index_name, "/indexes/search", "post"])
        .observe(metrics::since(&instant));
    Ok(Json(ApiResponse::new(
        instant,
        SearchResponse {
            hits: Some(result.0),
            index_version: result.1,
        },
    )))
}

pub(crate) async fn index_version(
    State(state): State<ApiState>,
    Path(index_name): Path<String>,
) -> Result<Json<ApiResponse<(u64, u64)>>> {
    let instant = Instant::now();
    let version = state.searcher.get_versions(&index_name).await?;

    metrics::ROUTER_INDEX_WITH_PATH_HISTOGRAM
        .with_label_values(&[&index_name, "/indexes/version", "get"])
        .observe(metrics::since(&instant));

    Ok(Json(ApiResponse::new(instant, version)))
}

pub(crate) async fn update_index(
    State(state): State<ApiState>,
    Json(update): Json<UpdateIndexRequest>,
) -> Result<Json<ApiResponse<u64>>> {
    let instant = Instant::now();
    let storage = if let Some(storage) = state.storage.clone() {
        storage
    } else {
        return Err(anyhow!("Server not start with cloud storage").into());
    };

    let index_version = match update {
        UpdateIndexRequest::Update { version } => {
            StorageMeta::activate(storage.clone(), version).await?;
            info!(
                "Activated index to version: {}, current: {}",
                version,
                state.searcher.version().await
            );
            version
        }
        UpdateIndexRequest::Reload => state.searcher.version().await,
    };

    metrics::ROUTER_INDEX_WITH_PATH_HISTOGRAM
        .with_label_values(&["", "/indexes", "post"])
        .observe(metrics::since(&instant));

    Ok(Json(ApiResponse::new(Instant::now(), index_version)))
}
