setup:
  - requires:
      cluster_features: [ "rrf_retriever.multi_fields_query_format_support" ]
      reason: "RRF retriever multi-fields query format support"
      test_runner_features: [ "contains" ]

  - do:
      inference.put:
        task_type: sparse_embedding
        inference_id: sparse-inference-id
        body: >
          {
            "service": "test_service",
            "service_settings": {
              "model": "my_model",
              "api_key": "abc64"
            },
            "task_settings": {
            }
          }

  - do:
      inference.put:
        task_type: text_embedding
        inference_id: dense-inference-id
        body: >
          {
            "service": "text_embedding_test_service",
            "service_settings": {
              "model": "my_model",
              "dimensions": 128,
              "similarity": "cosine",
              "api_key": "abc64"
            },
            "task_settings": {
            }
          }
  - do:
      inference.put:
        task_type: sparse_embedding
        inference_id: sparse-inference-another-id
        body: >
          {
            "service": "test_service",
            "service_settings": {
              "model": "my_model",
              "api_key": "abc64"
            },
            "task_settings": {
            }
          }
  - do:
      inference.put:
        task_type: text_embedding
        inference_id: dense-inference-another-id
        body: >
          {
            "service": "text_embedding_test_service",
            "service_settings": {
              "model": "my_model",
              "dimensions": 128,
              "similarity": "cosine",
              "api_key": "xyz"
            },
            "task_settings": {
            }
          }

  - do:
      indices.create:
        index: test-index
        body:
          mappings:
            properties:
              keyword:
                type: keyword
              dense_inference:
                type: semantic_text
                inference_id: dense-inference-id
              sparse_inference:
                type: semantic_text
                inference_id: sparse-inference-id
              text_1:
                type: text
              text_2:
                type: text
              timestamp:
                type: date
              dense_vector:
                type: dense_vector
                dims: 1
                index: true
                similarity: l2_norm
                index_options:
                  type: flat
              sparse_vector:
                type: sparse_vector

  - do:
      bulk:
        index: test-index
        refresh: true
        body: |
          {"index": {"_id": "1"}}
          {
            "keyword": "keyword match 1",
            "dense_inference": "you know",
            "sparse_inference": "for testing",
            "text_1": "foo match 1",
            "text_2": "x match 2",
            "timestamp": "2000-03-30",
            "dense_vector": [1],
            "sparse_vector": {
              "foo": 1.0
            }
          }
          {"index": {"_id": "2"}}
          {
            "keyword": "keyword match 2",
            "dense_inference": "ElasticSearch is an open source",
            "sparse_inference": "distributed, RESTful, search engine",
            "text_1": "bar match 3",
            "text_2": "y match 4",
            "timestamp": "2010-02-08",
            "dense_vector": [2],
            "sparse_vector": {
              "bar": 1.0
            }
          }
          {"index": {"_id": "3"}}
          {
            "keyword": "keyword match 3",
            "dense_inference": "which is built on top of Lucene internally",
            "sparse_inference": "and enjoys all the features it provides",
            "text_1": "baz match 5",
            "text_2": "z match 6",
            "timestamp": "2024-08-08",
            "dense_vector": [3],
            "sparse_vector": {
              "baz": 1.0
            }
          }
  - do:
      indices.create:
        index: test-another-index
        body:
          mappings:
            properties:
              keyword:
                type: keyword
              dense_inference:
                type: semantic_text
                inference_id: dense-inference-id
              sparse_inference:
                type: semantic_text
                inference_id: sparse-inference-another-id
              text_1:
                type: semantic_text
                inference_id: dense-inference-another-id
              text_2:
                type: text
              sparse_vector:
                type: sparse_vector

  - do:
      bulk:
        index: test-another-index
        refresh: true
        body: |
          {"index": {"_id": "4"}}
          {
            "keyword": "keyword match 1",
            "dense_inference": "You know",
            "sparse_inference": "For Search",
            "text_1": "Be excellent to each other",
            "text_2": "xyz match match 2",
            "sparse_vector": {
              "foo": 1.0
            }
          }
          {"index": {"_id": "5"}}
          {
            "keyword": "keyword match 2",
            "dense_inference": "Elasticsearch is simply the best",
            "sparse_inference": "Better than all the rest",
            "text_1": "Better than another vector database",
            "text_2": "yy match match 4",
            "sparse_vector": {
              "bar": 1.0
            }
          }
          {"index": {"_id": "6"}}
          {
            "keyword": "keyword match 3",
            "dense_inference": "Elasticsearch is the best vector database",
            "sparse_inference": "Live long and prosper",
            "text_1": "Most excellent",
            "text_2": "z match match 6",
            "sparse_vector": {
              "baz": 1.0
            }
          }

