# frozen_string_literal: true

require File.expand_path("../../test_helper", File.dirname(__FILE__))

class HashRedisStoreTest < Minitest::Test
  REDIS_STORAGE_FORMAT_VERSION = Coverband::Adapters::HashRedisStore::REDIS_STORAGE_FORMAT_VERSION

  class MockRelativeFileConverter
    def self.convert(file)
      file.sub("app_path/", "./")
    end
  end

  def setup
    super
    @redis = Coverband::Test.redis
    # FIXME: remove dependency on configuration and instead pass this in as an argument
    Coverband.configure do |config|
      config.root_paths = ["app_path/"]
    end
    @store = Coverband::Adapters::HashRedisStore.new(@redis, redis_namespace: "coverband_test", relative_file_converter: MockRelativeFileConverter)
    Coverband.configuration.store = @store
  end

  def mock_time(time = Time.now)
    Time.stubs(:now).returns(time)
    time
  end

  def test_no_coverage
    @store.save_report({})
    assert_equal({}, @store.coverage)
  end

  class UnsupportedRedis
    def info
      {"redis_version" => "2.5.0"}
    end
  end

  def test_unsupported_redis
    assert_raises RuntimeError do
      Coverband::Adapters::HashRedisStore.new(UnsupportedRedis.new)
    end
  end

  def test_coverage_for_file
    yesterday = DateTime.now.prev_day.to_time
    today = Time.now
    mock_time(yesterday)
    mock_file_hash
    @store.save_report(
      "app_path/dog.rb" => [0, 1, 2]
    )
    assert_equal(
      {
        "first_updated_at" => yesterday.to_i,
        "last_updated_at" => yesterday.to_i,
        "file_hash" => "abcd",
        "data" => [0, 1, 2],
        "timedata" => [nil, Time.at(yesterday.to_i), Time.at(yesterday.to_i)]
      },
      @store.coverage["./dog.rb"]
    )
    mock_time(today)
    @store.save_report(
      "app_path/dog.rb" => [1, 1, 0]
    )

    assert_equal("abcd", @store.coverage["./dog.rb"]["file_hash"])
    assert_equal(today.to_i, @store.coverage["./dog.rb"]["last_updated_at"])
    assert_equal(yesterday.to_i, @store.coverage["./dog.rb"]["first_updated_at"])
    assert_equal([1, 2, 2], @store.coverage["./dog.rb"]["data"])
    assert_equal([Time.at(today.to_i), Time.at(today.to_i), Time.at(yesterday.to_i)], @store.coverage["./dog.rb"]["timedata"])
  end

  def test_ttl_set
    mock_file_hash(hash: "abcd")
    @store = Coverband::Adapters::HashRedisStore.new(@redis, redis_namespace: "coverband_test", ttl: 3600, relative_file_converter: MockRelativeFileConverter)
    @store.save_report(
      "app_path/dog.rb" => [0, 1, 2]
    )
    assert_operator(@redis.ttl("#{REDIS_STORAGE_FORMAT_VERSION}.coverband_test.runtime../dog.rb.abcd"), :>, 0)
  end

  def test_no_ttl_set
    mock_file_hash(hash: "abcd")
    @store = Coverband::Adapters::HashRedisStore.new(@redis, redis_namespace: "coverband_test", ttl: nil, relative_file_converter: MockRelativeFileConverter)
    @store.save_report(
      "app_path/dog.rb" => [0, 1, 2]
    )
    assert_equal(-1, @redis.ttl("#{REDIS_STORAGE_FORMAT_VERSION}.coverband_test.runtime../dog.rb.abcd"))
  end

  def test_coverage_for_multiple_files
    current_time = mock_time
    mock_file_hash
    data = {
      "app_path/dog.rb" => [0, nil, 1, 2],
      "app_path/cat.rb" => [1, 2, 0, 1, 5],
      "app_path/ferrit.rb" => [1, 5, nil, 2, nil]
    }
    @store.save_report(data)
    @store.coverage
    assert_equal(
      {
        "first_updated_at" => current_time.to_i,
        "last_updated_at" => current_time.to_i,
        "file_hash" => "abcd",
        "data" => [0, nil, 1, 2],
        "timedata" => [nil, nil, Time.at(current_time.to_i), Time.at(current_time.to_i)]
      }, @store.coverage["./dog.rb"]
    )
    assert_equal [1, 2, 0, 1, 5], @store.coverage["./cat.rb"]["data"]
    assert_equal [1, 5, nil, 2, nil], @store.coverage["./ferrit.rb"]["data"]
  end

  def test_file_hash_change
    mock_file_hash(hash: "abc")
    @store.save_report("app_path/dog.rb" => [0, nil, 1, 2])
    @store.coverage
    assert_equal [0, nil, 1, 2], @store.coverage["./dog.rb"]["data"]
    @store.instance_eval { @file_hash_cache = {} }
    mock_file_hash(hash: "123")
    assert_nil @store.coverage["./dog.rb"]
  end

  def test_type
    mock_file_hash
    @store.type = :eager_loading
    data = {
      "app_path/dog.rb" => [0, nil, 1, 2]
    }
    @store.save_report(data)
    assert_equal 1, @store.coverage.length
    assert_equal [0, nil, 1, 2], @store.coverage["./dog.rb"]["data"]
    # eager_loading doesn't set last_updated_at
    assert_nil @store.coverage["./dog.rb"]["last_updated_at"]
    @store.type = Coverband::RUNTIME_TYPE
    data = {
      "app_path/cat.rb" => [1, 2, 0, 1, 5]
    }
    current_time = Time.now.to_i
    @store.save_report(data)
    assert_equal 1, @store.coverage.length
    assert_equal [1, 2, 0, 1, 5], @store.coverage["./cat.rb"]["data"]
    assert current_time <= @store.coverage["./cat.rb"]["last_updated_at"]
  end

  def test_coverage_type
    mock_file_hash
    @store.type = :eager_loading
    data = {
      "app_path/dog.rb" => [0, nil, 1, 2]
    }
    @store.save_report(data)
    @store.type = Coverband::RUNTIME_TYPE
    assert_equal [0, nil, 1, 2], @store.coverage(:eager_loading)["./dog.rb"]["data"]
  end

  def test_clear
    mock_file_hash
    @store.type = Coverband::EAGER_TYPE
    data = {
      "app_path/dog.rb" => [0, nil, 1, 2]
    }
    @store.save_report(data)
    assert_equal 1, @store.coverage.length
    @store.type = Coverband::RUNTIME_TYPE
    data = {
      "app_path/cat.rb" => [1, 2, 0, 1, 5]
    }
    @store.save_report(data)
    assert_equal 1, @store.coverage.length
    @redis.set("random", "data")
    @store.clear!
    @store.type = Coverband::RUNTIME_TYPE
    assert @store.coverage.empty?
    @store.type = :eager_loading
    assert @store.coverage.empty?
    assert_equal "data", @redis.get("random")
  end

  def test_clear_file
    mock_file_hash
    @store.type = :eager_loading
    @store.save_report("app_path/dog.rb" => [0, 1, 1])
    @store.type = Coverband::RUNTIME_TYPE
    @store.save_report("app_path/dog.rb" => [1, 0, 1])
    assert_equal [1, 1, 2], @store.get_coverage_report[:merged]["./dog.rb"]["data"]
    @store.clear_file!("app_path/dog.rb")
    assert_nil @store.get_coverage_report[:merged]["./dog.rb"]
  end

  def test_split_coverage
    @store = Coverband::Adapters::HashRedisStore.new(
      @redis,
      redis_namespace: "coverband_test",
      relative_file_converter: MockRelativeFileConverter
    )

    mock_file_hash
    yesterday = DateTime.now.prev_day.to_time
    mock_time(yesterday)

    @store.type = :eager_loading
    data = {
      "app_path/dog.rb" => [0, nil, 1]
    }
    @store.save_report(data)

    @store.type = :runtime
    @store.save_report(
      "app_path/dog.rb" => [0, 1, 2]
    )
    redis_pipelined = Spy.on(@redis, :pipelined).and_call_through
    assert_equal(
      {
        runtime: {
          "./dog.rb" => {
            "first_updated_at" => yesterday.to_i,
            "last_updated_at" => yesterday.to_i,
            "file_hash" => "abcd",
            "data" => [0, 1, 2],
            "timedata" => [nil, Time.at(yesterday.to_i), Time.at(yesterday.to_i)]
          }
        },
        eager_loading: {
          "./dog.rb" => {
            "first_updated_at" => yesterday.to_i,
            "last_updated_at" => nil,
            "file_hash" => "abcd",
            "data" => [0, nil, 1],
            "timedata" => [nil, nil, Time.at(yesterday.to_i)]
          }
        }
      },
      @store.split_coverage([Coverband::RUNTIME_TYPE, Coverband::EAGER_TYPE], {}, {page: 1})
    )
    assert_equal 2, redis_pipelined.calls.count
  end
end
