require 'spec_helper_min'

describe Carto::VisualizationsExportService2 do
  include NamedMapsHelper

  before(:each) do
    bypass_named_maps
  end

  let(:export) do
    {
      visualization: visualization_sync_export,
      version: '2.1.4'
    }
  end

  let(:visualization_sync_export) do
    sync = {
      id: "46a16aa4-3f67-11e8-b4f0-080027eb929e",
      name: "world_borders_hd",
      interval: 2592000,
      url: "https://common-data.carto.com/api/v2/sql?q=select+*+from+%22world_borders_hd%22&format=shp&filename=world_borders_hd",
      state: "success",
      created_at: Time.now.utc.to_json,
      updated_at: Time.now.utc.to_json,
      run_at: Time.now.utc.to_json,
      retried_times: 0,
      log_id: nil,
      error_code: nil,
      error_message: nil,
      ran_at: Time.now.utc.to_json,
      modified_at: nil,
      etag: nil,
      user_id: nil,
      visualization_id: nil,
      checksum: "",
      service_name: nil,
      service_item_id: "https://common-data.carto.com/api/v2/sql?q=select+*+from+%22world_borders_hd%22&format=shp&filename=world_borders_hd",
      type_guessing: true,
      quoted_fields_guessing: true,
      content_guessing: true,
      from_external_source: false
    }

    mapcapped_visualization_export[:mapcap][:export_json][:version] = '2.1.4'
    mapcapped_visualization_export.merge(synchronization: sync)
  end

  let(:mapcapped_visualization_export) do
    mapcap = {
      ids_json: {
        visualization_id: "04e0ddac-bea8-4e79-a362-f06b9a7396cf",
        map_id: "bf727caa-f05a-45df-baa5-c29cb4542ecd",
        layers: [
          { layer_id: "c2be46d2-d44e-49fa-a604-8814fcd150f7", widgets: [] },
          { layer_id: "dd1a006b-5791-4221-b120-4b9e1f2e93e7", widgets: [] },
          { layer_id: "f4a9823b-8de2-474f-bcc0-8b230f881a75", widgets: ["b5dcb89b-0c4a-466c-b459-cc5c8053f4ec"] },
          { layer_id: "4ffea696-e127-40bc-90cf-9e34b9a77d89", widgets: [] }
        ]
      },
      export_json: {
        visualization: base_visualization_export,
        version: '2.1.1'
      }
    }

    base_visualization_export.merge(mapcap: mapcap)
  end

  def base_visualization_export
    {
      id: '138110e4-7425-4978-843d-d7307bd70d1c',
      name: 'the name',
      description: 'the description',
      version: 3,
      type: 'derived', # derived / remote / table / slide
      tags: ['tag 1', 'tag 2'],
      privacy: 'private', # private / link / public
      source: 'the source',
      license: 'mit',
      title: 'the title',
      kind: 'geom', # geom / raster
      attributions: 'the attributions',
      bbox: '0103000000010000000500000031118AC72D246AC1A83916DE775E51C131118AC72D246AC18A9C928550D5614101D5E410F03E7' +
        '0418A9C928550D5614101D5E410F03E7041A83916DE775E51C131118AC72D246AC1A83916DE775E51C1',
      state: { json: { manolo: 'escobar' } },
      display_name: 'the display_name',
      uses_vizjson2: true,
      locked: false,
      map: {
        provider: 'leaflet',
        bounding_box_sw: '[-85.0511, -179]',
        bounding_box_ne: '[85.0511, 179]',
        center: '[34.672410587, 67.90919030050006]',
        zoom: 1,
        view_bounds_sw: '[15.775376695, -18.1672257149999]',
        view_bounds_ne: '[53.569444479, 153.985606316]',
        scrollwheel: false,
        legends: true,
        options: {
          legends: false,
          scrollwheel: true,
          layer_selector: false,
          dashboard_menu: false
        }
      },
      layers: [
        {
          options: JSON.parse('{"default":true,' +
            '"url":"http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png",' +
            '"subdomains":"abcd","minZoom":"0","maxZoom":"18","name":"Positron",' +
            '"className":"positron_rainbow_labels","attribution":"\u00a9 <a ' +
            'href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors \u00a9 ' +
            '<a href=\"https://carto.com/about-carto/\">CARTO</a>",' +
            '"labels":{"url":"http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png"},' +
            '"urlTemplate":"http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png"}').deep_symbolize_keys,
          kind: 'tiled'
        },
        {
          options: JSON.parse('{"attribution":"CARTO <a href=\"https://carto.com/about-carto/\" ' +
            'target=\"_blank\">attribution</a>","type":"CartoDB","active":true,"query":"","opacity":0.99,' +
            '"interactivity":"cartodb_id","interaction":true,"debug":false,"tiler_domain":"localhost.lan",' +
            '"tiler_port":"80","tiler_protocol":"http","sql_api_domain":"carto.com","sql_api_port":"80",' +
            '"sql_api_protocol":"http","extra_params":{"cache_policy":"persist","cache_buster":1459849314400},' +
            '"cdn_url":null,"maxZoom":28,"auto_bound":false,"visible":true,"sql_domain":"localhost.lan",' +
            '"sql_port":"80","sql_protocol":"http","tile_style_history":["#guess_ip_1 {\n  marker-fill: #FF6600;\n  ' +
            'marker-opacity: 0.9;\n  marker-width: 12;\n  marker-line-color: white;\n  marker-line-width: 3;\n  ' +
            'marker-line-opacity: 0.9;\n  marker-placement: point;\n  marker-type: ellipse;\n  ' +
            'marker-allow-overlap: true;\n}"],"style_version":"2.1.1","table_name":"guess_ip_1",' +
            '"user_name":"juanignaciosl","tile_style":"/** simple visualization */\n\n#guess_ip_1{\n  ' +
            'marker-fill-opacity: 0.9;\n  marker-line-color: #FFF;\n  marker-line-width: 1;\n  ' +
            'marker-line-opacity: 1;\n  marker-placement: point;\n  marker-type: ellipse;\n  marker-width: 10;\n  ' +
            'marker-fill: #FF6600;\n  marker-allow-overlap: true;\n}","id":"dbb6826a-09e5-4238-b81b-86a43535bf02",' +
            '"order":1,"use_server_style":true,"query_history":[],"stat_tag":"9e82a99a-fb12-11e5-80c0-080027880ca6",' +
            '"maps_api_template":"http://{user}.localhost.lan:8181","cartodb_logo":false,"no_cdn":false,' +
            '"force_cors":true,"tile_style_custom":false,"query_wrapper":null,"query_generated":false,' +
            '"wizard_properties":{"type":"polygon","properties":{"marker-width":10,"marker-fill":"#FF6600",' +
            '"marker-opacity":0.9,"marker-allow-overlap":true,"marker-placement":"point","marker-type":"ellipse",' +
            '"marker-line-width":1,"marker-line-color":"#FFF","marker-line-opacity":1,"marker-comp-op":"none",' +
            '"text-name":"None","text-face-name":"DejaVu Sans Book","text-size":10,"text-fill":"#000",' +
            '"text-halo-fill":"#FFF","text-halo-radius":1,"text-dy":-10,"text-allow-overlap":true,' +
            '"text-placement-type":"dummy","text-label-position-tolerance":0,"text-placement":"point",' +
            '"geometry_type":"point"}},"legend":{"type":"none","show_title":false,"title":"","template":"",' +
            '"visible":true}}').deep_symbolize_keys,
          kind: 'carto',
          infowindow: JSON.parse('{"fields":[],"template_name":"table/views/infowindow_light","template":"",' +
            '"alternative_names":{},"width":226,"maxHeight":180}').deep_symbolize_keys,
          tooltip: JSON.parse('{"fields":[],"template_name":"tooltip_light","template":"","alternative_names":{},' +
            '"maxHeight":180}').deep_symbolize_keys,
          active_layer: true
        },
        {
          options: JSON.parse('{"attribution":"CARTO <a href=\"http://carto.com/about-carto/\" ' +
            'target=\"_blank\">attribution</a>","type":"CartoDB","active":true,"query":"","opacity":0.99,' +
            '"interactivity":"cartodb_id","interaction":true,"debug":false,"tiler_domain":"localhost.lan",' +
            '"tiler_port":"80","tiler_protocol":"http","sql_api_domain":"carto.com","sql_api_port":"80",' +
            '"sql_api_protocol":"http","extra_params":{"cache_policy":"persist","cache_buster":1459849314400},' +
            '"cdn_url":null,"maxZoom":28,"auto_bound":false,"visible":true,"sql_domain":"localhost.lan",' +
            '"sql_port":"80","sql_protocol":"http","tile_style_history":["#guess_ip_1 {\n  marker-fill: #FF6600;\n  ' +
            'marker-opacity: 0.9;\n  marker-width: 12;\n  marker-line-color: white;\n  marker-line-width: 3;\n  ' +
            'marker-line-opacity: 0.9;\n  marker-placement: point;\n  marker-type: ellipse;\n  ' +
            'marker-allow-overlap: true;\n}"],"style_version":"2.1.1","table_name":"guess_ip_1",' +
            '"user_name":"juanignaciosl","tile_style":"/** simple visualization */\n\n#guess_ip_1{\n  ' +
            'marker-fill-opacity: 0.9;\n  marker-line-color: #FFF;\n  marker-line-width: 1;\n  ' +
            'marker-line-opacity: 1;\n  marker-placement: point;\n  marker-type: ellipse;\n  marker-width: 10;\n  ' +
            'marker-fill: #FF6600;\n  marker-allow-overlap: true;\n}","id":"dbb6826a-09e5-4238-b81b-86a43535bf02",' +
            '"order":1,"use_server_style":true,"query_history":[],"stat_tag":"9e82a99a-fb12-11e5-80c0-080027880ca6",' +
            '"maps_api_template":"http://{user}.localhost.lan:8181","cartodb_logo":false,"no_cdn":false,' +
            '"force_cors":true,"tile_style_custom":false,"query_wrapper":null,"query_generated":false,' +
            '"wizard_properties":{"type":"polygon","properties":{"marker-width":10,"marker-fill":"#FF6600",' +
            '"marker-opacity":0.9,"marker-allow-overlap":true,"marker-placement":"point","marker-type":"ellipse",' +
            '"marker-line-width":1,"marker-line-color":"#FFF","marker-line-opacity":1,"marker-comp-op":"none",' +
            '"text-name":"None","text-face-name":"DejaVu Sans Book","text-size":10,"text-fill":"#000",' +
            '"text-halo-fill":"#FFF","text-halo-radius":1,"text-dy":-10,"text-allow-overlap":true,' +
            '"text-placement-type":"dummy","text-label-position-tolerance":0,"text-placement":"point",' +
            '"geometry_type":"point"}},"legend":{"type":"none","show_title":false,"title":"","template":"",' +
            '"visible":true}}').deep_symbolize_keys,
          kind: 'carto',
          widgets: [
            {
              options: {
                aggregation: "count",
                aggregation_column: "category_t"
              },
              style: {
                widget_style: {
                  definition: {
                    fill: { color: { fixed: '#FFF' } }
                  }
                },
                auto_style: {
                  definition: {
                    fill: { color: { fixed: '#FFF' } }
                  }
                }
              },
              title: "Category category_t",
              type: "category",
              source_id: "a1",
              order: 1
            }
          ],
          legends: [
            {
              type: 'custom',
              title: 'the best legend on planet earth',
              pre_html: '<h1>Here it comes!</h1>',
              post_html: '<h2>Awesome right?</h2>',
              definition: {
                categories: [
                  {
                    title: 'foo',
                    color: '#fabada'
                  },
                  {
                    title: 'bar',
                    icon: 'super.png',
                    color: '#fabada'
                  },
                  {
                    title: 'ber'
                  },
                  {
                    title: 'baz'
                  },
                  {
                    title: 'bars',
                    icon: 'dupe.png',
                    color: '#fabada'
                  },
                  {
                    title: 'fooz',
                    color: '#fabada'
                  }
                ]
              }
            },
            {
              type: 'bubble',
              title: 'the second best legend on planet earth',
              pre_html: '<h1>Here it comes!</h1>',
              post_html: '<h2>Awesome right? But not so much</h2>',
              definition: {
                color: '#abc'
              }
            }
          ],
          infowindow: JSON.parse('{"fields":[],"template_name":"table/views/infowindow_light","template":"",' +
            '"alternative_names":{},"width":226,"maxHeight":180}').deep_symbolize_keys,
          tooltip: JSON.parse('{"fields":[],"template_name":"tooltip_light","template":"","alternative_names":{},' +
            '"maxHeight":180}').deep_symbolize_keys
        },
        {
          options: JSON.parse('{"default":true,' +
            '"url":"http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png", ' +
            '"subdomains":"abcd","minZoom":"0","maxZoom":"18","attribution":"\u00a9 <a ' +
            'href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors \u00a9 ' +
            '<a href=\"https://carto.com/about-carto/\">CARTO</a>",' +
            '"urlTemplate":"http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png","type":"Tiled",' +
            '"name":"Positron Labels"}').deep_symbolize_keys,
          kind: 'tiled'
        }
      ],
      overlays: [
        {
          options: JSON.parse('{"display":true,"x":20,"y":20}').deep_symbolize_keys,
          type: 'share'
        },
        {
          options: JSON.parse('{"display":true,"x":20,"y":150}').deep_symbolize_keys,
          type: 'loader',
          template: '<div class="loader" original-title=""></div>'
        }
      ],
      analyses: [
        { analysis_definition: { id: 'a1', type: 'source' } }
      ],
      user: { username: 'juanignaciosl' },
      permission: { access_control_list: [] },
      synchronization: nil,
      user_table: nil,
      created_at: DateTime.now.to_json,
      updated_at: DateTime.now.to_json
    }
  end

  CHANGING_LAYER_OPTIONS_KEYS = [:user_name, :id, :stat_tag].freeze

  def verify_visualization_vs_export(visualization, visualization_export, importing_user: nil, full_restore: false)
    visualization.name.should eq visualization_export[:name]
    visualization.description.should eq visualization_export[:description]
    visualization.type.should eq visualization_export[:type]
    visualization.tags.should eq visualization_export[:tags]
    visualization.privacy.should eq visualization_export[:privacy]
    visualization.source.should eq visualization_export[:source]
    visualization.license.should eq visualization_export[:license]
    visualization.title.should eq visualization_export[:title]
    visualization.kind.should eq visualization_export[:kind]
    visualization.attributions.should eq visualization_export[:attributions]
    visualization.bbox.should eq visualization_export[:bbox]
    visualization.display_name.should eq visualization_export[:display_name]
    visualization.version.should eq visualization_export[:version]

    verify_state_vs_export(visualization.state, visualization_export[:state])

    visualization.encrypted_password.should be_nil
    visualization.password_salt.should be_nil
    visualization.locked.should be_false

    verify_map_vs_export(visualization.map, visualization_export[:map])

    layers_export = visualization_export[:layers]

    verify_layers_vs_export(visualization.layers, layers_export, importing_user: importing_user)

    active_layer_order = layers_export.index { |l| l[:active_layer] }
    visualization.active_layer.should_not be_nil
    visualization.active_layer.order.should eq active_layer_order
    visualization.active_layer.id.should eq visualization.layers.find { |l| l.order == active_layer_order }.id

    verify_overlays_vs_export(visualization.overlays, visualization_export[:overlays])

    verify_analyses_vs_export(visualization.analyses, visualization_export[:analyses])

    verify_mapcap_vs_export(visualization.latest_mapcap, visualization_export[:mapcap])
    verify_sync_vs_export(visualization.synchronization, visualization_export[:synchronization], full_restore)
  end

  def verify_map_vs_export(map, map_export)
    map.provider.should eq map_export[:provider]
    map.bounding_box_sw.should eq map_export[:bounding_box_sw]
    map.bounding_box_ne.should eq map_export[:bounding_box_ne]
    map.center.should eq map_export[:center]
    map.zoom.should eq map_export[:zoom]
    map.view_bounds_sw.should eq map_export[:view_bounds_sw]
    map.view_bounds_ne.should eq map_export[:view_bounds_ne]
    map.scrollwheel.should eq map_export[:scrollwheel]
    map.legends.should eq map_export[:legends]

    map_options = map.options.with_indifferent_access
    map_options.should eq map_export[:options].with_indifferent_access
  end

  def verify_state_vs_export(state, state_export)
    state_export_json = state_export[:json] if state_export
    state_export_json ||= {}

    state.json.should eq state_export_json
  end

  def verify_mapcap_vs_export(mapcap, mapcap_export)
    return true if mapcap.nil? && mapcap_export.nil?

    deep_symbolize(mapcap.try(:ids_json)).should eq deep_symbolize(mapcap_export[:ids_json])
    deep_symbolize(mapcap.try(:export_json)).should eq deep_symbolize(mapcap_export[:export_json])
    mapcap.try(:created_at).should eq mapcap_export[:created_at]
  end

  def verify_sync_vs_export(sync, sync_export, full_restore)
    return true if sync.nil? && sync_export.nil?

    sync.id.should eq sync_export[:id] if full_restore
    sync.name.should eq sync_export[:name]
    sync.interval.should eq sync_export[:interval]
    sync.url.should eq sync_export[:url]
    sync.state.should eq sync_export[:state]
    sync.run_at.to_json.should eq sync_export[:run_at]
    sync.retried_times.should eq sync_export[:retried_times]
    sync.error_code.should eq sync_export[:error_code]
    sync.error_message.should eq sync_export[:error_message]
    sync.ran_at.to_json.should eq sync_export[:ran_at]
    sync.etag.should eq sync_export[:etag]
    sync.checksum.should eq sync_export[:checksum]
    sync.service_name.should eq sync_export[:service_name]
    sync.type_guessing.should eq sync_export[:type_guessing]
    sync.quoted_fields_guessing.should eq sync_export[:quoted_fields_guessing]
    sync.content_guessing.should eq sync_export[:content_guessing]
    sync.visualization_id.should eq sync_export[:visualization_id] if full_restore
    sync.from_external_source?.should eq sync_export[:from_external_source]

    verify_sync_service_item_id_vs_export(sync, sync_export)
  end

  def verify_sync_service_item_id_vs_export(sync, sync_export)
    if sync.service_name == 'connector' &&
       JSON.parse(sync_export[:service_item_id])['connection_id'].present?
      parameters = JSON.parse(sync_export[:service_item_id])
      parameters['connection_id'] = Carto::User.find_by(
        username: base_visualization_export[:user][:username]
      ).db_connections.find_by(name: 'AWS').id

      sync.service_item_id.should eq parameters.except('connection_name').to_json
    else
      sync.service_item_id.should eq sync_export[:service_item_id]
    end
  end

  def deep_symbolize(h)
    JSON.parse(JSON.dump(h), symbolize_names: true)
  end

  def verify_layers_vs_export(layers, layers_export, importing_user: nil)
    layers.should_not be_nil
    layers.length.should eq layers_export.length
    (0..(layers_export.length - 1)).each do |i|
      layer = layers[i]
      layer.order.should eq i

      verify_layer_vs_export(layer, layers_export[i], importing_user: importing_user)
    end
  end

  def verify_layer_vs_export(layer, layer_export, importing_user: nil)
    layer_options = layer.options.deep_symbolize_keys
    options_match = layer_options.reject { |k, _| CHANGING_LAYER_OPTIONS_KEYS.include?(k) }
    layer_export_options = layer_export[:options].deep_symbolize_keys
    export_options_match = layer_export_options.reject { |k, _| CHANGING_LAYER_OPTIONS_KEYS.include?(k) }
    options_match.should eq export_options_match

    if importing_user && layer_export_options.has_key?(:user_name)
      layer_options[:user_name].should eq importing_user.username
    else
      layer_options.has_key?(:user_name).should eq layer_export_options.has_key?(:user_name)
    end

    if importing_user && layer_export_options.has_key?(:id)
      # Persisted layer
      layer_options[:id].should_not be_nil
      layer_options[:id].should eq layer.id
    else
      layer_options.has_key?(:id).should eq layer_export_options.has_key?(:id)
      layer_options[:id].should be_nil
    end

    if importing_user && layer_export_options.has_key?(:stat_tag)
      # Persisted layer
      layer_options[:stat_tag].should_not be_nil
      layer_options[:stat_tag].should eq layer.maps.first.visualization.id
    else
      layer_options.has_key?(:stat_tag).should eq layer_export_options.has_key?(:stat_tag)
      layer_options[:stat_tag].should be_nil
    end

    layer.kind.should eq layer_export[:kind]

    if layer.infowindow
      layer.infowindow.deep_symbolize_keys.should eq layer_export[:infowindow].deep_symbolize_keys
    else
      layer.infowindow.should eq layer_export[:infowindow]
    end

    if layer.tooltip
      layer.tooltip.deep_symbolize_keys.should eq layer_export[:tooltip].deep_symbolize_keys
    else
      layer.tooltip.should eq layer_export[:tooltip]
    end

    verify_widgets_vs_export(layer.widgets, layer_export[:widgets])
    verify_legends_vs_export(layer.legends, layer_export[:legends])
  end

  def verify_widgets_vs_export(widgets, widgets_export)
    widgets_export_length = widgets_export.nil? ? 0 : widgets_export.length
    widgets.length.should eq widgets_export_length
    (0..(widgets_export_length - 1)).each do |i|
      verify_widget_vs_export(widgets[i], widgets_export[i])
    end
  end

  def verify_widget_vs_export(widget, widget_export)
    widget.type.should eq widget_export[:type]
    widget.title.should eq widget_export[:title]
    widget.options.symbolize_keys.should eq widget_export[:options]
    widget.layer.should_not be_nil
    widget.source_id.should eq widget_export[:source_id]
    widget.order.should eq widget_export[:order]
    widget.style.should eq widget_export[:style]
  end

  def verify_legends_vs_export(legends, legends_export)
    legends.each_with_index do |legend, index|
      legend_presentation = {
        definition: JSON.parse(JSON.dump(legend.definition), symbolize_names: true), # Recursive symbolize, with arrays
        post_html: legend.post_html,
        pre_html: legend.pre_html,
        title: legend.title,
        type: legend.type
      }

      legend_presentation.should eq legends_export[index]
    end
  end

  def verify_analyses_vs_export(analyses, analyses_export)
    analyses.should_not be_nil
    analyses.length.should eq analyses_export.length
    (0..(analyses_export.length - 1)).each do |i|
      verify_analysis_vs_export(analyses[i], analyses_export[i])
    end
  end

  def clean_analysis_definition(analysis_definition)
    # Remove options[:style_history] from all nested nodes for comparison
    definition_node = Carto::AnalysisNode.new(analysis_definition.deep_symbolize_keys)
    definition_node.descendants.each do |n|
      n.definition[:options].delete(:style_history) if n.definition[:options].present?
      n.definition.delete(:options) if n.definition[:options] == {}
    end

    definition_node.definition
  end

  def verify_analysis_vs_export(analysis, analysis_export)
    clean_analysis_definition(analysis.analysis_definition.deep_symbolize_keys).should eq clean_analysis_definition(analysis_export[:analysis_definition].deep_symbolize_keys)
  end

  def verify_overlays_vs_export(overlays, overlays_export)
    overlays.should_not be_nil
    overlays.length.should eq overlays_export.length
    (0..(overlays_export.length - 1)).each do |i|
      overlay = overlays[i]
      verify_overlay_vs_export(overlay, overlays_export[i])
    end
  end

  def verify_overlay_vs_export(overlay, overlay_export)
    overlay.options.deep_symbolize_keys.should eq overlay_export[:options].deep_symbolize_keys
    overlay.type.should eq overlay_export[:type]
    overlay.template.should eq overlay_export[:template]
  end

  before(:all) do
    @user = create(:carto_user, private_maps_enabled: true)
    @no_private_maps_user = create(:carto_user, private_maps_enabled: false)
  end

  after(:all) do
    bypass_named_maps
    ::User[@user.id].destroy
    ::User[@no_private_maps_user.id].destroy
  end

  describe 'importing' do
    include Carto::Factories::Visualizations

    describe '#build_visualization_from_json_export' do
      include Carto::Factories::Visualizations

      after :each do
        if export[:visualization][:synchronization]
          sync = Carto::Synchronization.where(id: export[:visualization][:synchronization][:id]).first
          sync.destroy if sync
        end
      end

      it 'fails if version is not 2' do
        expect {
          Carto::VisualizationsExportService2.new.build_visualization_from_json_export(export.merge(version: 1).to_json)
        }.to raise_error("Wrong export version")
      end

      it 'builds base visualization' do
        visualization = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(export.to_json)
        visualization_export = export[:visualization]
        verify_visualization_vs_export(visualization, visualization_export)

        visualization.id.should eq visualization_export[:id]
        visualization.user_id.should be_nil # Import build step is "user-agnostic"

        visualization.state.id.should be_nil

        map = visualization.map
        map.id.should be_nil # Not set until persistence
        map.updated_at.should be_nil # Not set until persistence
        map.user_id.should be_nil # Import build step is "user-agnostic"

        visualization.layers.each do |layer|
          layer.updated_at.should be_nil
          layer.id.should be_nil
        end

        visualization.analyses.each do |analysis|
          analysis.id.should be_nil
          analysis.user_id.should be_nil
          analysis.created_at.should be_nil
          analysis.updated_at.should be_nil
        end
      end

      it 'builds base visualization that can be persisted by VisualizationsExportPersistenceService' do
        json_export = export.to_json
        imported_viz = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(json_export)
        visualization = Carto::VisualizationsExportPersistenceService.new.save_import(@user, imported_viz)
        # Let's make sure everything is persisted
        visualization = Carto::Visualization.find(visualization.id)

        visualization_export = export[:visualization]
        visualization_export.delete(:mapcap) # Not imported here
        verify_visualization_vs_export(visualization, visualization_export, importing_user: @user)

        visualization.id.should_not be_nil
        visualization.user_id.should eq @user.id
        visualization.created_at.should_not be_nil
        visualization.updated_at.should_not be_nil

        map = visualization.map
        map.id.should_not be_nil
        map.updated_at.should_not be_nil
        map.user_id.should eq @user.id

        visualization.layers.each do |layer|
          layer.updated_at.should_not be_nil
          layer.id.should_not be_nil
        end

        visualization.analyses.each do |analysis|
          analysis.id.should_not be_nil
          analysis.user_id.should_not be_nil
          analysis.created_at.should_not be_nil
          analysis.updated_at.should_not be_nil
        end
      end

      it 'builds base vis with symbols in name that can be persisted by VisualizationsExportPersistenceService' do
        export_hash = export
        export_hash[:visualization][:name] = %{A name' with" special!"·$% symbols&/()"}
        json_export = export_hash.to_json
        json_export
        imported_viz = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(json_export)
        visualization = Carto::VisualizationsExportPersistenceService.new.save_import(@user, imported_viz)
        # Let's make sure everything is persisted
        visualization = Carto::Visualization.find(visualization.id)

        visualization_export = export[:visualization]
        visualization_export.delete(:mapcap) # Not imported here
        verify_visualization_vs_export(visualization, visualization_export, importing_user: @user)

        visualization.id.should_not be_nil
        visualization.user_id.should eq @user.id
        visualization.created_at.should_not be_nil
        visualization.updated_at.should_not be_nil

        map = visualization.map
        map.id.should_not be_nil
        map.updated_at.should_not be_nil
        map.user_id.should eq @user.id

        visualization.layers.each do |layer|
          layer.updated_at.should_not be_nil
          layer.id.should_not be_nil
        end

        visualization.analyses.each do |analysis|
          analysis.id.should_not be_nil
          analysis.user_id.should_not be_nil
          analysis.created_at.should_not be_nil
          analysis.updated_at.should_not be_nil
        end
      end

      it 'is backwards compatible with old models' do
        json_export = export.to_json
        imported_viz = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(json_export)
        visualization = Carto::VisualizationsExportPersistenceService.new.save_import(@user, imported_viz)
        # Let's make sure everything is persisted
        visualization = CartoDB::Visualization::Member.new(id: visualization.id).fetch

        # We've found some issues with json serialization that makes `public`
        visualization.map.layers.map do |layer|
          layer.public_values.should_not be_nil
        end
      end

      it 'imports private maps as public for users that have not private maps' do
        imported = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(export.to_json)
        visualization = Carto::VisualizationsExportPersistenceService.new.save_import(@user, imported)
        visualization.privacy.should eq Carto::Visualization::PRIVACY_PRIVATE
        visualization.destroy

        imported = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(export.to_json)
        visualization = Carto::VisualizationsExportPersistenceService.new.save_import(@no_private_maps_user, imported)
        visualization.privacy.should eq Carto::Visualization::PRIVACY_PUBLIC
      end

      it 'imports protected maps as private' do
        imported = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(export.to_json)
        imported.privacy = Carto::Visualization::PRIVACY_PROTECTED
        visualization = Carto::VisualizationsExportPersistenceService.new.save_import(@user, imported)
        visualization.privacy.should eq Carto::Visualization::PRIVACY_PRIVATE
      end

      it 'imports protected maps as public if the user does not have private maps enabled' do
        @user.stubs(:private_maps_enabled).returns(false)
        imported = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(export.to_json)
        imported.privacy = Carto::Visualization::PRIVACY_PROTECTED
        visualization = Carto::VisualizationsExportPersistenceService.new.save_import(@user, imported)
        visualization.privacy.should eq Carto::Visualization::PRIVACY_PUBLIC
      end

      it 'does not import more layers than the user limit' do
        old_max_layers = @user.max_layers
        @user.max_layers = 1
        @user.save

        begin
          imported = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(export.to_json)
          tiled_layers_count = imported.layers.select { |l| ['tiled'].include?(l.kind) }.count
          tiled_layers_count.should eq 2
          imported.layers.select { |l| ['carto', 'torque'].include?(l.kind) }.count.should > @user.max_layers

          visualization = Carto::VisualizationsExportPersistenceService.new.save_import(@user, imported)
          visualization.layers.select { |l| ['tiled'].include?(l.kind) }.count.should eq tiled_layers_count
          visualization.layers.select { |l| ['carto', 'torque'].include?(l.kind) }.count.should eq @user.max_layers

          destroy_visualization(visualization.id)
        ensure
          @user.max_layers = old_max_layers
          @user.save
        end
      end

      it "Doesn't register layer tables dependencies if user table doesn't exist" do
        imported = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(export.to_json)
        visualization = Carto::VisualizationsExportPersistenceService.new.save_import(@user, imported)
        layer_with_table = visualization.layers.find { |l| l.options[:table_name].present? }
        layer_with_table.should_not be_nil
        layer_with_table.user_tables.should be_empty
      end

      it "Register layer tables dependencies if user table exists" do
        user_table = create(:carto_user_table, user_id: @user.id, name: "guess_ip_1")
        imported = Carto::VisualizationsExportService2.new.build_visualization_from_json_export(export.to_json)
        visualization = Carto::VisualizationsExportPersistenceService.new.save_import(@user, imported)
        layer_with_table = visualization.layers.find { |l| l.options[:table_name].present? }
        layer_with_table.should_not be_nil
        layer_with_table.user_tables.should_not be_empty
        layer_with_table.user_tables.first.id.should eq user_table.id
      end

      it 'links synchronizations with connections' do
        connection_data = {
          name: 'AWS', connector: 'postgres',
          parameters: {
            server: '0.0.0.0', port: '5432', database: 'postgres', username: 'postgres',
            password: 'postgres'
          }
        }
        DbCartoConnectors::PostgreSQLProvider.any_instance.stubs(:check_connection).returns(true)
        connection = create(:db_connection, connection_data.merge(user: @user))
        exported_user = create(:carto_user, username: base_visualization_export[:user][:username])
        create(:db_connection, connection_data.merge(user: exported_user))

        visualization_with_sync_and_db_connection = export[:visualization]
        visualization_with_sync_and_db_connection[:synchronization].merge!(
          service_name: 'connector',
          service_item_id: {
            sql_query: 'SELECT * FROM world_borders_hd',
            import_as: 'world_borders_hd',
            connection_id: connection.id,
            connection_name: connection.name
          }.to_json
        )

        visualization = described_class.new.build_visualization_from_json_export(
          export.merge(visualization: visualization_with_sync_and_db_connection).to_json
        )
        verify_visualization_vs_export(visualization, export[:visualization])
      end

      describe 'maintains backwards compatibility with' do
        describe '2.1.1' do
          it 'defaults to locked visualizations' do
            export_2_1_1 = export
            export_2_1_1[:visualization].delete(:locked)

            service = Carto::VisualizationsExportService2.new
            visualization = service.build_visualization_from_json_export(export_2_1_1.to_json)
            expect(visualization.locked).to eq(false)
          end

          it 'sets password protected visualizations to private' do
            export_2_1_1 = export
            export_2_1_1[:visualization][:privacy] = 'password'

            service = Carto::VisualizationsExportService2.new
            visualization = service.build_visualization_from_json_export(export_2_1_1.to_json)
            imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user, visualization)

            expect(visualization.privacy).to eq('private')
          end
        end

        describe '2.1.0' do
          it 'without mark_as_vizjson2' do
            export_2_1_0 = export
            export_2_1_0[:visualization].delete(:uses_vizjson2)

            service = Carto::VisualizationsExportService2.new
            expect(service.marked_as_vizjson2_from_json_export?(export_2_1_0.to_json)).to be_false
          end

          it 'without mapcap' do
            export_2_1_0 = export
            export_2_1_0[:visualization].delete(:mapcap)

            service = Carto::VisualizationsExportService2.new
            visualization = service.build_visualization_from_json_export(export_2_1_0.to_json)
            expect(visualization.mapcapped?).to be_false
          end

          it 'without dates' do
            export_2_1_0 = export
            export_2_1_0[:visualization].delete(:created_at)
            export_2_1_0[:visualization].delete(:updated_at)

            service = Carto::VisualizationsExportService2.new
            visualization = service.build_visualization_from_json_export(export_2_1_0.to_json)
            expect(visualization.created_at).to be_nil
            expect(visualization.updated_at).to be_nil
          end
        end

        it '2.0.9 (without permission, sync nor user_table)' do
          export_2_0_9 = export
          export_2_0_9[:visualization].delete(:synchronization)
          export_2_0_9[:visualization].delete(:user_table)
          export_2_0_9[:visualization].delete(:permission)

          service = Carto::VisualizationsExportService2.new
          visualization = service.build_visualization_from_json_export(export_2_0_9.to_json)

          visualization.permission.should be_nil
          visualization.synchronization.should be_nil
        end

        it '2.0.8 (without id)' do
          export_2_0_8 = export
          export_2_0_8[:visualization].delete(:id)

          service = Carto::VisualizationsExportService2.new
          visualization = service.build_visualization_from_json_export(export_2_0_8.to_json)

          visualization.id.should be_nil
        end

        it '2.0.7 (without Widget.style)' do
          export_2_0_7 = export
          export_2_0_7[:visualization][:layers].each do |layer|
            layer.fetch(:widgets, []).each { |widget| widget.delete(:style) }
          end

          service = Carto::VisualizationsExportService2.new
          visualization = service.build_visualization_from_json_export(export_2_0_7.to_json)
          visualization.widgets.first.style.blank?.should be_true

          imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user, visualization)
          imported_viz.widgets.first.style.should == {}
        end

        describe '2.0.6 (without map options)' do
          it 'missing options' do
            export_2_0_6 = export
            export_2_0_6[:visualization][:map].delete(:options)

            service = Carto::VisualizationsExportService2.new
            visualization = service.build_visualization_from_json_export(export_2_0_6.to_json)

            visualization.map.options.should be
          end

          it 'partial options' do
            export_2_0_6 = export
            export_2_0_6[:visualization][:map][:options].delete(:dashboard_menu)

            service = Carto::VisualizationsExportService2.new
            visualization = service.build_visualization_from_json_export(export_2_0_6.to_json)

            visualization.map.options[:dashboard_menu].should be
          end
        end

        it '2.0.5 (without version)' do
          export_2_0_5 = export
          export_2_0_5[:visualization].delete(:version)

          service = Carto::VisualizationsExportService2.new
          visualization = service.build_visualization_from_json_export(export_2_0_5.to_json)

          visualization.version.should eq 2
        end

        it '2.0.4 (without Widget.order)' do
          export_2_0_4 = export
          export_2_0_4[:visualization][:layers].each do |layer|
            layer.fetch(:widgets, []).each { |widget| widget.delete(:order) }
          end

          service = Carto::VisualizationsExportService2.new
          visualization = service.build_visualization_from_json_export(export_2_0_4.to_json)

          visualization_export = export_2_0_4[:visualization]
          visualization_export[:layers][2][:widgets][0][:order] = 0 # Should assign order 0 to the first widget
          verify_visualization_vs_export(visualization, visualization_export)
        end

        it '2.0.3 (without Layer.legends)' do
          export_2_0_3 = export
          export_2_0_3[:visualization][:layers].each do |layer|
            layer.delete(:legends)
          end

          service = Carto::VisualizationsExportService2.new
          visualization = service.build_visualization_from_json_export(export_2_0_3.to_json)

          visualization_export = export_2_0_3[:visualization]
          verify_visualization_vs_export(visualization, visualization_export)
        end

        it '2.0.2 (without Visualization.state)' do
          export_2_0_2 = export
          export_2_0_2[:visualization].delete(:state)

          service = Carto::VisualizationsExportService2.new
          visualization = service.build_visualization_from_json_export(export_2_0_2.to_json)

          visualization_export = export_2_0_2[:visualization]
          verify_visualization_vs_export(visualization, visualization_export)
        end

        describe '2.0.1 (without username)' do
          it 'when not renaming tables' do
            export_2_0_1 = export
            export_2_0_1[:visualization].delete(:user)

            service = Carto::VisualizationsExportService2.new
            visualization = service.build_visualization_from_json_export(export_2_0_1.to_json)

            visualization_export = export_2_0_1[:visualization]
            verify_visualization_vs_export(visualization, visualization_export)
          end

          it 'when renaming tables' do
            export_2_0_1 = export
            export_2_0_1.delete(:user)

            service = Carto::VisualizationsExportService2.new
            built_viz = service.build_visualization_from_json_export(export_2_0_1.to_json)
            Carto::VisualizationsExportPersistenceService.any_instance.stubs(:test_query).returns(true)
            Carto::AnalysisNode.any_instance.stubs(:test_query).returns(true)
            Carto::Layer.any_instance.stubs(:test_query).returns(true)
            imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user, built_viz)
            imported_viz.layers[1].options[:user_name].should eq @user.username
          end
        end

        it '2.0.0 (without Widget.source_id)' do
          export_2_0_0 = export
          export_2_0_0[:visualization][:layers].each do |layer|
            if layer[:widgets]
              layer[:widgets].each { |widget| widget.delete(:source_id) }
            end
          end

          service = Carto::VisualizationsExportService2.new
          visualization = service.build_visualization_from_json_export(export_2_0_0.to_json)

          visualization_export = export_2_0_0[:visualization]
          verify_visualization_vs_export(visualization, visualization_export)
        end
      end
    end
  end

  describe 'exporting' do
    describe '#export_visualization_json_string' do
      include Carto::Factories::Visualizations

      before(:all) do
        bypass_named_maps
        @user = create(:carto_user, private_maps_enabled: true, private_tables_enabled: true)
        @user2 = create(:carto_user, private_maps_enabled: true)
        @map, @table, @table_visualization, @visualization = create_full_visualization(@user)
        @analysis = create(:source_analysis, visualization: @visualization, user: @user)
      end

      after(:all) do
        @analysis.destroy
        destroy_full_visualization(@map, @table, @table_visualization, @visualization)
        # This avoids connection leaking.
        ::User[@user.id].destroy
        ::User[@user2.id].destroy
      end

      describe 'visualization types' do
        before(:all) do
          bypass_named_maps
          @remote_visualization = create(:carto_visualization, type: 'remote')
        end

        after(:all) do
          @remote_visualization.destroy
        end

        it 'works for derived/canonical/remote visualizations' do
          exporter = Carto::VisualizationsExportService2.new
          expect {
            exporter.export_visualization_json_hash(@table_visualization.id, @user)
          }.not_to raise_error
          expect {
            exporter.export_visualization_json_hash(@visualization.id, @user)
          }.not_to raise_error
          expect {
            exporter.export_visualization_json_hash(@remote_visualization.id, @user)
          }.not_to raise_error
        end
      end

      it 'exports visualization' do
        exported_json = Carto::VisualizationsExportService2.new.export_visualization_json_hash(@visualization.id, @user)

        exported_json[:version].split('.')[0].to_i.should eq 2

        exported_visualization = exported_json[:visualization]
        verify_visualization_vs_export(@visualization, exported_visualization)
      end

      it 'only exports layers that a user has permissions at' do
        map = create(:carto_map, user: @user)

        private_table = create(:private_user_table, user: @user)
        public_table = create(:public_user_table, user: @user)

        private_layer = create(:carto_layer, options: { table_name: private_table.name }, maps: [map])
        create(:carto_layer, options: { table_name: public_table.name }, maps: [map])

        map, table, table_visualization, visualization = create_full_visualization(@user,
                                                                                   map: map,
                                                                                   table: private_table,
                                                                                   data_layer: private_layer)

        exported_own = Carto::VisualizationsExportService2.new.export_visualization_json_hash(visualization.id, @user)
        own_layers = exported_own[:visualization][:layers]
        own_layers.count.should eq 2
        own_layers.map { |l| l[:options][:table_name] }.sort.should eq [private_table.name, public_table.name].sort

        exported_nown = Carto::VisualizationsExportService2.new.export_visualization_json_hash(visualization.id, @user2)
        nown_layers = exported_nown[:visualization][:layers]
        nown_layers.count.should eq 1
        nown_layers.map { |l| l[:options][:table_name] }.sort.should eq [public_table.name].sort

        destroy_full_visualization(map, table, table_visualization, visualization)
      end

      it 'truncates long sync logs' do
        synchronization = create(:carto_synchronization, visualization: @table_visualization)
        @table_visualization.reload
        @table_visualization.synchronization.log.update_attribute(:entries, 'X' * 15000)
        export = Carto::VisualizationsExportService2.new.export_visualization_json_hash(@table_visualization.id, @user)
        expect(export[:visualization][:synchronization][:log][:entries].length).to be < 10000
        synchronization.destroy
      end

      it 'exports sync connections' do
        DbCartoConnectors::PostgreSQLProvider.any_instance.stubs(:check_connection).returns(true)
        connection = create(
          :db_connection,
          user: @user, name: 'AWS', connector: 'postgres',
          parameters: {
            server: '0.0.0.0', port: '5432', database: 'postgres', username: 'postgres',
            password: 'postgres'
          }
        )
        synchronization = create(
          :carto_synchronization,
          visualization: @table_visualization, service_name: 'connector',
          service_item_id: {
            sql_query: 'SELECT * FROM world_borders_hd',
            import_as: 'world_borders_hd',
            connection_id: connection.id
          }.to_json
        )

        export = described_class.new.export_visualization_json_hash(@table_visualization.id, @user)

        expect(
          JSON.parse(export[:visualization][:synchronization][:service_item_id])['connection_name']
        ).to eq(connection.name)

        synchronization.destroy
      end
    end

    describe 'exporting + importing visualizations with shared tables' do
      include_context 'organization with users helper'
      include Carto::Factories::Visualizations
      include CartoDB::Factories

      before(:all) do
        @helper = TestUserFactory.new
        @org_user_with_dash_1 = @helper.create_test_user(unique_name('user-1-'), @organization)
        @org_user_with_dash_2 = @helper.create_test_user(unique_name('user-2-'), @organization)
        @carto_org_user_with_dash_1 = Carto::User.find(@org_user_with_dash_1.id)
        @carto_org_user_with_dash_2 = Carto::User.find(@org_user_with_dash_2.id)

        @normal_user = @helper.create_test_user(unique_name('n-user-1'))
        @carto_normal_user = Carto::User.find(@normal_user.id)
      end

      before(:each) do
        bypass_named_maps
        delete_user_data @org_user_with_dash_1
        delete_user_data @org_user_with_dash_2
        Carto::VisualizationsExportPersistenceService.any_instance.stubs(:test_query).returns(true)
        Carto::AnalysisNode.any_instance.stubs(:test_query).returns(true)
        Carto::Layer.any_instance.stubs(:test_query).returns(true)
      end

      let(:table_name) { 'a_shared_table' }

      def query(table, user = nil)
        if user.present?
          "SELECT * FROM #{user.sql_safe_database_schema}.#{table.name}"
        else
          "SELECT * FROM #{table.name}"
        end
      end

      def setup_visualization_with_layer_query(owner_user, shared_user)
        @map, @table, @table_visualization, @visualization = shared_table_viz_with_layer_query(owner_user, shared_user)
      end

      def shared_table_viz_with_layer_query(owner_user, shared_user)
        table = create_table(privacy: UserTable::PRIVACY_PRIVATE, name: table_name, user_id: owner_user.id)
        user_table = Carto::UserTable.find(table.id)
        share_table_with_user(table, shared_user)

        query1 = query(table, owner_user)
        layer_options = {
          table_name: table.name,
          query: query1,
          user_name: owner_user.username,
          query_history: [query1]
        }
        layer = create(:carto_layer, options: layer_options)
        layer.options['query'].should eq query1
        layer.options['user_name'].should eq owner_user.username
        layer.options['query_history'].should eq [query1]

        map = create(:carto_map, layers: [layer], user: owner_user)
        map, table, table_visualization, visualization = create_full_visualization(owner_user,
                                                                                       map: map,
                                                                                       table: user_table,
                                                                                       data_layer: layer)

        create(:source_analysis, visualization: visualization, user: owner_user,
                                             source_table: table.name, query: query1)
        return map, table, table_visualization, visualization
      end

      after(:each) do
        ::UserTable[@table.id].destroy if @table && ::UserTable[@table.id]
        destroy_full_visualization(@map, @table, @table_visualization, @visualization)
      end

      let(:export_service) { Carto::VisualizationsExportService2.new }

      def check_username_change(visualization, table, source_user, target_user)
        query1 = query(table, source_user)
        exported = export_service.export_visualization_json_hash(visualization.id, target_user)
        layers = exported[:visualization][:layers]
        layers.should_not be_nil
        layer = layers[0]
        layer[:options]['query'].should eq query1
        layer[:options]['user_name'].should eq source_user.username
        layer[:options]['query_history'].should eq [query1]

        built_viz = export_service.build_visualization_from_hash_export(exported)
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(target_user, built_viz)
        imported_layer = imported_viz.layers[0]

        imported_layer[:options]['user_name'].should eq target_user.username

        analysis_definition = imported_viz.analyses.first.analysis_definition
        analysis_definition[:params][:query].should eq imported_layer[:options]['query']

        imported_layer[:options]['query']
      end

      it 'replaces owner name with new user name on import for user_name and query' do
        source_user = @carto_org_user_1
        target_user = @carto_org_user_2
        setup_visualization_with_layer_query(source_user, target_user)
        source_user.username.should_not include('-')
        check_username_change(@visualization, @table, source_user, target_user).should eq query(@table, target_user)
      end

      it 'replaces owner name (with dash) with new user name on import for user_name and query' do
        source_user = @carto_org_user_with_dash_1
        target_user = @carto_org_user_with_dash_2
        setup_visualization_with_layer_query(source_user, target_user)
        source_user.username.should include('-')
        check_username_change(@visualization, @table, source_user, target_user).should eq query(@table, target_user)
      end

      it 'replaces owner name (without dash) with new user name (with dash) on import for user_name and query' do
        source_user = @carto_org_user_1
        target_user = @carto_org_user_with_dash_2
        setup_visualization_with_layer_query(source_user, target_user)
        source_user.username.should_not include('-')
        target_user.username.should include('-')
        check_username_change(@visualization, @table, source_user, target_user).should eq query(@table, target_user)
      end

      it 'removes owner name from user_name and query importing into a non-organization account' do
        source_user = @carto_org_user_with_dash_1
        target_user = @carto_normal_user
        setup_visualization_with_layer_query(source_user, target_user)
        source_user.username.should include('-')
        check_username_change(@visualization, @table, source_user, target_user).should eq query(@table)
      end

      it 'removes owner name from user_name and query importing into a non-org. even if username matches' do
        name = 'wadus-user'
        org_user_same_name = @helper.create_test_user(name, @organization)
        source_user = Carto::User.find(org_user_same_name.id)
        target_user = @carto_org_user_1
        @map, @table, @table_visualization, @visualization = shared_table_viz_with_layer_query(source_user, target_user)

        query1 = query(@table, source_user)
        # Self export
        exported = export_service.export_visualization_json_hash(@visualization.id, source_user)
        layers = exported[:visualization][:layers]
        layers.should_not be_nil
        layer = layers[0]
        layer[:options]['query'].should eq query1
        layer[:options]['user_name'].should eq source_user.username
        layer[:options]['query_history'].should eq [query1]

        delete_user_data org_user_same_name
        org_user_same_name.destroy

        user_same_name = @helper.create_test_user(name)
        carto_user_same_name = Carto::User.find(user_same_name.id)

        built_viz = export_service.build_visualization_from_hash_export(exported)
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(carto_user_same_name, built_viz)
        imported_layer = imported_viz.layers[0]

        imported_layer[:options]['user_name'].should eq user_same_name.username

        analysis_definition = imported_viz.analyses.first.analysis_definition
        analysis_definition[:params][:query].should eq imported_layer[:options]['query']

        imported_layer[:options]['query'].should eq query(@table)

        delete_user_data user_same_name
        user_same_name.destroy
      end

      it 'does not replace owner name with new user name on import when new query fails' do
        Carto::VisualizationsExportPersistenceService.any_instance.stubs(:test_query).returns(false)
        Carto::AnalysisNode.any_instance.stubs(:test_query).returns(false)
        Carto::Layer.any_instance.stubs(:test_query).returns(false)
        source_user = @carto_org_user_1
        target_user = @carto_org_user_2
        setup_visualization_with_layer_query(source_user, target_user)
        check_username_change(@visualization, @table, source_user, target_user).should eq query(@table, source_user)
      end
    end

    describe 'exporting + importing visualizations with renamed tables' do
      include Carto::Factories::Visualizations
      include CartoDB::Factories

      before(:all) do
        bypass_named_maps
        @user = create(:carto_user)
      end

      before(:each) do
        Carto::VisualizationsExportPersistenceService.any_instance.stubs(:test_query).returns(true)
        Carto::AnalysisNode.any_instance.stubs(:test_query).returns(true)
        Carto::Layer.any_instance.stubs(:test_query).returns(true)
      end

      def default_query(table_name = @table.name)
        if @user.present?
          "SELECT * FROM #{@user.sql_safe_database_schema}.#{table_name}"
        else
          "SELECT * FROM #{table_name}"
        end
      end

      def setup_visualization_with_layer_query(table_name, query = nil)
        @table = create_table(name: table_name, user_id: @user.id)
        user_table = Carto::UserTable.find(@table.id)

        query ||= default_query(table_name)
        layer_options = {
          table_name: @table.name,
          query: query,
          user_name: @user.username,
          query_history: [query]
        }
        layer = create(:carto_layer, options: layer_options)
        layer.options['query'].should eq query
        layer.options['user_name'].should eq @user.username
        layer.options['table_name'].should eq @table.name
        layer.options['query_history'].should eq [query]

        map = create(:carto_map, layers: [layer], user: @user)
        @map, @table, @table_visualization, @visualization = create_full_visualization(@user,
                                                                                       map: map,
                                                                                       table: user_table,
                                                                                       data_layer: layer)
        create(:source_analysis, visualization: @visualization, user: @user,
                                             source_table: @table.name, query: query)
        create(:analysis_with_source, visualization: @visualization, user: @user,
                                                  source_table: @table.name, query: query)
      end

      after(:each) do
        ::UserTable[@table.id].destroy
        destroy_full_visualization(@map, @table, @table_visualization, @visualization)
      end

      let(:export_service) { Carto::VisualizationsExportService2.new }

      def import_and_check_query(renamed_tables, expected_table_name, expected_query = nil)
        exported = export_service.export_visualization_json_hash(@visualization.id, @user)

        built_viz = export_service.build_visualization_from_hash_export(exported)
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user, built_viz,
                                                                                     renamed_tables: renamed_tables)

        expected_query ||= default_query(expected_table_name)
        imported_layer_options = imported_viz.layers[0][:options]

        imported_layer_options['query'].should eq expected_query
        imported_layer_options['table_name'].should eq expected_table_name

        source_analysis = imported_viz.analyses.find { |a| a.analysis_node.source? }.analysis_definition
        check_analysis_defition(source_analysis, expected_table_name, expected_query)
        nested_analysis = imported_viz.analyses.find { |a| !a.analysis_node.source? }.analysis_definition
        check_analysis_defition(nested_analysis[:params][:source], expected_table_name, expected_query)
      end

      def check_analysis_defition(analysis_definition, expected_table_name, expected_query)
        analysis_definition[:options][:table_name].should eq expected_table_name
        analysis_definition[:params][:query].should eq expected_query
      end

      it 'replaces table name in default queries on import (with schema)' do
        setup_visualization_with_layer_query('tabula')
        renamed_tables = { 'tabula' => 'rasa' }
        import_and_check_query(renamed_tables, 'rasa')
      end

      it 'replaces table name in more complex queries on import' do
        setup_visualization_with_layer_query('tabula', 'SELECT COUNT(*) AS count FROM tabula WHERE tabula.yoyo=2')
        renamed_tables = { 'tabula' => 'rasa' }
        import_and_check_query(renamed_tables, 'rasa', 'SELECT COUNT(*) AS count FROM rasa WHERE rasa.yoyo=2')
      end

      it 'does not replace table name as part of a longer identifier' do
        setup_visualization_with_layer_query('tabula', 'SELECT * FROM tabula WHERE tabulacol=2')
        renamed_tables = { 'tabula' => 'rasa' }
        import_and_check_query(renamed_tables, 'rasa', 'SELECT * FROM rasa WHERE tabulacol=2')
      end

      it 'does not replace table name when query fails' do
        Carto::VisualizationsExportPersistenceService.any_instance.stubs(:test_query).returns(false)
        Carto::AnalysisNode.any_instance.stubs(:test_query).returns(false)
        Carto::Layer.any_instance.stubs(:test_query).returns(false)
        setup_visualization_with_layer_query('tabula', 'SELECT * FROM tabula WHERE tabulacol=2')
        renamed_tables = { 'tabula' => 'rasa' }
        import_and_check_query(renamed_tables, 'rasa', 'SELECT * FROM tabula WHERE tabulacol=2')
      end
    end
  end

  describe 'exporting + importing' do
    include Carto::Factories::Visualizations

    def verify_visualizations_match(imported_visualization,
                                    original_visualization,
                                    importing_user: nil,
                                    imported_name: original_visualization.name,
                                    imported_privacy: original_visualization.privacy,
                                    full_restore: false)
      imported_visualization.id.should eq original_visualization.id if full_restore
      imported_visualization.name.should eq imported_name
      imported_visualization.description.should eq original_visualization.description
      imported_visualization.type.should eq original_visualization.type
      imported_visualization.tags.should eq original_visualization.tags
      imported_visualization.privacy.should eq imported_privacy
      imported_visualization.source.should eq original_visualization.source
      imported_visualization.license.should eq original_visualization.license
      imported_visualization.title.should eq original_visualization.title
      imported_visualization.kind.should eq original_visualization.kind
      imported_visualization.attributions.should eq original_visualization.attributions
      imported_visualization.bbox.should eq original_visualization.bbox
      imported_visualization.display_name.should eq original_visualization.display_name
      imported_visualization.version.should eq original_visualization.version

      verify_maps_match(imported_visualization.map, original_visualization.map)

      imported_layers = imported_visualization.layers
      original_layers = original_visualization.layers

      verify_layers_match(imported_layers, original_layers, importing_user: importing_user)

      imported_active_layer = imported_visualization.active_layer
      imported_active_layer.should_not be_nil
      active_layer_order = original_visualization.active_layer.order
      imported_active_layer.order.should eq active_layer_order
      imported_active_layer.id.should eq imported_layers.find_by_order(active_layer_order).id

      verify_overlays_match(imported_visualization.overlays, original_visualization.overlays)

      verify_analyses_match(imported_visualization.analyses, original_visualization.analyses)

      verify_mapcap_match(imported_visualization.latest_mapcap, original_visualization.latest_mapcap)

      verify_sync_match(imported_visualization.synchronization, original_visualization.synchronization, importing_user, full_restore)
    end

    def verify_maps_match(imported_map, original_map)
      imported_map.provider.should eq original_map.provider
      imported_map.bounding_box_sw.should eq original_map.bounding_box_sw
      imported_map.bounding_box_ne.should eq original_map.bounding_box_ne
      imported_map.center.should eq original_map.center
      imported_map.zoom.should eq original_map.zoom
      imported_map.view_bounds_sw.should eq original_map.view_bounds_sw
      imported_map.view_bounds_ne.should eq original_map.view_bounds_ne
      imported_map.scrollwheel.should eq original_map.scrollwheel
      imported_map.legends.should eq original_map.legends

      map_options = imported_map.options.with_indifferent_access
      original_map_options = original_map.options.with_indifferent_access
      map_options.should eq original_map_options
    end

    def verify_layers_match(imported_layers, original_layers, importing_user: nil)
      imported_layers.should_not be_nil
      imported_layers.length.should eq original_layers.length
      (0..(original_layers.length - 1)).each do |i|
        layer = imported_layers[i]
        layer.order.should eq i

        verify_layer_match(layer, original_layers[i], importing_user: importing_user)
      end
    end

    def verify_layer_match(imported_layer, original_layer, importing_user: nil)
      imported_layer_options = imported_layer.options.deep_symbolize_keys
      imported_options_match = imported_layer_options.reject { |k, _| CHANGING_LAYER_OPTIONS_KEYS.include?(k) }
      original_layer_options = original_layer.options.deep_symbolize_keys
      original_options_match = original_layer_options.reject { |k, _| CHANGING_LAYER_OPTIONS_KEYS.include?(k) }
      imported_options_match.should eq original_options_match

      if importing_user && original_layer_options.has_key?(:user_name)
        imported_layer_options[:user_name].should_not be_nil
        imported_layer_options[:user_name].should eq importing_user.username
      else
        imported_layer_options.has_key?(:user_name).should eq original_layer_options.has_key?(:user_name)
        imported_layer_options[:user_name].should be_nil
      end

      if importing_user && original_layer_options.has_key?(:id)
        # Persisted layer
        imported_layer_options[:id].should_not be_nil
        imported_layer_options[:id].should eq imported_layer.id
      else
        imported_layer_options.has_key?(:id).should eq original_layer_options.has_key?(:id)
        imported_layer_options[:id].should be_nil
      end

      if importing_user && original_layer_options.has_key?(:stat_tag)
        # Persisted layer
        imported_layer_options[:stat_tag].should_not be_nil
        imported_layer_options[:stat_tag].should eq imported_layer.maps.first.visualization.id
      else
        imported_layer_options.has_key?(:stat_tag).should eq original_layer_options.has_key?(:stat_tag)
        imported_layer_options[:stat_tag].should be_nil
      end

      imported_layer.kind.should eq original_layer.kind
      imported_layer.infowindow.should eq original_layer.infowindow
      imported_layer.tooltip.should eq original_layer.tooltip

      verify_widgets_match(imported_layer.widgets, original_layer.widgets)
    end

    def verify_widgets_match(imported_widgets, original_widgets)
      original_widgets_length = original_widgets.nil? ? 0 : original_widgets.length
      imported_widgets.length.should eq original_widgets_length
      (0..(original_widgets_length - 1)).each do |i|
        imported_widget = imported_widgets[i]
        imported_widget.order.should eq i

        verify_widget_match(imported_widget, original_widgets[i])
      end
    end

    def verify_widget_match(imported_widget, original_widget)
      imported_widget.type.should eq original_widget.type
      imported_widget.title.should eq original_widget.title
      imported_widget.options.should eq original_widget.options
      imported_widget.layer.should_not be_nil
      imported_widget.source_id.should eq original_widget.source_id
      imported_widget.styke.should eq original_widget.style
    end

    def verify_analyses_match(imported_analyses, original_analyses)
      imported_analyses.should_not be_nil
      imported_analyses.length.should eq original_analyses.length
      (0..(imported_analyses.length - 1)).each do |i|
        verify_analysis_match(imported_analyses[i], original_analyses[i])
      end
    end

    def verify_analysis_match(imported_analysis, original_analysis)
      imported_analysis.analysis_definition.should eq original_analysis.analysis_definition
    end

    def verify_overlays_match(imported_overlays, original_overlays)
      imported_overlays.should_not be_nil
      imported_overlays.length.should eq original_overlays.length
      (0..(imported_overlays.length - 1)).each do |i|
        imported_overlay = imported_overlays[i]
        imported_overlay.order.should eq (i + 1)
        verify_overlay_match(imported_overlay, original_overlays[i])
      end
    end

    def verify_overlay_match(imported_overlay, original_overlay)
      imported_overlay.options.should eq original_overlay.options
      imported_overlay.type.should eq original_overlay.type
      imported_overlay.template.should eq original_overlay.template
    end

    def verify_mapcap_match(imported_mapcap, original_mapcap)
      return true if imported_mapcap.nil? && original_mapcap.nil?

      imported_mapcap.export_json.should eq original_mapcap.export_json
      imported_mapcap.ids_json.should eq original_mapcap.ids_json
    end

    def verify_sync_match(imported_sync, original_sync, importing_user, full_restore)
      return true if imported_sync.nil? && original_sync.nil?

      imported_sync.id.should eq original_sync.id if full_restore
      imported_sync.name.should eq original_sync.name
      imported_sync.interval.should eq original_sync.interval
      imported_sync.url.should eq original_sync.url
      imported_sync.state.should eq original_sync.state
      imported_sync.run_at.utc.to_s.should eq original_sync.run_at.utc.to_s
      imported_sync.retried_times.should eq original_sync.retried_times
      imported_sync.error_code.should eq original_sync.error_code
      imported_sync.error_message.should eq original_sync.error_message
      imported_sync.ran_at.utc.to_s.should eq original_sync.ran_at.utc.to_s
      imported_sync.etag.should eq original_sync.etag
      imported_sync.checksum.should eq original_sync.checksum
      imported_sync.user_id.should eq importing_user.try(:id) || original_sync.user_id
      imported_sync.service_name.should eq original_sync.service_name
      imported_sync.type_guessing.should eq original_sync.type_guessing
      imported_sync.quoted_fields_guessing.should eq original_sync.quoted_fields_guessing
      imported_sync.content_guessing.should eq original_sync.content_guessing
      imported_sync.visualization_id.should eq original_sync.visualization_id if full_restore
      imported_sync.from_external_source?.should eq original_sync.from_external_source?

      verify_sync_service_item_id_match(imported_sync, original_sync, importing_user)
    end

    def verify_sync_service_item_id_match(imported_sync, original_sync, importing_user)
      if imported_sync.service_name == 'connector' &&
         JSON.parse(imported_sync[:service_item_id])['connection_id'].present?

        parameters = JSON.parse(imported_sync[:service_item_id])
        parameters['connection_id'] = importing_user.db_connections.find_by(name: 'AWS').id

        imported_sync.service_item_id.should eq parameters.to_json
      else
        imported_sync.service_item_id.should eq original_sync.service_item_id
      end
    end

    def verify_data_import_match(imported_data_import, original_data_import)
      return true if imported_data_import.nil? && original_data_import.nil?

      imported_data_import.synchronization_id.should eq original_data_import.synchronization_id
    end

    describe 'maps' do
      before(:all) do
        @user = create(:carto_user, private_maps_enabled: true)
        @user2 = create(:carto_user, private_maps_enabled: true)
      end

      after(:all) do
        # This avoids connection leaking.
        ::User[@user.id].destroy
        ::User[@user2.id].destroy
      end

      before(:each) do
        bypass_named_maps
        @map, @table, @table_visualization, @visualization = create_full_visualization(@user)
        carto_layer = @visualization.layers.find { |l| l.kind == 'carto' }
        carto_layer.options[:user_name] = @user.username
        carto_layer.save
        @analysis = create(:source_analysis, visualization: @visualization, user: @user)
      end

      after(:each) do
        @analysis.destroy
        destroy_full_visualization(@map, @table, @table_visualization, @visualization)
      end

      let(:export_service) { Carto::VisualizationsExportService2.new }

      it 'importing an exported visualization should create a new visualization with matching metadata' do
        exported_string = export_service.export_visualization_json_string(@visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user, built_viz)

        imported_viz = Carto::Visualization.find(imported_viz.id)
        verify_visualizations_match(imported_viz,
                                    @visualization,
                                    importing_user: @user,
                                    imported_name: "#{@visualization.name} Import")

        destroy_visualization(imported_viz.id)
      end

      it 'importing an exported visualization several times should change imported name' do
        exported_string = export_service.export_visualization_json_string(@visualization.id, @user)

        built_viz = export_service.build_visualization_from_json_export(exported_string)
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user, built_viz)
        imported_viz.name.should eq "#{@visualization.name} Import"

        built_viz = export_service.build_visualization_from_json_export(exported_string)
        imported_viz2 = Carto::VisualizationsExportPersistenceService.new.save_import(@user, built_viz)
        imported_viz2.name.should eq "#{@visualization.name} Import 2"

        destroy_visualization(imported_viz.id)
        destroy_visualization(imported_viz2.id)
      end

      it 'importing an exported visualization into another account should change layer user_name option' do
        exported_string = export_service.export_visualization_json_string(@visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

        imported_viz = Carto::Visualization.find(imported_viz.id)
        verify_visualizations_match(imported_viz, @visualization, importing_user: @user2)

        destroy_visualization(imported_viz.id)
      end

      it 'importing an exported visualization should not keep the vizjson2 flag, but should return it' do
        @visualization.mark_as_vizjson2
        exported_string = export_service.export_visualization_json_string(@visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

        expect(imported_viz.uses_vizjson2?).to be_false
        expect(export_service.marked_as_vizjson2_from_json_export?(exported_string)).to be_true

        destroy_visualization(imported_viz.id)
      end

      it 'importing a password-protected visualization keeps the password' do
        @visualization.privacy = 'password'
        @visualization.password = 'super_secure_secret'
        @visualization.save!

        exported_string = export_service.export_visualization_json_string(@visualization.id, @user, with_password: true)
        built_viz = export_service.build_visualization_from_json_export(exported_string)
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

        verify_visualizations_match(imported_viz, @visualization, importing_user: @user2)
        expect(imported_viz.password_protected?).to be_true
        expect(imported_viz.has_password?).to be_true
        expect(imported_viz.password_valid?('super_secure_secret')).to be_true

        destroy_visualization(imported_viz.id)
      end

      it 'raises an unauthorized error when the import is over public map quota' do
        @visualization.privacy = 'password'
        @visualization.user_id = @user2.id
        @visualization.password = 'super_secure_secret'
        @visualization.save!

        @user2.stubs(:public_map_quota).returns(1)
        exported_string = export_service.export_visualization_json_string(@visualization.id, @user, with_password: true)
        built_viz = export_service.build_visualization_from_json_export(exported_string)
        expect {
          Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)
        }.to raise_error(Carto::UnauthorizedError)
        @user2.unstub(:public_map_quota)
      end

      it 'raises an unauthorized error when the import is over private map quota' do
        @visualization.privacy = 'private'
        @visualization.user_id = @user2.id
        @visualization.save!

        @user2.stubs(:private_map_quota).returns(1)
        exported_string = export_service.export_visualization_json_string(@visualization.id, @user, with_password: true)
        built_viz = export_service.build_visualization_from_json_export(exported_string)
        expect {
          Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)
        }.to raise_error(Carto::UnauthorizedError)
        @user2.unstub(:private_map_quota)
      end

      describe 'if full_restore is' do
        before(:each) do
          @visualization.permission.acl = [{
            type: 'user',
            entity: {
              id: @user2.id,
              username: @user2.username
            },
            access: 'r'
          }]
          @visualization.permission.save
          @visualization.locked = true
          @visualization.save!
          @visualization.create_mapcap!
          @visualization.reload
        end

        it 'false, it should generate a random uuid and blank permission, no mapcap and unlocked' do
          exported_string = export_service.export_visualization_json_string(@visualization.id, @user)
          original_attributes = @visualization.attributes.symbolize_keys
          built_viz = export_service.build_visualization_from_json_export(exported_string)
          original_id = built_viz.id

          Delorean.jump(10)
          imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)
          Delorean.back_to_the_present
          imported_viz.id.should_not eq original_id
          imported_viz.permission.acl.should be_empty
          imported_viz.shared_entities.count.should be_zero
          imported_viz.mapcapped?.should be_false
          expect(imported_viz.created_at.to_s).not_to eq original_attributes[:created_at].to_s
          expect(imported_viz.updated_at.to_s).not_to eq original_attributes[:updated_at].to_s
          puts imported_viz.created_at, original_attributes[:created_at]

          destroy_visualization(imported_viz.id)
        end

        it 'true, it should keep the imported uuid, permission, mapcap, synchroniztion and locked' do
          sync = create(:carto_synchronization, visualization: @visualization)
          exported_string = export_service.export_visualization_json_string(@visualization.id, @user)
          original_attributes = @visualization.attributes.symbolize_keys
          built_viz = export_service.build_visualization_from_json_export(exported_string)
          test_id = random_uuid
          built_viz.id = test_id
          sync_id = sync.id
          sync.destroy

          Delorean.jump(10)
          imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz, full_restore: true)
          Delorean.back_to_the_present
          imported_viz.id.should eq test_id
          imported_viz.permission.acl.should_not be_empty
          imported_viz.shared_entities.count.should eq 1
          imported_viz.shared_entities.first.recipient_id.should eq @user2.id
          imported_viz.mapcapped?.should be_true
          imported_viz.locked?.should be_true
          imported_viz.synchronization.should be
          imported_viz.synchronization.id.should eq sync_id
          expect(imported_viz.created_at.to_s).to eq original_attributes[:created_at].to_s
          expect(imported_viz.updated_at.to_s).to eq original_attributes[:updated_at].to_s

          destroy_visualization(imported_viz.id)
        end
      end

      describe 'basemaps' do
        describe 'custom' do
          before(:each) do
            @user2.reload
            @user2.layers.clear

            carto_layer = @visualization.layers.find { |l| l.kind == 'carto' }
            carto_layer.options[:user_name] = @user.username
            carto_layer.save
          end

          let(:mapbox_layer) do
            {
              urlTemplate: 'https://api.mapbox.com/styles/v1/wadus/ciu4g7i1500t62iqgcgt9xwez/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1IaoianVhcmlnbmFjaW9zbCIsImEiOiJjaXU1ZzZmcXMwMDJ0MnpwYWdiY284dTFiIn0.uZuarRtgWTv20MdNCq856A',
              attribution: nil,
              maxZoom: 21,
              minZoom: 0,
              name: '',
              category: 'Mapbox',
              type: 'Tiled',
              className: 'httpsapimapboxcomstylesv1wadusiu4g7i1500t62iqgcgt9xweztiles256zxy2xaccess_tokenpkeyj1iaoianvhbmlnbmfjaw9zbcisimeioijjaxu1zzzmcxmwmdj0mnpwywdiy284dtfiin0uzuarrtgwtv20mdncq856a'
            }
          end

          it 'importing an exported visualization with a custom basemap should add the layer to user layers' do
            base_layer = @visualization.base_layers.first
            base_layer.options = mapbox_layer
            base_layer.save

            user_layers = @user2.layers
            user_layers.find { |l| l.options['urlTemplate'] == base_layer.options['urlTemplate'] }.should be_nil
            user_layers_count = user_layers.count

            exported_string = export_service.export_visualization_json_string(@visualization.id, @user)
            built_viz = export_service.build_visualization_from_json_export(exported_string)
            imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

            @user2.reload
            new_user_layers = @user2.layers

            new_user_layers.count.should eq user_layers_count + 1
            new_user_layers.find { |l| l.options['urlTemplate'] == base_layer.options['urlTemplate'] }.should_not be_nil

            destroy_visualization(imported_viz.id)

            # Layer should not be the map one
            @user2.reload
            new_user_layers = @user2.layers

            new_user_layers.count.should eq user_layers_count + 1
          end

          it 'importing an exported visualization with a custom basemap twice should add the layer to user layers only once' do
            base_layer = @visualization.base_layers.first
            base_layer.options = mapbox_layer
            base_layer.save

            user_layers = @user2.layers
            user_layers.find { |l| l.options['urlTemplate'] == base_layer.options['urlTemplate'] }.should be_nil
            user_layers_count = user_layers.count

            exported_string = export_service.export_visualization_json_string(@visualization.id, @user)
            built_viz = export_service.build_visualization_from_json_export(exported_string)
            imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)
            built_viz = export_service.build_visualization_from_json_export(exported_string)
            imported_viz2 = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

            @user2.reload
            new_user_layers = @user2.layers

            new_user_layers.count.should eq user_layers_count + 1
            destroy_visualization(imported_viz2.id)
            destroy_visualization(imported_viz.id)
          end
        end
      end
    end

    describe 'datasets' do
      before(:all) do
        @sequel_user = create(:valid_user, :private_tables, private_maps_enabled: true, table_quota: nil)
        @user = Carto::User.find(@sequel_user.id)

        @sequel_user2 = create(:valid_user, :private_tables, private_maps_enabled: true, table_quota: nil)
        @user2 = Carto::User.find(@sequel_user2.id)

        @sequel_user_no_private_tables = create(:valid_user, private_maps_enabled: true, table_quota: nil)
        @user_no_private_tables = Carto::User.find(@sequel_user_no_private_tables.id)
      end

      after(:all) do
        @sequel_user.destroy
      end

      before(:each) do
        bypass_named_maps
        @table = create(:carto_user_table, :full, user: @user)
        @table_visualization = @table.table_visualization
        @table_visualization.reload
        @table_visualization.active_layer = @table_visualization.data_layers.first
        @table_visualization.save
      end

      after(:each) do
        @table_visualization.destroy
      end

      let(:export_service) { Carto::VisualizationsExportService2.new }

      it 'importing an exported dataset should keep the user_table' do
        exported_string = export_service.export_visualization_json_string(@table_visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)

        # Create user db table (destroyed above)
        @user2.in_database.execute("CREATE TABLE #{@table_visualization.name} (cartodb_id int)")
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

        imported_viz = Carto::Visualization.find(imported_viz.id)
        verify_visualizations_match(imported_viz, @table_visualization, importing_user: @user2)
        imported_viz.map.user_table.should be

        destroy_visualization(imported_viz.id)
      end

      it 'importing a dataset with an existing name should raise an error' do
        exported_string = export_service.export_visualization_json_string(@table_visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)

        expect { Carto::VisualizationsExportPersistenceService.new.save_import(@user, built_viz) }.to raise_error(
          'Cannot rename a dataset during import')
      end

      it 'importing a dataset without a table should raise an error' do
        exported_string = export_service.export_visualization_json_string(@table_visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)

        expect { Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz) }.to raise_error(
          'Cannot import a dataset without physical table')
      end

      it 'importing an exported dataset should keep the synchronization' do
        create(:carto_synchronization, visualization: @table_visualization)

        exported_string = export_service.export_visualization_json_string(@table_visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)

        # Create user db table (destroyed above)
        @user2.in_database.execute("CREATE TABLE #{@table_visualization.name} (cartodb_id int)")
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

        imported_viz = Carto::Visualization.find(imported_viz.id)

        verify_visualizations_match(imported_viz, @table_visualization, importing_user: @user2, full_restore: false)
        sync = imported_viz.synchronization
        sync.should be
        sync.user_id.should eq @user2.id
        sync.log.user_id.should eq @user2.id

        destroy_visualization(imported_viz.id)
      end

      it 'imports a synchronization without log' do
        create(:carto_synchronization, visualization: @table_visualization)
        @table_visualization.synchronization.log = nil
        @table_visualization.synchronization.save

        exported_string = export_service.export_visualization_json_string(@table_visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)

        # Create user db table (destroyed above)
        @user2.in_database.execute("CREATE TABLE #{@table_visualization.name} (cartodb_id int)")
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

        imported_viz = Carto::Visualization.find(imported_viz.id)
        sync = imported_viz.synchronization
        sync.should be
        sync.log.should be_nil

        destroy_visualization(imported_viz.id)
      end

      it 'imports a connector synchronization should keep the link with the DB connection' do
        connection_data = {
          user: @user, name: 'AWS', connector: 'postgres',
          parameters: {
            server: '0.0.0.0', port: '5432', database: 'postgres', username: 'postgres',
            password: 'postgres'
          }
        }
        connection = create(:db_connection, connection_data.merge(user: @user))
        create(
          :carto_synchronization,
          visualization: @table_visualization, service_name: 'connector',
          service_item_id: {
            sql_query: 'SELECT * FROM world_borders_hd',
            import_as: 'world_borders_hd',
            connection_id: connection.id
          }.to_json
        )
        imported_connection = create(:db_connection, connection_data.merge(user: @user2))

        exported_string = JSON.parse(
          export_service.export_visualization_json_string(@table_visualization.id, @user)
        )
        exported_string['visualization']['user']['username'] = @user2.username
        built_viz = export_service.build_visualization_from_json_export(exported_string.to_json)

        # Create user db table (destroyed above)
        @user2.in_database.execute("CREATE TABLE #{@table_visualization.name} (cartodb_id int)")
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

        imported_viz = Carto::Visualization.find(imported_viz.id)

        verify_visualizations_match(imported_viz, @table_visualization, importing_user: @user2, full_restore: false)
        sync = imported_viz.synchronization
        sync.should be
        sync.user_id.should eq @user2.id

        service_item_id = JSON.parse(sync.service_item_id)
        service_item_id.except('connection_id').should eq(
          JSON.parse(@table_visualization.synchronization.service_item_id).except('connection_id')
        )
        service_item_id['connection_id'].should eq imported_connection.id

        destroy_visualization(imported_viz.id)
      end

      it 'imports an exported dataset with external data import without a synchronization' do
        @table.data_import = create(:data_import, user: @user2, table_id: @table.id)
        @table.save!
        edi = create(:external_data_import_with_external_source, data_import: @table.data_import)
        exported_string = export_service.export_visualization_json_string(@table_visualization.id, @user2)
        built_viz = export_service.build_visualization_from_json_export(exported_string)
        @user2.in_database.execute("CREATE TABLE #{@table_visualization.name} (cartodb_id int)")

        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

        imported_viz.should be
        destroy_visualization(imported_viz.id)
      end

      it 'keeps private privacy is private tables enabled' do
        @table_visualization.update_attributes(privacy: 'private')
        exported_string = export_service.export_visualization_json_string(@table_visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)

        # Create user db table (destroyed above)
        @user2.in_database.execute("CREATE TABLE #{@table_visualization.name} (cartodb_id int)")
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user2, built_viz)

        imported_viz = Carto::Visualization.find(imported_viz.id)
        verify_visualizations_match(imported_viz, @table_visualization, importing_user: @user2,
                                                                        imported_privacy: 'private')
        expect(imported_viz.user_table).to(be)
        expect(imported_viz.user_table.privacy).to(eq(Carto::UserTable::PRIVACY_PRIVATE))

        destroy_visualization(imported_viz.id)
      end

      it 'converts to privacy public if private tables disabled' do
        @table_visualization.update_attributes(privacy: 'private')
        exported_string = export_service.export_visualization_json_string(@table_visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)

        # Create user db table (destroyed above)
        @user_no_private_tables.in_database.execute("CREATE TABLE #{@table_visualization.name} (cartodb_id int)")
        imported_viz = Carto::VisualizationsExportPersistenceService.new.save_import(@user_no_private_tables, built_viz)

        imported_viz = Carto::Visualization.find(imported_viz.id)
        verify_visualizations_match(imported_viz, @table_visualization, importing_user: @user_no_private_tables,
                                                                        imported_privacy: 'public')
        expect(imported_viz.user_table).to(be)
        expect(imported_viz.user_table.privacy).to(eq(Carto::UserTable::PRIVACY_PUBLIC))

        destroy_visualization(imported_viz.id)
      end

      it 'raises an unauthorized error when the import is over public dataset quota' do
        @table_visualization.update_attributes(privacy: 'public')
        exported_string = export_service.export_visualization_json_string(@table_visualization.id, @user)
        built_viz = export_service.build_visualization_from_json_export(exported_string)

        # Create user db table (destroyed above)
        @user_no_private_tables.in_database.execute("CREATE TABLE #{@table_visualization.name} (cartodb_id int)")
        @user_no_private_tables.public_dataset_quota = 0
        @user_no_private_tables.save!

        expect {
          Carto::VisualizationsExportPersistenceService.new.save_import(@user_no_private_tables, built_viz)
        }.to raise_error(Carto::UnauthorizedError)
      end
    end
  end
end
