require File.expand_path("../spec_helper", __dir__)

RSpec.describe SearchFlip::Connection do
  describe "#distribution" do
    it "reutrns the distribution" do
      expect([nil, "opensearch"]).to include(SearchFlip::Connection.new.distribution)
    end

    it "returns the distribution from the config when given" do
      SearchFlip::Config[:version] = { distribution: "distribution" }

      expect(SearchFlip::Connection.new.distribution).to eq("distribution")
    ensure
      SearchFlip::Config.delete(:version)
    end
  end

  describe "#version" do
    it "returns the version" do
      expect(SearchFlip::Connection.new.version).to match(/\A[0-9.]+\z/)
    end

    it "returns the version from the config when given" do
      SearchFlip::Config[:version] = { number: "1.2.3" }

      expect(SearchFlip::Connection.new.version).to eq("1.2.3")
    ensure
      SearchFlip::Config.delete(:version)
    end
  end

  describe "#cluster_health" do
    it "returns the cluster health" do
      expect(["red", "yellow", "green"]).to include(SearchFlip::Connection.new.cluster_health["status"])
    end
  end

  describe "#base_url" do
    it "returns the correct url" do
      expect(SearchFlip::Connection.new(base_url: "base url").base_url).to eq("base url")
    end
  end

  describe "#msearch" do
    it "sends multiple queries and returns all responses" do
      ProductIndex.import create(:product)
      CommentIndex.import create(:comment)

      responses = SearchFlip::Connection.new.msearch([ProductIndex.match_all, CommentIndex.match_all])

      expect(responses.size).to eq(2)
      expect(responses[0].total_entries).to eq(1)
      expect(responses[1].total_entries).to eq(1)
    end
  end

  describe "#update_aliases" do
    it "changes the aliases" do
      connection = SearchFlip::Connection.new

      connection.update_aliases(actions: [{ add: { index: "products", alias: "alias1" } }])
      expect(connection.get_aliases(alias_name: "alias1").keys).to eq(["products"])

      connection.update_aliases(actions: [{ remove: { index: "products", alias: "alias1" } }])
      expect(connection.alias_exists?("alias1")).to eq(false)
    end
  end

  describe "#get_aliases" do
    it "returns the specified aliases" do
      begin
        connection = SearchFlip::Connection.new

        connection.update_aliases(actions: [
          { add: { index: "comments", alias: "alias1" } },
          { add: { index: "products", alias: "alias2" } },
          { add: { index: "products", alias: "alias3" } }
        ])

        expect(connection.get_aliases.keys.to_set).to eq(["comments", "products"].to_set)
        expect(connection.get_aliases["products"]["aliases"].keys.to_set).to eq(["alias2", "alias3"].to_set)
        expect(connection.get_aliases["comments"]["aliases"].keys).to eq(["alias1"])
        expect(connection.get_aliases(index_name: "products").keys).to eq(["products"])
        expect(connection.get_aliases(index_name: "comments,products").keys.to_set).to eq(["comments", "products"].to_set)
        expect(connection.get_aliases(alias_name: "alias1,alias2").keys.to_set).to eq(["comments", "products"].to_set)
        expect(connection.get_aliases(alias_name: "alias1,alias2")["products"]["aliases"].keys).to eq(["alias2"])
      ensure
        connection.update_aliases(actions: [
          { remove: { index: "comments", alias: "alias1" } },
          { remove: { index: "products", alias: "alias2" } },
          { remove: { index: "products", alias: "alias3" } }
        ])
      end
    end
  end

  describe "#alias_exists?" do
    it "returns whether or not the specified alias exists?" do
      begin
        connection = SearchFlip::Connection.new

        expect(connection.alias_exists?(:some_alias)).to eq(false)

        connection.update_aliases(actions: [{ add: { index: "products", alias: "some_alias" } }])

        expect(connection.alias_exists?(:some_alias)).to eq(true)
      ensure
        connection.update_aliases(actions: [{ remove: { index: "products", alias: "some_alias" } }])
      end
    end
  end

  describe "#get_indices" do
    it "returns the specified indices" do
      connection = SearchFlip::Connection.new

      expect(connection.get_indices.map { |index| index["index"] }.grep_v(/^\./).to_set).to eq(["comments", "products"].to_set)
      expect(connection.get_indices("com*").map { |index| index["index"] }).to eq(["comments"])
    end

    it "accepts additional parameters" do
      connection = SearchFlip::Connection.new

      expect(connection.get_indices("comments", params: { h: "i" })).to eq([{ "i" => "comments" }])
    end
  end

  describe "#create_index" do
    it "creates the specified index" do
      begin
        connection = SearchFlip::Connection.new

        expect(connection.index_exists?("index_name")).to eq(false)

        connection.create_index("index_name")

        expect(connection.index_exists?("index_name")).to eq(true)
      ensure
        connection.delete_index("index_name") if connection.index_exists?("index_name")
      end
    end

    it "respects a payload" do
      begin
        connection = SearchFlip::Connection.new

        connection.create_index("index_name", settings: { number_of_shards: 3 })

        expect(connection.get_index_settings("index_name")["index_name"]["settings"]["index"]["number_of_shards"]).to eq("3")
      ensure
        connection.delete_index("index_name") if connection.index_exists?("index_name")
      end
    end
  end

  describe "#close_index" do
    it "closes the specified index" do
      begin
        connection = SearchFlip::Connection.new

        connection.create_index("index_name")
        sleep(0.1) while connection.cluster_health["status"] == "red"

        connection.close_index("index_name")

        expect(connection.get_indices("index_name").first["status"]).to eq("close")
      ensure
        connection.delete_index("index_name") if connection.index_exists?("index_name")
      end
    end
  end

  describe "#open_index" do
    it "opens the specified index" do
      begin
        connection = SearchFlip::Connection.new

        connection.create_index("index_name")
        sleep(0.1) while connection.cluster_health["status"] == "red"
        connection.close_index("index_name")

        connection.open_index("index_name")

        expect(connection.get_indices("index_name").first["status"]).to eq("open")
      ensure
        connection.delete_index("index_name") if connection.index_exists?("index_name")
      end
    end
  end

  describe "#freeze_index" do
    it "freezes the specified index" do
      connection = SearchFlip::Connection.new

      if connection.distribution.nil? && connection.version.to_f >= 6.6 && connection.version.to_i < 8
        begin
          connection.create_index("index_name")
          connection.freeze_index("index_name")

          expect(connection.get_indices("index_name", params: { h: "sth" }).first["sth"]).to eq("true")
        ensure
          connection.delete_index("index_name") if connection.index_exists?("index_name")
        end
      end
    end
  end

  describe "#unfreeze_index" do
    it "unfreezes the specified index" do
      connection = SearchFlip::Connection.new

      if connection.distribution.nil? && connection.version.to_f >= 6.6 && connection.version.to_i < 8
        begin
          connection.create_index("index_name")
          connection.freeze_index("index_name")
          connection.unfreeze_index("index_name")

          expect(connection.get_indices("index_name", params: { h: "sth" }).first["sth"]).to eq("false")
        ensure
          connection.delete_index("index_name") if connection.index_exists?("index_name")
        end
      end
    end
  end

  describe ".analyze" do
    it "analyzes the provided request" do
      connection = SearchFlip::Connection.new

      tokens = connection.analyze(analyzer: "standard", text: "some text")["tokens"].map { |token| token["token"] }

      expect(tokens).to include("some", "text")
    end
  end

  describe "#update_index_settings" do
    it "updates the index settings" do
      begin
        connection = SearchFlip::Connection.new

        connection.create_index("index_name")
        connection.update_index_settings("index_name", settings: { number_of_replicas: 3 })

        expect(connection.get_index_settings("index_name")["index_name"]["settings"]["index"]["number_of_replicas"]).to eq("3")
      ensure
        connection.delete_index("index_name") if connection.index_exists?("index_name")
      end
    end
  end

  describe "#get_index_settings" do
    it "fetches the index settings" do
      begin
        connection = SearchFlip::Connection.new

        connection.create_index("index_name", settings: { number_of_shards: 3 })

        expect(connection.get_index_settings("index_name")["index_name"]["settings"]["index"]["number_of_shards"]).to eq("3")
      ensure
        connection.delete_index("index_name") if connection.index_exists?("index_name")
      end
    end
  end

  describe "#update_mapping" do
    if SearchFlip::Connection.new.then { |connection| connection.distribution || connection.version.to_i >= 7 }
      it "updates the mapping of an index without type name" do
        begin
          connection = SearchFlip::Connection.new

          mapping = { "properties" => { "id" => { "type" => "long" } } }

          connection.create_index("index_name")
          connection.update_mapping("index_name", mapping)

          expect(connection.get_mapping("index_name")).to eq("index_name" => { "mappings" => mapping })
        ensure
          connection.delete_index("index_name") if connection.index_exists?("index_name")
        end
      end
    end

    it "updates the mapping of an index" do
      begin
        connection = SearchFlip::Connection.new

        mapping = { "properties" => { "id" => { "type" => "long" } } }

        connection.create_index("index_name")

        if connection.distribution.nil? && connection.version.to_i < 8
          connection.update_mapping("index_name", { "type_name" => mapping }, type_name: "type_name")

          expect(connection.get_mapping("index_name", type_name: "type_name")).to eq("index_name" => { "mappings" => { "type_name" => mapping } })
        else
          connection.update_mapping("index_name", mapping)

          expect(connection.get_mapping("index_name")).to eq("index_name" => { "mappings" => mapping })
        end
      ensure
        connection.delete_index("index_name") if connection.index_exists?("index_name")
      end
    end
  end

  describe "#delete_index" do
    it "deletes the specified index" do
      begin
        connection = SearchFlip::Connection.new

        connection.create_index("index_name")
        expect(connection.index_exists?("index_name")).to eq(true)

        connection.delete_index("index_name")
        expect(connection.index_exists?("index_name")).to eq(false)
      ensure
        connection.delete_index("index_name") if connection.index_exists?("index_name")
      end
    end
  end

  describe "#refresh" do
    it "refreshes all or the specified indices" do
      begin
        connection = SearchFlip::Connection.new

        connection.create_index("index1")
        connection.create_index("index2")

        expect(connection.refresh).to be_truthy
        expect(connection.refresh("index1")).to be_truthy
        expect(connection.refresh(["index1", "index2"])).to be_truthy
      ensure
        connection.delete_index("index1") if connection.index_exists?("index1")
        connection.delete_index("index2") if connection.index_exists?("index2")
      end
    end
  end

  describe "#bulk" do
    it "imports objects to the specified indices" do
      connection = SearchFlip::Connection.new

      bulk = proc do
        connection.bulk do |indexer|
          indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
          indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
          indexer.index 1, { id: 1 }, _index: CommentIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: CommentIndex.type_name } : {}
        end
      end

      expect(&bulk).to(change { CommentIndex.total_count }.by(1).and(change { CommentIndex.total_count }.by(1)))
    end

    it "raises when no index is given" do
      connection = SearchFlip::Connection.new

      bulk = proc do
        connection.bulk do |indexer|
          indexer.index 1, id: 1
        end
      end

      expect(&bulk).to raise_error(SearchFlip::ResponseError)
    end

    it "respects options" do
      connection = SearchFlip::Connection.new

      connection.bulk do |indexer|
        indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
        indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
      end

      bulk = proc do
        connection.bulk do |indexer|
          indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
          indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
        end
      end

      expect(&bulk).to raise_error(SearchFlip::Bulk::Error)

      bulk = proc do
        connection.bulk ignore_errors: [409] do |indexer|
          indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
          indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
        end
      end

      expect(&bulk).not_to(change { ProductIndex.total_count })
    end

    it "passes default options" do
      allow(SearchFlip::Bulk).to receive(:new)

      connection = SearchFlip::Connection.new

      connection.bulk do |indexer|
        indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
      end

      expect(SearchFlip::Bulk).to have_received(:new).with(
        anything,
        http_client: connection.http_client,
        bulk_limit: connection.bulk_limit,
        bulk_max_mb: connection.bulk_max_mb
      )
    end

    it "passes custom options" do
      allow(SearchFlip::Bulk).to receive(:new)

      connection = SearchFlip::Connection.new

      options = {
        bulk_limit: "bulk limit",
        bulk_max_mb: "bulk max mb",
        http_client: "http client"
      }

      connection.bulk(options) do |indexer|
        indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
      end

      expect(SearchFlip::Bulk).to have_received(:new).with(anything, options)
    end
  end

  describe "#index_url" do
    it "returns the index url for the specified index" do
      connection = SearchFlip::Connection.new(base_url: "base_url")

      expect(connection.index_url("index_name")).to eq("base_url/index_name")
    end
  end

  describe "#type_url" do
    it "returns the type url for the specified index and type" do
      connection = SearchFlip::Connection.new(base_url: "base_url")

      expect(connection.type_url("index_name", "type_name")).to eq("base_url/index_name/type_name")
    end
  end
end
