# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Remotable do
  class Foo
    def initialize
      @attrs = {}
    end

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

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

    def hoge=(arg); end

    def hoge_file_name=(arg); end

    def has_attribute?(arg); end

    def self.attachment_definitions
      { hoge: nil }
    end
  end

  context 'Remotable module is included' do
    before do
      class Foo
        include Remotable
        remotable_attachment :hoge, 1.kilobyte
      end
    end

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

    let(:request) do
      stub_request(:get, url)
        .to_return(status: code, headers: headers)
    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

    describe '#hoge_remote_url' do
      before do
        request
      end

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

      context 'Addressable::URI::InvalidURIError raised' do
        it 'makes no request' do
          allow(Addressable::URI).to receive_message_chain(:parse, :normalize)
            .with(url).with(no_args).and_raise(Addressable::URI::InvalidURIError)

          foo.hoge_remote_url = url
          expect(request).not_to have_been_requested
        end
      end

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

        it 'makes no request' do
          foo.hoge_remote_url = url
          expect(request).not_to have_been_requested
        end
      end

      context 'parsed_url.host is empty' do
        it 'makes no request' do
          parsed_url = double(scheme: 'https', host: double(blank?: true))
          allow(Addressable::URI).to receive_message_chain(:parse, :normalize)
            .with(url).with(no_args).and_return(parsed_url)

          foo.hoge_remote_url = url
          expect(request).not_to have_been_requested
        end
      end

      context 'parsed_url.host is nil' do
        it 'makes no request' do
          parsed_url = Addressable::URI.parse('https:https://example.com/path/file.png')
          allow(Addressable::URI).to receive_message_chain(:parse, :normalize)
            .with(url).with(no_args).and_return(parsed_url)

          foo.hoge_remote_url = url
          expect(request).not_to have_been_requested
        end
      end

      context 'foo[attribute_name] == url' do
        it 'makes no request' do
          allow(foo).to receive(:[]).with(attribute_name).and_return(url)

          foo.hoge_remote_url = url
          expect(request).not_to have_been_requested
        end
      end

      context "scheme is https, parsed_url.host isn't empty, and foo[attribute_name] != url" do
        it 'makes a request' do
          foo.hoge_remote_url = url
          expect(request).to have_been_requested
        end

        context 'response.code != 200' do
          let(:code) { 500 }

          it 'calls not send' do
            expect(foo).not_to receive(:send).with("#{hoge}=", any_args)
            expect(foo).not_to receive(:send).with("#{hoge}_file_name=", any_args)
            foo.hoge_remote_url = url
          end
        end

        context 'response.code == 200' do
          let(:code) { 200 }

          context 'response contains headers["content-disposition"]' do
            let(:file)      { 'filename="foo.txt"' }
            let(:headers)   { { 'content-disposition' => file } }

            it 'calls send' do
              string_io = StringIO.new('')
              extname   = '.txt'
              basename  = '0123456789abcdef'

              allow(SecureRandom).to receive(:hex).and_return(basename)
              allow(StringIO).to receive(:new).with(anything).and_return(string_io)

              expect(foo).to receive(:send).with("#{hoge}=", string_io)
              expect(foo).to receive(:send).with("#{hoge}_file_name=", basename + extname)
              foo.hoge_remote_url = url
            end
          end

          context 'if has_attribute?' do
            it 'calls foo[attribute_name] = url' do
              allow(foo).to receive(:has_attribute?).with(attribute_name).and_return(true)
              expect(foo).to receive('[]=').with(attribute_name, url)
              foo.hoge_remote_url = url
            end
          end

          context 'unless has_attribute?' do
            it 'calls not foo[attribute_name] = url' do
              allow(foo).to receive(:has_attribute?)
                .with(attribute_name).and_return(false)
              expect(foo).not_to receive('[]=').with(attribute_name, url)
              foo.hoge_remote_url = url
            end
          end
        end

        context 'an error raised during the request' do
          let(:request) { stub_request(:get, url).to_raise(error_class) }

          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
              expect(Rails.logger).to receive(:debug).with(/^Error fetching remote #{hoge}: /)
              foo.hoge_remote_url = url
            end
          end
        end
      end
    end

    describe '#reset_hoge!' do
      context 'if url.blank?' do
        it 'returns nil, without clearing foo[attribute_name] and calling #hoge_remote_url=' do
          url = nil
          expect(foo).not_to receive(:send).with(:hoge_remote_url=, url)
          foo[attribute_name] = url
          expect(foo.reset_hoge!).to be_nil
          expect(foo[attribute_name]).to be_nil
        end
      end

      context 'unless url.blank?' do
        it 'clears foo[attribute_name] and calls #hoge_remote_url=' do
          foo[attribute_name] = url
          expect(foo).to receive(:send).with(:hoge_remote_url=, url)
          foo.reset_hoge!
          expect(foo[attribute_name]).to be ''
        end
      end
    end
  end
end
