# Contains specs that both vizjson2 and 3 must verify.
# Specs including this should define:
# - api_vx_visualizations_vizjson_url: an alias for the desired URL generator.
# - vizjson_vx_version: vizjson version.
# Example:
#  it_behaves_like 'vizjson generator' do
#    def api_vx_visualizations_vizjson_url(options)
#      api_v2_visualizations_vizjson_url(options)
#    end
#
#    def vizjson_vx_version
#      '0.1.0'
#    end
#  end
shared_examples_for 'vizjson generator' do
  describe 'vizjson2 compatibility' do
    include_context 'visualization creation helpers'
    include_context 'users helper'

    TEST_UUID = '00000000-0000-0000-0000-000000000000'.freeze

    before(:all) do
      bypass_named_maps

      @user_1 = create(:valid_user, private_tables_enabled: false)
      @api_key = @user_1.api_key
      @user_2 = create(:valid_user)

      @headers = { 'CONTENT_TYPE'  => 'application/json' }
      host! "#{@user_1.subdomain}.localhost.lan"
    end

    after(:all) do
      @user_1.destroy
      @user_2.destroy
    end

    it 'returns 404 for non existing visualizations' do
      get api_vx_visualizations_vizjson_url(id: TEST_UUID, api_key: @api_key), {}, http_json_headers
      last_response.status.should == 404
    end

    it 'tests privacy of vizjsons' do
      Carto::NamedMaps::Api.any_instance.stubs(show: { template: { auth: { valid_tokens: ['mammas', 'pappas'] } } },
                                               create: true,
                                               update: true,
                                               destroy: true)

      user_1 = create_user(
        username: "test#{rand(9999)}-1",
        email: "client#{rand(9999)}@carto.com",
        password: 'clientex',
        private_tables_enabled: true
      )

      user_2 = create_user(
        username: "test#{rand(9999)}-2",
        email: "client#{rand(9999)}@carto.com",
        password: 'clientex',
        private_tables_enabled: true
      )

      organization = test_organization
      organization.save

      user_org = CartoDB::UserOrganization.new(organization.id, user_1.id)
      user_org.promote_user_to_admin
      organization.reload
      user_1.reload

      user_2.organization_id = organization.id
      user_2.save.reload
      organization.reload

      post api_v1_visualizations_create_url(user_domain: user_1.username, api_key: user_1.api_key),
           factory(user_1).to_json, http_json_headers
      last_response.status.should == 200
      body = JSON.parse(last_response.body)
      u1_vis_1_id = body.fetch('id')
      u1_vis_1_perm_id = body.fetch('permission').fetch('id')
      # By default derived vis from private tables are WITH_LINK, so setprivate
      put api_v1_visualizations_update_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key),
          {
            id: u1_vis_1_id,
            privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE
          }.to_json, http_json_headers
      last_response.status.should == 200

      # Share vis with user_2 in readonly (vis can never be shared in readwrite)
      request_payload = {
        acl: [
          {
            type: Carto::Permission::TYPE_USER,
            entity: { id: user_2.id },
            access: Carto::Permission::ACCESS_READONLY
          }
        ]
      }.to_json
      request_url_params = { user_domain: user_1.username, api_key: user_1.api_key, id: u1_vis_1_perm_id }
      put api_v1_permissions_update_url(request_url_params), request_payload, http_json_headers
      last_response.status.should == 200

      # privacy private checks
      # ----------------------

      # Owner, authenticated
      get api_vx_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key)
      last_response.status.should == 200
      body = JSON.parse(last_response.body)
      body['id'].should eq u1_vis_1_id

      # Other user, has it shared in readonly mode
      get api_vx_visualizations_vizjson_url(user_domain: user_2.username, id: u1_vis_1_id, api_key: user_2.api_key)
      last_response.status.should == 200
      body = JSON.parse(last_response.body)
      body['id'].should eq u1_vis_1_id

      # Unauthenticated user
      get api_vx_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user_1.api_key)
      last_response.status.should == 403

      # Unauthenticated user
      get api_vx_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user_1.api_key)
      last_response.status.should == 403

      # Now with link
      # -------------
      put api_v1_visualizations_update_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key),
          {
            id: u1_vis_1_id,
            privacy: CartoDB::Visualization::Member::PRIVACY_LINK
          }.to_json, http_json_headers
      last_response.status.should == 200

      # Owner authenticated
      get api_vx_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key)
      last_response.status.should == 200
      body = JSON.parse(last_response.body)
      body['id'].should eq u1_vis_1_id

      # Other user has it shared in readonly mode
      get api_vx_visualizations_vizjson_url(user_domain: user_2.username, id: u1_vis_1_id, api_key: user_2.api_key)
      last_response.status.should == 200
      body = JSON.parse(last_response.body)
      body['id'].should eq u1_vis_1_id

      # Unauthenticated user
      get api_vx_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user_1.api_key)
      last_response.status.should == 200
      body = JSON.parse(last_response.body)
      body['id'].should eq u1_vis_1_id

      # Now public
      # ----------
      put api_v1_visualizations_update_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key),
          {
            id: u1_vis_1_id,
            privacy: CartoDB::Visualization::Member::PRIVACY_LINK
          }.to_json, http_json_headers
      last_response.status.should == 200

      get api_vx_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key)
      last_response.status.should == 200
      body = JSON.parse(last_response.body)
      body['id'].should eq u1_vis_1_id

      # Other user has it shared in readonly mode
      get api_vx_visualizations_vizjson_url(user_domain: user_2.username, id: u1_vis_1_id, api_key: user_2.api_key)
      last_response.status.should == 200
      body = JSON.parse(last_response.body)
      body['id'].should eq u1_vis_1_id

      # Unauthenticated user
      get api_vx_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user_1.api_key)
      last_response.status.should == 200
      body = JSON.parse(last_response.body)
      body['id'].should eq u1_vis_1_id

      # Check visualization id under wrong subdomain triggers 404
      get api_vx_visualizations_vizjson_url(user_domain: @user_1.username, id: u1_vis_1_id, api_key: @user_1.api_key)
      last_response.status.should == 404

      # Check visualization id under shared with user subdomain triggers 200
      get api_vx_visualizations_vizjson_url(user_domain: user_2.username, id: u1_vis_1_id, api_key: user_2.api_key)
      last_response.status.should == 200
    end

    it 'Sanitizes vizjson callback' do
      valid_callback = 'my_function'
      valid_callback2 = 'a'
      invalid_callback1 = 'alert(1);'
      invalid_callback2 = '%3B'
      invalid_callback3 = '123func' # JS names cannot start by number

      viz = api_visualization_creation(@user_1, @headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_DERIVED })
      viz_id = viz.id
      get api_vx_visualizations_vizjson_url(id: viz_id, api_key: @api_key, callback: valid_callback), {}, @headers
      last_response.status.should == 200
      (last_response.body =~ /^(\/\*\*\/)?#{valid_callback}\(\{/i).should eq 0

      get api_vx_visualizations_vizjson_url(id: viz_id, api_key: @api_key, callback: invalid_callback1), {}, @headers
      last_response.status.should == 400

      get api_vx_visualizations_vizjson_url(id: viz_id, api_key: @api_key, callback: invalid_callback2), {}, @headers
      last_response.status.should == 400

      get api_vx_visualizations_vizjson_url(id: viz_id, api_key: @api_key, callback: invalid_callback3), {}, @headers
      last_response.status.should == 400

      # if param specified, must not be empty
      get api_vx_visualizations_vizjson_url(id: viz_id, api_key: @api_key, callback: ''), {}, @headers
      last_response.status.should == 400

      get api_vx_visualizations_vizjson_url(id: viz_id, api_key: @api_key, callback: valid_callback2), {}, @headers
      last_response.status.should == 200
      (last_response.body =~ /^(\/\*\*\/)?#{valid_callback2}\(\{/i).should eq 0

      get api_vx_visualizations_vizjson_url(id: viz_id, api_key: @api_key), {}, @headers
      last_response.status.should == 200
      (last_response.body =~ /^\{/i).should eq 0
    end

    describe 'get vizjson' do
      before(:each) do
        Carto::NamedMaps::Api.any_instance.stubs(show: nil, create: true, update: true, destroy: true)
        delete_user_data(@user_1)
      end

      it 'renders vizjson vx' do
        viz = api_visualization_creation(@user_1, @headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_DERIVED })
        get api_vx_visualizations_vizjson_url(id: viz.id, api_key: @api_key),
          {}, @headers
        last_response.status.should == 200
        ::JSON.parse(last_response.body).keys.length.should > 1
      end

      it 'returns 200 if subdomain is empty' do
        viz = api_visualization_creation(@user_1, @headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_DERIVED })
        # INFO: I couldn't get rid of subdomain, so I stubbed
        CartoDB.stubs(:extract_subdomain).returns('')
        get api_vx_visualizations_vizjson_url(id: viz.id)
        last_response.status.should == 200
      end

      it 'returns 200 if subdomain matches' do
        viz = api_visualization_creation(@user_1, @headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_DERIVED })
        get api_vx_visualizations_vizjson_url(user_domain: @user_1.username, id: viz.id)
        last_response.status.should == 200
      end

      it 'returns 404 if subdomain does not match' do
        viz = api_visualization_creation(@user_1, @headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_DERIVED })
        get api_vx_visualizations_vizjson_url(user_domain: 'whatever', id: viz.id)
        last_response.status.should == 404
      end

      it "comes with proper surrogate-key" do
        Carto::NamedMaps::Api.any_instance.stubs(get: nil, create: true, update: true)
        table                 = table_factory(privacy: 1)
        source_visualization  = table.fetch('table_visualization')
        map_id = source_visualization[:map_id]

        derived_visualization = create(:derived_visualization, user_id: @user_1.id, map_id: map_id)
        viz_id = derived_visualization.id

        put api_v1_visualizations_show_url(user_domain: @user_1.username, id: viz_id, api_key: @api_key),
            { privacy: Carto::Visualization::PRIVACY_PUBLIC, id: viz_id }.to_json, @headers

        get api_vx_visualizations_vizjson_url(user_domain: @user_1.username, id: viz_id, api_key: @api_key),
            { id: viz_id }, @headers

        last_response.status.should == 200
        last_response.headers.should have_key('Surrogate-Key')
        last_response['Surrogate-Key'].should include(CartoDB::SURROGATE_NAMESPACE_VIZJSON)
        last_response['Surrogate-Key'].should include(get_surrogate_key(CartoDB::SURROGATE_NAMESPACE_VISUALIZATION, viz_id))

        delete api_v1_visualizations_destroy_url(user_domain: @user_1.username, id: viz_id, api_key: @api_key),
               { id: viz_id }.to_json,
               @headers
      end

      it 'joins the attributions of the layers in a layergroup in the viz.json' do
        table1 = table_factory
        table2 = table_factory
        table3 = table_factory

        payload = {
          name: 'new visualization',
          tables: [
            table1.fetch('name'),
            table2.fetch('name'),
            table3.fetch('name')
          ],
          privacy: 'public'
        }

        post api_v1_visualizations_create_url(api_key: @api_key), payload.to_json, @headers
        last_response.status.should == 200

        # Set the attributions of the tables to check that they are included in the viz.json
        table1_visualization = Carto::Visualization.find(table1["table_visualization"][:id])
        table1_visualization.update_attribute(:attributions, 'attribution1')
        table2_visualization = Carto::Visualization.find(table2["table_visualization"][:id])
        table2_visualization.update_attribute(:attributions, 'attribution2')
        table3_visualization = Carto::Visualization.find(table3["table_visualization"][:id])
        table3_visualization.update_attribute(:attributions, '')

        visualization = JSON.parse(last_response.body)

        get api_vx_visualizations_vizjson_url(id: visualization.fetch('id'), api_key: @api_key), {}, @headers

        visualization = JSON.parse(last_response.body)

        # Attribution of the layergroup layer is right
        layer_group_attributions = attributions_from_vizjson(visualization)

        layer_group_attributions.size.should == 2
        layer_group_attributions.should include('attribution1')
        layer_group_attributions.should include('attribution2')
      end

      it 'joins the attributions of the layers in a namedmap in the viz.json' do
        table1 = table_factory
        table2 = table_factory
        table3 = table_factory

        payload = {
          name: 'new visualization',
          tables: [
            table1.fetch('name'),
            table2.fetch('name'),
            table3.fetch('name')
          ],
          privacy: 'private'
        }

        post api_v1_visualizations_create_url(api_key: @api_key), payload.to_json, @headers
        last_response.status.should eq 200

        # Set the attributions of the tables to check that they are included in the viz.json
        table1_visualization = Carto::Visualization.find(table1["table_visualization"][:id])
        table1_visualization.update_attribute(:attributions, 'attribution1')
        table2_visualization = Carto::Visualization.find(table2["table_visualization"][:id])
        table2_visualization.update_attribute(:attributions, 'attribution2')
        table3_visualization = Carto::Visualization.find(table3["table_visualization"][:id])
        table3_visualization.update_attribute(:attributions, '')

        visualization = JSON.parse(last_response.body)

        # Stubs privacy of the visualization so that the viz_json generates a named_map
        Carto::Visualization.any_instance.stubs('retrieve_named_map?' => true)

        get api_vx_visualizations_vizjson_url(id: visualization.fetch('id'), api_key: @api_key), {}, @headers

        visualization = JSON.parse(last_response.body)

        named_map_attributions = attributions_from_vizjson(visualization)

        named_map_attributions.size.should == 2
        named_map_attributions.should include('attribution1')
        named_map_attributions.should include('attribution2')
      end

      it "Updates viz.json's layergroup when attributions in a related layer change" do
        table_1_attribution = 'attribution 1'
        modified_table_2_attribution = 'modified attribution 2'

        table1 = table_factory
        table2 = table_factory

        payload = {
          name: 'new visualization',
          tables: [
            table1.fetch('name'),
            table2.fetch('name')
          ],
          privacy: 'public'
        }

        post api_v1_visualizations_create_url(api_key: @api_key), payload.to_json, @headers
        last_response.status.should == 200
        visualization = JSON.parse(last_response.body)

        table1_visualization = Carto::Visualization.find(table1["table_visualization"][:id])
        table1_visualization.update_attribute(:attributions, table_1_attribution)
        table2_visualization = Carto::Visualization.find(table2["table_visualization"][:id])
        table2_visualization.update_attribute(:attributions, 'attribution 2')

        # Call to cache the vizjson after generating it
        get api_vx_visualizations_vizjson_url(id: visualization.fetch('id'), api_key: @api_key),
            { id: visualization.fetch('id') },
            @headers

        # Now force a change
        put api_v1_visualizations_update_url(api_key: @api_key, id: table2_visualization.id),
            { attributions: modified_table_2_attribution, id: table2_visualization.id }.to_json,
            @headers
        last_response.status.should == 200

        get api_vx_visualizations_vizjson_url(id: visualization.fetch('id'), api_key: @api_key),
            { id: visualization.fetch('id') },
            @headers
        visualization = JSON.parse(last_response.body)

        layer_group_attributions = attributions_from_vizjson(visualization)
        layer_group_attributions.size.should eq 2

        layer_group_attributions.should include(table_1_attribution)
        layer_group_attributions.should include(modified_table_2_attribution)
      end
    end
  end

  describe 'attributes' do
    include Carto::Factories::Visualizations
    include_context 'visualization creation helpers'

    before(:all) do
      @user_1 = create(:valid_user, private_tables_enabled: false)
      host! "#{@user_1.subdomain}.localhost.lan"
      @map, @table, @table_visualization, @visualization = create_full_visualization(Carto::User.find(@user_1.id))
    end

    after(:all) do
      destroy_full_visualization(@map, @table, @table_visualization, @visualization)
      @user_1.destroy
    end

    let(:viewer_user) { @visualization.user }

    it 'contain type, not kind, for basemaps' do
      get api_vx_visualizations_vizjson_url(id: @visualization.id, api_key: @api_key), {}, http_json_headers
      last_response.status.should eq 200
      visualization = JSON.parse(last_response.body)
      basemap_layer = visualization["layers"][0]
      basemap_layer['type'].should eq @visualization.map.base_layers.first.kind
      basemap_layer['kind'].should be_nil
    end
  end
end
