use crate::errors::{Error, SummaResult, ValidationError};
use crate::proto;
use tantivy::schema::Schema as Fields;
use tantivy::IndexSortByField;

#[derive(Builder)]
pub struct CreateIndexRequest {
    pub index_name: String,
    pub index_engine: proto::IndexEngine,
    pub fields: Fields,
    #[builder(default = "None")]
    pub autocommit_interval_ms: Option<u64>,
    #[builder(default = "tantivy::store::Compressor::None")]
    pub compression: tantivy::store::Compressor,
    #[builder(default = "Vec::new()")]
    pub default_fields: Vec<String>,
    #[builder(default = "Vec::new()")]
    pub multi_fields: Vec<String>,
    #[builder(default = "None")]
    pub primary_key: Option<String>,
    #[builder(default = "None")]
    pub sort_by_field: Option<IndexSortByField>,
    #[builder(default = "None")]
    pub stop_words: Option<Vec<String>>,
    #[builder(default = "None")]
    pub writer_threads: Option<u64>,
    #[builder(default = "None")]
    pub writer_heap_size_bytes: Option<u64>,
}

impl CreateIndexRequest {
    fn parse_fields(fields: &str) -> SummaResult<Fields> {
        serde_yaml::from_str(fields).map_err(|_| Error::Validation(ValidationError::InvalidFields(fields.to_owned())))
    }

    fn parse_default_fields(fields: &Fields, default_fields: &[String]) -> SummaResult<Vec<String>> {
        Ok(default_fields
            .iter()
            .map(|default_field_name| match fields.get_field(default_field_name) {
                Some(_) => Ok(default_field_name.to_owned()),
                None => Err(ValidationError::MissingDefaultField(default_field_name.to_owned())),
            })
            .collect::<Result<_, _>>()?)
    }

    fn parse_primary_key(fields: &Fields, primary_key: &Option<String>) -> SummaResult<Option<String>> {
        Ok(match primary_key {
            Some(primary_key) => Some(match fields.get_field(primary_key) {
                Some(_) => primary_key.to_owned(),
                None => return Err(ValidationError::MissingPrimaryKey(Some(primary_key.to_owned())).into()),
            }),
            None => None,
        })
    }
}

impl TryFrom<proto::CreateIndexRequest> for CreateIndexRequest {
    type Error = Error;

    fn try_from(proto_request: proto::CreateIndexRequest) -> SummaResult<CreateIndexRequest> {
        let fields = CreateIndexRequest::parse_fields(&proto_request.fields)?;
        let default_fields = CreateIndexRequest::parse_default_fields(&fields, &proto_request.default_fields)?;
        let primary_key = CreateIndexRequest::parse_primary_key(&fields, &proto_request.primary_key)?;
        let compression = proto::Compression::from_i32(proto_request.compression)
            .map(proto::Compression::into)
            .unwrap_or(tantivy::store::Compressor::None);
        let multi_fields = proto_request
            .multi_fields
            .iter()
            .map(|multi_field_name| match fields.get_field(multi_field_name) {
                Some(_) => Ok(multi_field_name.to_owned()),
                None => Err(ValidationError::MissingMultiField(multi_field_name.to_owned())),
            })
            .collect::<Vec<Result<_, _>>>()
            .into_iter()
            .collect::<Result<_, _>>()?;
        Ok(CreateIndexRequestBuilder::default()
            .index_name(proto_request.index_name)
            .index_engine(proto::IndexEngine::from_i32(proto_request.index_engine).unwrap())
            .fields(fields)
            .primary_key(primary_key)
            .compression(compression)
            .default_fields(default_fields)
            .multi_fields(multi_fields)
            .sort_by_field(proto_request.sort_by_field.map(proto::SortByField::into))
            .stop_words(if !proto_request.stop_words.is_empty() {
                Some(proto_request.stop_words)
            } else {
                None
            })
            .autocommit_interval_ms(proto_request.autocommit_interval_ms)
            .writer_threads(proto_request.writer_threads)
            .writer_heap_size_bytes(proto_request.writer_heap_size_bytes)
            .build()
            .unwrap())
    }
}
