# frozen_string_literal: true

require 'spec_helper'
require 'fspath'
require 'image_optim/cache'
require 'image_optim/path'

describe ImageOptim::Cache do
  before do
    stub_const('Cache', ImageOptim::Cache)
    stub_const('CachePath', ImageOptim::CachePath)
  end

  let(:tmp_file){ double('/somewhere/tmp/foo/bar', rename: 0) }

  let(:cache_dir) do
    dir = '/somewhere/cache'
    allow(FileUtils).to receive(:mkpath).with(Regexp.new(Regexp.escape(dir)), any_args)
    allow(FileUtils).to receive(:touch)
    allow(FSPath).to receive(:temp_file_path) do
      tmp_file
    end
    FSPath.new(dir)
  end

  let(:original) do
    original = double('/somewhere/original', image_format: :ext)
    allow(Digest::SHA1).to receive(:file).with(original) do
      Digest::SHA1.new << 'some content!'
    end
    original
  end

  let(:optimized) do
    double('/somewhere/optimized', format: :ext, basename: 'optimized')
  end

  let(:cached) do
    cached = cache_dir / digest
    allow(Digest::SHA1).to receive(:file).with(cached) do
      Digest::SHA1.new << 'some optimized content!'
    end
    CachePath.convert(cached)
  end

  context 'when cache is disabled (default)' do
    let(:image_optim) do
      double(:image_optim, cache_dir: nil, cache_worker_digests: false, timeout: nil)
    end
    let(:cache){ Cache.new(image_optim, double) }

    describe :fetch do
      it 'always return block' do
        expect(cache.fetch(original){ optimized }).to be optimized
      end
    end

    describe :fetch do
      it 'does not write to disk' do
        expect(FileUtils).not_to receive(:mv)
        expect(FileUtils).not_to receive(:touch)
        expect(cache.fetch(original){ optimized })
      end
    end
  end

  # rubocop:disable Style/RedundantFetchBlock
  shared_examples 'an enabled cache' do
    context 'when cached file does not exist' do
      describe :fetch do
        it 'writes to cache when file is optimizable' do
          cached_s = cached.to_s
          expect(FileTest).to receive(:file?).with(cached_s).and_return(false)
          expect(FileTest).not_to receive(:size?).with(cached_s)
          expect(FileUtils).to receive(:mv).with(optimized, tmp_file)
          expect(tmp_file).to receive(:rename).with(cached)

          allow(File).to receive(:umask).and_return(0o024)
          expect(tmp_file).to receive(:chmod).with(0o642)

          expect(cache.fetch(original){ optimized }).to eq(cached)
        end

        it 'writes an empty file to cache when file is already optimized' do
          cached_s = cached.to_s
          expect(FileTest).to receive(:file?).with(cached_s).and_return(false)
          expect(FileTest).not_to receive(:size?).with(cached_s)
          expect(FileUtils).not_to receive(:mv)
          expect(FileUtils).to receive(:touch).with(cached)

          expect(cache.fetch(original){ nil }).to eq(nil)
        end
      end
    end

    context 'when cached file exists (options and/or workers match)' do
      describe(:fetch) do
        it 'returns cached file' do
          cached_s = cached.to_s
          allow(FileTest).to receive(:file?).with(cached_s).and_return(true)
          allow(FileTest).to receive(:size?).with(cached_s).and_return(1234)
          expect(FileUtils).not_to receive(:mv)
          expect(File).not_to receive(:rename)

          expect(cache.fetch(original){ nil }).to eq(cached)
        end

        it 'returns nil when file is already optimized' do
          cached_s = cached.to_s
          allow(FileTest).to receive(:file?).with(cached_s).and_return(true)
          allow(FileTest).to receive(:size?).with(cached_s).and_return(nil)
          expect(FileUtils).not_to receive(:mv)
          expect(File).not_to receive(:rename)

          expect(cache.fetch(original){ nil }).to eq(nil)
        end
      end
    end
  end
  # rubocop:enable Style/RedundantFetchBlock

  context 'when cache is enabled (without worker digests)' do
    let(:image_optim) do
      double(:image_optim,
             cache_dir: cache_dir, cache_worker_digests: false, timeout: nil)
    end
    let(:cache) do
      cache = Cache.new(image_optim, {})
      allow(cache).
        to receive(:options_by_format).
        with(original.image_format).
        and_return('some options!')
      allow(cache).
        to receive(:bins_by_format).
        with(original.image_format).
        and_return('some bins!')
      cache
    end
    let(:digest){ cache.send(:digest, original, original.image_format) }

    it_behaves_like 'an enabled cache'
  end

  context 'when cache is enabled (with worker digests)' do
    let(:image_optim) do
      double(:image_optim,
             cache_dir: cache_dir,
             cache_worker_digests: true, timeout: nil)
    end
    let(:cache) do
      cache = Cache.new(image_optim, {})
      allow(cache).
        to receive(:options_by_format).
        with(original.image_format).
        and_return('some options!')
      allow(cache).
        to receive(:bins_by_format).
        with(original.image_format).
        and_return('some bins!')
      cache
    end
    let(:digest){ cache.send(:digest, original, original.image_format) }

    it_behaves_like 'an enabled cache'
  end
end