---
"Query all fields using the simplified format":
  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              query: "match"

  - match: { hits.total.value: 3 }
  - length: { hits.hits: 3 }
  - match: { hits.hits.0._id: "2" }
  - match: { hits.hits.1._id: "1" }
  - match: { hits.hits.2._id: "3" }

---
"Lexical match per-field boosting using the simplified format":
  - requires:
      cluster_features: ["rrf_retriever.simplified_weighted_support"]
      reason: "Simplified weighted fields syntax support"

  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "text_1", "text_2" ]
              query: "foo 1 z"

  # Lexical-only match without weights
  - match: { hits.total.value: 2 }
  - length: { hits.hits: 2 }
  - match: { hits.hits.0._id: "1" }
  - match: { hits.hits.1._id: "3" }

  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: ["text_1", "text_2^3"]
              query: "foo 1 z"

  # Lexical-only match with text_2^3 weighting - doc "3" should rank higher due to text_2 boost
  - match: { hits.total.value: 2 }
  - length: { hits.hits: 2 }
  - match: { hits.hits.0._id: "3" }
  - match: { hits.hits.1._id: "1" }

---
"Semantic match per-field boosting using the simplified format":
  - requires:
      cluster_features: ["rrf_retriever.simplified_weighted_support"]
      reason: "Simplified weighted fields syntax support"

  # The mock inference services generate synthetic vectors that don't accurately represent similarity to non-identical
  # input, so it's hard to create a test that produces intuitive results. Instead, we rely on the fact that the inference
  # services generate consistent vectors (i.e. same input -> same output) to demonstrate that per-field boosting on
  # a semantic_text field can change the result order.
  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "dense_inference", "sparse_inference" ]
              query: "distributed, RESTful, search engine"

  - match: { hits.total.value: 3 }
  - length: { hits.hits: 3 }
  - match: { hits.hits.0._id: "2" }
  - match: { hits.hits.1._id: "3" }
  - match: { hits.hits.2._id: "1" }

  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "dense_inference^3", "sparse_inference" ]
              query: "distributed, RESTful, search engine"

  - match: { hits.total.value: 3 }
  - length: { hits.hits: 3 }
  - match: { hits.hits.0._id: "3" }
  - match: { hits.hits.1._id: "2" }
  - match: { hits.hits.2._id: "1" }

---
"Negative weight validation":
  - requires:
      cluster_features: ["rrf_retriever.simplified_weighted_support"]
      reason: "Simplified weighted fields syntax support"

  - do:
      catch: bad_request
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: ["text_1^-1"]
              query: "foo"

  - match: { error.root_cause.0.reason: "[rrf] per-field weights must be non-negative" }

---
"Can query text fields":
  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "text_1", "text_2" ]
              query: "foo z"

  - match: { hits.total.value: 2 }
  - length: { hits.hits: 2 }
  - match: { hits.hits.0._id: "1" }
  - match: { hits.hits.1._id: "3" }

---
"Can query semantic text fields":
  # Result order is not correlated with relevance because we generate synthetic embeddings
  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "sparse_inference", "dense_inference" ]
              query: "elasticsearch"

  - match: { hits.total.value: 3 }
  - length: { hits.hits: 3 }
  - match: { hits.hits.0._id: "2" }
  - match: { hits.hits.1._id: "3" }
  - match: { hits.hits.2._id: "1" }

---
"Can query keyword fields":
  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "keyword" ]
              query: "keyword match 1"

  - match: { hits.total.value: 1 }
  - length: { hits.hits: 1 }
  - match: { hits.hits.0._id: "1" }

---
"Can query date fields":
  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "timestamp" ]
              query: "2010-02-08"

  - match: { hits.total.value: 1 }
  - length: { hits.hits: 1 }
  - match: { hits.hits.0._id: "2" }

---
"Can query sparse vector fields":
  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "sparse_vector" ]
              query: "foo"

  - match: { hits.total.value: 1 }
  - length: { hits.hits: 1 }
  - match: { hits.hits.0._id: "1" }

---
"Cannot query dense vector fields":
  - do:
      catch: bad_request
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "dense_vector" ]
              query: "foo"

  - contains: { error.root_cause.0.reason: "[rrf] search failed - retrievers '[standard]' returned errors" }
  - contains: { error.root_cause.0.suppressed.0.failed_shards.0.reason.reason: "Field [dense_vector] of type [dense_vector] does not support match queries" }

