# frozen_string_literal: true

describe CMSScanner::WebSite do
  subject(:web_site) { described_class.new(url, opts) }
  let(:url)          { 'http://e.org' }
  let(:opts)         { {} }

  describe '#url=' do
    context 'when the url is incorrect' do
      after do
        expect { web_site.url = @url }.to raise_error Addressable::URI::InvalidURIError
      end

      it 'raises an error if empty' do
        @url = ''
      end

      it 'raises an error if wrong format' do
        @url = 'jj'
      end
    end

    context 'when valid' do
      it 'creates an Addressable object and adds a traling slash' do
        web_site.url = 'http://site.com'

        expect(web_site.url).to eq('http://site.com/')
        expect(web_site.uri).to be_a Addressable::URI
      end

      it 'does not modify the original url given' do
        url = 'http://site.com'

        web_site.url = url

        expect(url).to eql('http://site.com')
        expect(web_site.url).to eq('http://site.com/')
      end
    end

    context 'when non ascii chars' do
      it 'normalize it' do
        web_site.url = 'http://пример.испытание/'

        expect(web_site.url).to eql 'http://xn--e1afmkfd.xn--80akhbyknj4f/'
      end
    end
  end

  describe '#url' do
    context 'when no path argument' do
      its(:url) { should eql 'http://e.org/' }
    end

    context 'when a path argument' do
      it 'appends the path' do
        expect(web_site.url('file.txt')).to eql "#{url}/file.txt"
      end

      it 'encodes the path' do
        expect(web_site.url('f ile.txt')).to eql "#{url}/f%20ile.txt"
        expect(web_site.url('s/a%.txt')).to eql "#{url}/s/a%25.txt"
        expect(web_site.url('#file.txt#')).to eql "#{url}/%23file.txt%23"
      end

      context 'when relative path' do
        let(:url) { 'http://e.org/dir/' }

        it 'appends it from the host/domain' do
          expect(web_site.url('/sub/file.txt')).to eql 'http://e.org/sub/file.txt'
        end
      end
    end
  end

  describe '#ip' do
    context 'when target host not known' do
      let(:url) { 'http://lab.local' }

      its(:ip) { should eql 'Unknown' }
    end

    context 'when target host known' do
      before { expect(IPSocket).to receive(:getaddress).and_return('127.0.0.1') }

      its(:ip) { should eql '127.0.0.1' }
    end
  end

  describe '#error_404_res' do
    before { stub_request(:any, /[a-z\d]{6}\.html/).to_return(stubbed_response) }

    context 'when no redirect' do
      let(:stubbed_response) { { status: 200, body: 'hello world!' } }

      its('error_404_res.body') { should eql 'hello world!' }
    end

    # TODO: Uncomment when webmock supports redirects
    # context 'when redirect' do
    #  let(:redirect) { web_site.url('redirect') }
    #  let(:stubbed_response) { { status: 302, headers: { 'Location' => redirect } } }

    #  before { stub_request(:get, redirect).to_return(status: 200, body: 'redirected!') }

    #  its('error_404_res.body') { should eql 'redirected!' }
    # end
  end

  describe '#error_404_url' do
    its(:error_404_url) { should match ERROR_404_URL_PATTERN }

    it 'returns the same url when called more than once' do
      url1 = web_site.error_404_url
      url2 = web_site.error_404_url

      expect(url1).to eql url2
    end
  end

  describe '#opts' do
    its(:opts) { should eql({}) }

    context 'when opts' do
      let(:opts) { { test: 'mm' } }

      its(:opts) { should eql opts }
    end
  end

  describe '#online?, #http_auth?, #access_forbidden?, #proxy_auth?' do
    before { stub_request(:get, web_site.url(path)).to_return(status: status) }

    [nil, 'file-path.txt'].each do |p|
      context "when path = #{p}" do
        let(:path) { p }

        context 'when response status is a 200' do
          let(:status) { 200 }

          it 'is considered fine' do
            expect(web_site.online?(path)).to be true
            expect(web_site.http_auth?(path)).to be false
            expect(web_site.access_forbidden?(path)).to be false
            expect(web_site.proxy_auth?(path)).to be false
          end
        end

        context 'when offline' do
          let(:status) { 0 }

          it 'returns false' do
            expect(web_site.online?(path)).to be false
          end
        end

        context 'when http auth required' do
          let(:status) { 401 }

          it 'returns true' do
            expect(web_site.http_auth?(path)).to be true
          end
        end

        context 'when access is forbidden' do
          let(:status) { 403 }

          it 'return true' do
            expect(web_site.access_forbidden?(path)).to be true
          end
        end

        context 'when proxy auth required' do
          let(:status) { 407 }

          it 'returns true' do
            expect(web_site.proxy_auth?(path)).to be true
          end
        end
      end
    end
  end

  describe '#head_or_get_params' do
    context 'when not already checked' do
      before do
        stub_request(:get, web_site.url)
        stub_request(:head, web_site.homepage_url).to_return(status: status)
      end

      context 'when HEAD dropped/timeout' do
        let(:status) { 0 }

        its(:head_or_get_params) { should eql(method: :get, maxfilesize: 1) }
      end

      context 'when HEAD not supported' do
        let(:status) { 405 }

        its(:head_or_get_params) { should eql(method: :get, maxfilesize: 1) }
      end

      context 'when HEAD not implemented' do
        let(:status) { 501 }

        its(:head_or_get_params) { should eql(method: :get, maxfilesize: 1) }
      end

      context 'when HEAD supported' do
        let(:status) { 200 }

        its(:head_or_get_params) { should eql(method: :head) }
      end
    end

    context 'when already set' do
      it 'returns it' do
        web_site.instance_variable_set(:@head_or_get_params, method: :head)

        expect(web_site.head_or_get_params).to eql(method: :head)
      end
    end
  end

  describe '#head_and_get' do
    before { expect(web_site).to receive(:head_or_get_params).and_return(method: :head) }

    let(:path) { 'something' }

    context 'when no request params given' do
      before { stub_request(:head, web_site.url(path)).to_return(status: status) }

      context 'when HEAD response status is not in the codes given' do
        let(:status) { 404 }

        after do
          expect(@res).to be_a(Typhoeus::Response)
          expect(@res.code).to eql status
          expect(@res.request.options[:method]).to eql :head
          expect(@res.body).to be_empty
        end

        context 'when codes not provided' do
          it 'uses the default [200] and returns the HEAD response' do
            @res = web_site.head_and_get(path)
          end
        end

        context 'when codes provided' do
          it 'uses them and returns the HEAD response' do
            @res = web_site.head_and_get(path, [400])
          end
        end
      end

      context 'when HEAD response status is in the codes given' do
        before { stub_request(:get, web_site.url(path)).to_return(status: status, body: 'hello') }

        after do
          expect(@res).to be_a(Typhoeus::Response)
          expect(@res.code).to eql status
          expect(@res.request.options[:method]).to eql :get
          expect(@res.body).to eql 'hello'
        end

        context 'when codes not provided' do
          let(:status) { 200 }

          it 'uses the default [200] and returns the GET response' do
            @res = web_site.head_and_get(path)
          end
        end

        context 'when codes provided' do
          let(:status) { 400 }

          it 'uses them and returns the GET response' do
            @res = web_site.head_and_get(path, [400])
          end
        end
      end
    end

    context 'when request params given' do
      let(:status) { 200 }

      before do
        stub_request(:head, web_site.url(path)).with(headers: { 'Key' => 'Hello' })

        stub_request(:get, web_site.url(path))
          .with(query: { k: 'value' })
          .and_return(status: 400, body: 'done')
      end

      it 'perform the requests with the expected params' do
        res = web_site.head_and_get(path,
                                    [200],
                                    head: { headers: { 'Key' => 'Hello' } },
                                    get: { params: { k: 'value' } })

        expect(res).to be_a Typhoeus::Response
        expect(res.code).to eql 400
        expect(res.request.options[:method]).to eql :get
        expect(res.body).to eql 'done'
      end
    end
  end
end
