# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Remotable do
  let(:foo_class) do
    Class.new do
      def initialize
        @attrs = {}
      end

      def [](arg)
        @attrs[arg]
      end

      def []=(arg1, arg2)
        @attrs[arg1] = arg2
      end

      def hoge=(arg); end

      def hoge_file_name; end

      def hoge_file_name=(arg); end

      def has_attribute?(arg); end

      def self.attachment_definitions
        { hoge: nil }
      end
    end
  end

  let(:attribute_name) { :"#{hoge}_remote_url" }
  let(:code)           { 200 }
  let(:file)           { 'filename="foo.txt"' }
  let(:foo)            { foo_class.new }
  let(:headers)        { { 'content-disposition' => file } }
  let(:hoge)           { :hoge }
  let(:url)            { 'https://google.com' }

  before do
    foo_class.include described_class
    foo_class.remotable_attachment :hoge, 1.kilobyte
  end

  it 'defines a method #hoge_remote_url=' do
    expect(foo).to respond_to(:hoge_remote_url=)
  end

  it 'defines a method #reset_hoge!' do
    expect(foo).to respond_to(:reset_hoge!)
  end

  it 'defines a method #download_hoge!' do
    expect(foo).to respond_to(:download_hoge!)
  end

  describe '#hoge_remote_url=' do
    before do
      stub_request(:get, url).to_return(status: code, headers: headers)
    end

    it 'always returns its argument' do
      [nil, '', [], {}].each do |arg|
        expect(foo.hoge_remote_url = arg).to be arg
      end
    end

    context 'with an invalid URL' do
      before do
        parsed = instance_double(Addressable::URI)
        allow(parsed).to receive(:normalize).with(no_args).and_raise(Addressable::URI::InvalidURIError)
        allow(Addressable::URI).to receive(:parse).with(url).and_return(parsed)
      end

      it 'makes no request' do
        foo.hoge_remote_url = url
        expect(a_request(:get, url)).to_not have_been_made
      end
    end

    context 'with scheme that is neither http nor https' do
      let(:url) { 'ftp://google.com' }

      it 'makes no request' do
        foo.hoge_remote_url = url
        expect(a_request(:get, url)).to_not have_been_made
      end
    end

    context 'with relative URL' do
      let(:url) { 'https:///path' }

      it 'makes no request' do
        foo.hoge_remote_url = url
        expect(a_request(:get, url)).to_not have_been_made
      end
    end

    context 'when URL has not changed' do
      it 'makes no request if file is already saved' do
        allow(foo).to receive(:[]).with(attribute_name).and_return(url)
        allow(foo).to receive(:hoge_file_name).and_return('foo.jpg')

        foo.hoge_remote_url = url
        expect(a_request(:get, url)).to_not have_been_made
      end

      it 'makes request if file is not already saved' do
        allow(foo).to receive(:[]).with(attribute_name).and_return(url)
        allow(foo).to receive(:hoge_file_name).and_return(nil)

        foo.hoge_remote_url = url
        expect(a_request(:get, url)).to have_been_made
      end
    end

    context 'when instance has no attribute for URL' do
      before do
        allow(foo).to receive(:has_attribute?).with(attribute_name).and_return(false)
      end

      it 'does not try to write attribute' do
        allow(foo).to receive('[]=').with(attribute_name, url)

        foo.hoge_remote_url = url

        expect(foo).to_not have_received('[]=').with(attribute_name, url)
      end
    end

    context 'when instance has an attribute for URL' do
      before do
        allow(foo).to receive(:has_attribute?).with(attribute_name).and_return(true)
      end

      it 'does not try to write attribute' do
        allow(foo).to receive('[]=').with(attribute_name, url)

        foo.hoge_remote_url = url

        expect(foo).to have_received('[]=').with(attribute_name, url)
      end
    end

    context 'with a valid URL' do
      it 'makes a request' do
        foo.hoge_remote_url = url
        expect(a_request(:get, url)).to have_been_made
      end

      context 'when the response is not successful' do
        let(:code) { 500 }

        it 'does not assign file' do
          allow(foo).to receive(:public_send)
          allow(foo).to receive(:public_send)

          foo.hoge_remote_url = url

          expect(foo).to_not have_received(:public_send).with("#{hoge}=", any_args)
          expect(foo).to_not have_received(:public_send).with("#{hoge}_file_name=", any_args)
        end
      end

      context 'when the response is successful' do
        let(:code) { 200 }

        context 'when contains Content-Disposition header' do
          let(:file)      { 'filename="foo.txt"' }
          let(:headers)   { { 'content-disposition' => file } }

          it 'assigns file' do
            response_with_limit = ResponseWithLimit.new(nil, 0)

            allow(ResponseWithLimit).to receive(:new).with(anything, anything).and_return(response_with_limit)

            allow(foo).to receive(:public_send)
            foo.hoge_remote_url = url
            expect(foo).to have_received(:public_send).with(:"download_#{hoge}!", url)

            allow(foo).to receive(:public_send)
            foo.download_hoge!(url)
            expect(foo).to have_received(:public_send).with(:"#{hoge}=", response_with_limit)
          end
        end
      end

      context 'when an error is raised during the request' do
        before do
          stub_request(:get, url).to_raise(error_class)
        end

        error_classes = [
          HTTP::TimeoutError,
          HTTP::ConnectionError,
          OpenSSL::SSL::SSLError,
          Paperclip::Errors::NotIdentifiedByImageMagickError,
          Addressable::URI::InvalidURIError,
        ]

        error_classes.each do |error_class|
          let(:error_class) { error_class }

          it 'calls Rails.logger.debug' do
            allow(Rails.logger).to receive(:debug)

            foo.hoge_remote_url = url

            expect(Rails.logger).to have_received(:debug) do |&block|
              expect(block.call).to match(/^Error fetching remote #{hoge}: /)
            end
          end
        end
      end
    end
  end
end