---
"Filters are propagated":
  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              query: "match"
              filter:
                - term:
                    keyword: "keyword match 1"

  - match: { hits.total.value: 1 }
  - length: { hits.hits: 1 }
  - match: { hits.hits.0._id: "1" }

---
"Wildcard index patterns that do not resolve to any index are handled gracefully":
  - do:
      search:
        index: wildcard-*
        body:
          retriever:
            rrf:
              query: "match"

  - match: { hits.total.value: 0 }
  - length: { hits.hits: 0 }

---
"Wildcard field patterns that do not resolve to any field are handled gracefully":
  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "wildcard-*" ]
              query: "match"

  - match: { hits.total.value: 0 }
  - length: { hits.hits: 0 }

---
"Cannot mix simplified query format with custom sub-retrievers":
  - do:
      catch: bad_request
      search:
        index: test-index
        body:
          retriever:
            rrf:
              query: "foo"
              retrievers:
                - standard:
                    query:
                      match:
                        keyword: "bar"

  - contains: { error.root_cause.0.reason: "[rrf] cannot combine [retrievers] and [query]" }

---
"Missing required params":
  - do:
      catch: bad_request
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "text_1", "text_2" ]

  - contains: { error.root_cause.0.reason: "[rrf] [query] must be provided when [fields] is specified" }

  - do:
      catch: bad_request
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "text_1", "text_2" ]
              query: ""

  - contains: { error.root_cause.0.reason: "[rrf] [query] cannot be empty" }

  - do:
      catch: bad_request
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ ]
              query: "foo"

  - contains: { error.root_cause.0.reason: "[rrf] [fields] cannot be empty" }

  - do:
      catch: bad_request
      search:
        index: test-index
        body:
          retriever:
            rrf: {}

  - contains: { error.root_cause.0.reason: "[rrf] must provide [retrievers] or [query]" }

---
"Expansion to equivalent custom sub-retrievers returns the same results":
  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              fields: [ "text_1", "text_2", "sparse_inference", "dense_inference" ]
              query: "match"

  - match: { hits.total.value: 3 }
  - length: { hits.hits: 3 }
  - match: { hits.hits.0._id: "2" }
  - match: { hits.hits.1._id: "1" }
  - match: { hits.hits.2._id: "3" }

  - do:
      search:
        index: test-index
        body:
          retriever:
            rrf:
              retrievers:
                - standard:
                    query:
                      multi_match:
                        query: "match"
                        fields: [ "text_1", "text_2" ]
                        type: "most_fields"
                - rrf:
                    retrievers:
                      - standard:
                          query:
                            match:
                              dense_inference:
                                query: "match"
                      - standard:
                          query:
                            match:
                              sparse_inference:
                                query: "match"

  - match: { hits.total.value: 3 }
  - length: { hits.hits: 3 }
  - match: { hits.hits.0._id: "2" }
  - match: { hits.hits.1._id: "1" }
  - match: { hits.hits.2._id: "3" }

---
"Queries multiple indices using default_field":
  - requires:
      cluster_features: [ "rrf_retriever.multi_index_simplified_format_support" ]
      reason: "Support for querying multiple indices in simplified RRF retriever"
  - do:
      search:
        index: test-index,test-another-index
        body:
          retriever:
            rrf:
              query: "match"

  - match: { hits.total.value: 6 }
  - length: { hits.hits: 6 }
  - match: { hits.hits.0._id: "4" }
  - match: { hits.hits.1._id: "6" }
  - match: { hits.hits.2._id: "1" }
  - match: { hits.hits.3._id: "2" }
  - match: { hits.hits.4._id: "3" }
  - match: { hits.hits.5._id: "5" }

---
"Queries multiple indices with a list of fields":
  - requires:
      cluster_features: [ "rrf_retriever.multi_index_simplified_format_support" ]
      reason: "Support for querying multiple indices in simplified RRF retriever"
  - do:
      search:
        index: test-index,test-another-index
        body:
          retriever:
            rrf:
              fields: ["dense_inference", "text_1", "sparse_vector"]
              query: "match"

  - match: { hits.total.value: 6 }
  - length: { hits.hits: 6 }
  - match: { hits.hits.0._id: "1" }
  - match: { hits.hits.1._id: "2" }
  - match: { hits.hits.2._id: "3" }
  - match: { hits.hits.3._id: "6" }
  - match: { hits.hits.4._id: "4" }
  - match: { hits.hits.5._id: "5" }
