require 'spec_helper_min'
require 'active_support'
require 'active_support/core_ext'

require_relative '../../lib/datasources'
require_relative '../doubles/user'

include CartoDB::Datasources

describe Url::ArcGIS do
  let(:url) { 'http://myserver/arcgis/rest/services/MyFakeService/featurename' }
  let(:invalid_url) { 'http://myserver/mysite/rest/myfakefolder/MyFakeService/featurename' }
  let(:user) { CartoDB::Datasources::Doubles::User.new }

  before { Typhoeus::Expectation.clear }

  describe '#set_data_from' do
    it 'tests preparing the correct url from the one given from the UI' do
      invalid_1 = 'http://myserver/services/MyFakeService/featurename/MapServer'
      invalid_2 = 'myserver/services/MyFakeService/featurename/MapServer'

      test1 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer'
      test2 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/'
      test3 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/0'
      test4 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/0?'
      test5 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/0?q=blablabla'
      test6 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer?q=blablabla'

      valid_map = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer'
      valid_map_trailing_slash = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/'
      valid_layer = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/0'

      # Should be treated as ok
      test7   = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/2314/'
      valid_7 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/2314/'

      arcgis = described_class.get_new(user)

      expect {
        arcgis.send(:sanitize_id, invalid_1)
      }.to raise_error InvalidInputDataError
      expect {
        arcgis.send(:sanitize_id, invalid_2)
      }.to raise_error InvalidInputDataError

      arcgis.send(:sanitize_id, test1).should eq valid_map
      arcgis.send(:sanitize_id, test2).should eq valid_map_trailing_slash
      arcgis.send(:sanitize_id, test3).should eq valid_layer
      arcgis.send(:sanitize_id, test4).should eq valid_layer
      arcgis.send(:sanitize_id, test5).should eq valid_layer
      arcgis.send(:sanitize_id, test6).should eq valid_map
      arcgis.send(:sanitize_id, test7).should eq valid_7
    end
  end

  describe '#get_resource_metadata' do
    it 'tests error scenarios' do
      arcgis = described_class.get_new(user)

      sub_id = '0'

      # 'general http error (non-200)'
      Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/layers/) do
        Typhoeus::Response.new(
          code: 400,
          headers: { 'Content-Type' => 'application/json' },
          body: ''
        )
      end

      expect {
        arcgis.get_resource_metadata(url)
      }.to raise_error DataDownloadError

      # Stub layers request (so now works)
      Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/layers/) do |request|
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_layers.json"))
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: ::JSON.dump(::JSON.parse(body))
        )
      end

      layers_data = arcgis.get_resource_metadata(url)
      layers_data_expected = {
        id: url,
        subresources: [{
          id: "#{url}/#{sub_id}",
          title: 'first layer'
        }]
      }
      layers_data.should eq layers_data_expected

      # 'fields' part
      Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/0/) do |request|
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_metadata_minimal.json"))

        body = ::JSON.parse(body)
        body.delete('fields')
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: ::JSON.dump(body)
        )
      end

      expect {
        arcgis.send(:get_subresource_metadata, url, sub_id)
      }.to raise_error ResponseError

      # Another required field
      Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/0/) do |request|
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_metadata_minimal.json"))

        body = ::JSON.parse(body)
        body.delete('name')
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: ::JSON.dump(body)
        )
      end

      expect {
        arcgis.send(:get_subresource_metadata, url, sub_id)
      }.to raise_error ResponseError

      # Invalid ArcGIS version
      Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/0/) do |request|
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_metadata_minimal.json"))

        body = ::JSON.parse(body)
        body['currentVersion'] = 9.0
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: ::JSON.dump(body)
        )
      end

      expect {
        arcgis.send(:get_subresource_metadata, url, sub_id)
      }.to raise_error InvalidServiceError

      # Invalid ArcGIS URL
      expect {
        arcgis.send(:get_resource_metadata, invalid_url)
      }.to raise_error InvalidInputDataError
    end

    it 'tests metadata retrieval' do
      arcgis = described_class.get_new(user)

      # Stub layers request (so now works)
      Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/layers/) do |request|
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_layers.json"))
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: ::JSON.dump(::JSON.parse(body))
        )
      end

      Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/0/) do |request|
        accept = (request.options[:headers]||{})['Accept'] || 'application/json'
        format = accept.split(',').first

        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_metadata_minimal.json"))

        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => format },
          body: body
        )
      end

      expected_metadata = {
        :arcgis_version=>10.22,
        :name=>"Test Feature",
        :description=>"Sample metadata payload",
        :type=>"Feature Layer",
        :geometry_type=>"esriGeometryPolygon",
        :copyright=>"CartoDB",
        :fields=>[
          {
            :name => "OBJECTID",
            :type => "esriFieldTypeOID"
          },
          {
            :name => "NAME",
            :type => "esriFieldTypeString"
          }
        ],
        :max_records_per_query=>1000,
        :supported_formats=>["JSON", "AMF"],
        :advanced_queries_supported=>true
      }

      expected_metadata_response = {
        id: "#{url}/0",
        title:    'Test Feature',
        url:      nil,
        service:  Url::ArcGIS::DATASOURCE_NAME,
        checksum: nil,
        size:     0,
        filename: 'test_feature.json'
      }

      # Multi-resource scenario already tested above
      response = arcgis.get_resource_metadata("#{url}/0")

      response.nil?.should be false
      arcgis.metadata.should eq expected_metadata

      response.should eq expected_metadata_response
    end
  end

  describe '#get_resource' do
    it 'tests the get_ids_list() private method with error scenarios' do
      arcgis = described_class.get_new(user)

      id = arcgis.send(:sanitize_id, url)

      # 'general http error (non-200)'
      Typhoeus.stub(/\/arcgis\/rest\//) do
        Typhoeus::Response.new(
          code: 400,
          headers: { 'Content-Type' => 'application/json' },
          body: ''
        )
      end

      expect {
        arcgis.send(:get_ids_list, id)
      }.to raise_error DataDownloadError

      # 'objectIds' not present
      Typhoeus::Expectation.clear
      Typhoeus.stub(/\/arcgis\/rest\//) do
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_ids_list.json"))

        body = ::JSON.parse(body)
        body.delete('objectIds')
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: ::JSON.dump(body)
        )
      end

      expect {
        arcgis.send(:get_ids_list, id)
      }.to raise_error ResponseError
    end

    it 'does not break when importing an empty feature list' do
      arcgis = described_class.get_new(user)

      id = arcgis.send(:sanitize_id, url)

      Typhoeus.stub(/\/arcgis\/rest\//) do
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_ids_list.json"))

        body = ::JSON.parse(body)
        body['objectIds'] = []
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: ::JSON.dump(body)
        )
      end

      arcgis.send(:get_ids_list, id)
    end

    it 'tests the get_ids_list() private method' do
      arcgis = described_class.get_new(user)

      id = arcgis.send(:sanitize_id, url)

      Typhoeus.stub(/\/arcgis\/rest\//) do |request|
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_ids_list.json"))
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: body
        )
      end

      expected_ids = [1,2,3,4,5,6,7,8,9,10]

      respose_ids = arcgis.send(:get_ids_list, id)

      respose_ids.nil?.should be false
      respose_ids.should eq expected_ids
    end

    it 'tests the get_ids_list() private method on out-of-order ids' do
      arcgis = described_class.get_new(user)

      id = arcgis.send(:sanitize_id, url)

      Typhoeus.stub(/\/arcgis\/rest\//) do |request|
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_unordered_ids_list.json"))
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: body
        )
      end

      expected_ids = [1,2,3,4,5,6,7,8,9,10]

      respose_ids = arcgis.send(:get_ids_list, id)

      respose_ids.nil?.should be false
      respose_ids.should eq expected_ids
    end

    it 'tests the get_by_ids() private method with error scenarios' do
      arcgis = described_class.get_new(user)

      id = arcgis.send(:sanitize_id, url)

      # Empty ids
      expect {
        arcgis.send(:get_by_ids, id, [], [{ key: 'value' }])
      }.to raise_error InvalidInputDataError

      # Empty fields
      expect {
        arcgis.send(:get_by_ids, id, [1], [])
      }.to raise_error InvalidInputDataError


      # 'general http error (non-200)'
      Typhoeus.stub(/\/arcgis\/rest\//) do
        Typhoeus::Response.new(
          code: 400,
          headers: { 'Content-Type' => 'application/json' },
          body: ''
        )
      end

      expect {
        arcgis.send(:get_by_ids, id, [1], [{ key: 'value' }])
      }.to raise_error DataDownloadError
    end

    it 'tests the get_by_ids() private method' do
      arcgis = described_class.get_new(user)

      id = arcgis.send(:sanitize_id, url)

      Typhoeus.stub(/\/arcgis\/rest\//) do
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_data_01.json"))
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: body
        )
      end

      expected_response_data = {
        geometryType: "esriGeometryPolygon",
        spatialReference: {
          "wkid" => 4326,
          "latestWkid" => 4326
        },
        fields: [
          {
            "name" => "OBJECTID",
            "type" => "esriFieldTypeOID",
            "alias" => "OBJECTID"
          },
          {
            "name" => "WDPAID",
            "type" => "esriFieldTypeInteger",
            "alias" => "WDPAID"
          },
          {
            "name" => "NAME",
            "type" => "esriFieldTypeString",
            "alias" => "NAME",
            "length" => 254
          }
        ],
        features: [
          {"attributes"=>{"OBJECTID"=>1, "NAME"=>"Name of object 1"}, "geometry"=>{"fake"=>"geom"}},
          {"attributes"=>{"OBJECTID"=>2, "NAME"=>"Name of object 2"}, "geometry"=>{"fake"=>"geom"}},
          {"attributes"=>{"OBJECTID"=>3, "NAME"=>"Name of object 3"}, "geometry"=>{"fake"=>"geom"}}
        ]
      }

      ids_to_retrieve = [1,2,3]
      # WDPAID also present, but left on purpose untouched
      fields_to_retrieve = [{
                              name: 'OBJECTID',
                              type: 'esriFieldTypeOID'
                            },
                            {
                              name: 'NAME',
                              type: 'esriFieldTypeString'
                            }]

      response_data = arcgis.send(:get_by_ids, id, ids_to_retrieve, fields_to_retrieve)

      response_data.nil?.should eq false
      response_data[:features].length.should eq 3

      response_data.should eq expected_response_data

    end

    it 'tests retrieval of data' do
      arcgis = described_class.get_new(user)

      feature_names = []

      # Layers request
      Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/layers/) do |request|
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_layers.json"))
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: ::JSON.dump(::JSON.parse(body))
        )
      end

      # Metadata of a layer
      Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/0\?f=json/) do
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_metadata_minimal.json"))
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: body
        )
      end

      # IDs list of a layer
      Typhoeus.stub(/\/arcgis\/rest\/(.*)query\?where=/) do
        body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_ids_list_01.json"))
        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: body
        )
      end

      Typhoeus.stub(/\/arcgis\/rest\/(.*)query$/) do |response|
        if response.options[:body][:objectIds].to_i == 1
          # First item fetch of a layer
          body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_data_01.json"))
          body = ::JSON.parse(body)

          feature_names.push body['features'][0]['attributes']['NAME']
          body['features'] = [ body['features'][0] ]
        else
          # Remaining items fetch of a layer, will not use :objectIds
          body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_data_01.json"))
          body = ::JSON.parse(body)

          feature_names.push body['features'][1]['attributes']['NAME']
          feature_names.push body['features'][2]['attributes']['NAME']
          body['features'] = [ body['features'][1], body['features'][2] ]
        end

        Typhoeus::Response.new(
          code: 200,
          headers: { 'Content-Type' => 'application/json' },
          body: ::JSON.dump(body)
        )
      end

      # 1) Retrieve lists of layers
      metadata = arcgis.get_resource_metadata(url)

      # 2) Retrieve metadata of a specific layer (doesn't adds much, but to replicate flow)
      item_metadata = arcgis.get_resource_metadata(metadata[:subresources].first[:id])

      item_metadata.nil?.should eq false
      # No more checks as will fail later if missing something

      spatial_ref_expectation = {"wkid"=>4326, "latestWkid"=>4326}

      initial_stream_data = arcgis.initial_stream(item_metadata[:id])

      initial_stream_data.nil?.should eq false
      initial_stream_data = ::JSON.parse(initial_stream_data)
      initial_stream_data['geometryType'].should eq 'esriGeometryPolygon'
      initial_stream_data['spatialReference'].should eq spatial_ref_expectation
      initial_stream_data['fields'].count.should eq 3
      initial_stream_data['fields'][0]['name'].should eq 'OBJECTID'
      initial_stream_data['fields'][1]['name'].should eq 'WDPAID'
      initial_stream_data['fields'][2]['name'].should eq 'NAME'
      initial_stream_data['features'].count.should eq 1
      initial_stream_data['features'][0]['attributes'].nil?.should eq false
      initial_stream_data['features'][0]['attributes']['NAME'].should eq feature_names[0]
      initial_stream_data['features'][0]['geometry'].nil?.should eq false

      streamed_data = arcgis.stream_resource(item_metadata[:id])

      streamed_data.nil?.should eq false
      streamed_data = ::JSON.parse(streamed_data)
      streamed_data['geometryType'].should eq 'esriGeometryPolygon'
      streamed_data['spatialReference'].should eq spatial_ref_expectation
      streamed_data['fields'].count.should eq 3
      streamed_data['fields'][0]['name'].should eq 'OBJECTID'
      streamed_data['fields'][1]['name'].should eq 'WDPAID'
      streamed_data['fields'][2]['name'].should eq 'NAME'
      streamed_data['features'].count.should eq 2
      streamed_data['features'][0]['attributes']['NAME'].should eq feature_names[1]
      streamed_data['features'][1]['attributes']['NAME'].should eq feature_names[2]

    end
  end
end
