require_relative '../../../spec_helper'
require_dependency 'carto/uuidhelper'

include Carto::UUIDHelper

shared_context 'layer hierarchy' do
  before(:all) do
    @user1 = create(:valid_user, private_tables_enabled: true, private_maps_enabled: true)
    @user2 = create(:valid_user, private_tables_enabled: true)
    @map = create(:carto_map_with_layers, user_id: @user1.id)
    @layer = @map.layers.first
    @visualization = create(:carto_visualization, map: @map, privacy: Carto::Visualization::PRIVACY_PRIVATE, user_id: @user1.id)
  end

  before(:each) do
    @widget = create(:widget, layer: @layer)
  end

  after(:each) do
    Carto::Widget.destroy_all
  end

  after(:all) do
    @visualization.destroy if @visualization
  end

  def response_widget_should_match_widget(response_widget, widget)
    response_widget[:id].should == widget.id
    response_widget[:order].should == widget.order
    response_widget[:type].should == widget.type
    response_widget[:title].should == widget.title
    response_widget[:layer_id].should == widget.layer.id
    response_widget[:options].should == widget.options.symbolize_keys
    response_widget[:style].should == widget.style.symbolize_keys
    if widget.source_id.present?
      response_widget[:source][:id].should eq widget.source_id
    else
      response_widget[:source].should be_nil
    end
  end

  def response_widget_should_match_payload(response_widget, payload)
    response_widget[:layer_id].should == payload[:layer_id]
    response_widget[:type].should == payload[:type]
    response_widget[:title].should == payload[:title]
    response_widget[:options].should == payload[:options].symbolize_keys
    if payload[:style].present?
      response_widget[:style].should == payload[:style].symbolize_keys
    else
      response_widget[:style].blank?.should be_true
    end
    if payload[:source].present?
      response_widget[:source][:id].should == payload[:source][:id]
    else
      response_widget[:source].should be_nil
    end
    if payload[:order].present?
      response_widget[:order].should == payload[:order]
    else
      @visualization.reload
      response_widget[:order].should == @visualization.widgets.count - 1
    end
  end

  def widget_payload(
    layer_id: @layer.id,
    type: 'formula',
    title: 'the title',
    options: { 'a field' => 'first', 'another field' => 'second' },
    order: nil,
    source: { id: 'a0' },
    style: { 'widget_style': { 'fill': 'wadus' } })

    payload = {
      layer_id: layer_id,
      type: type,
      title: title,
      options: options,
      style: style
    }

    payload[:order] = order if order
    payload[:source] = source if source

    payload
  end
end

describe Carto::Api::WidgetsController do
  include_context 'layer hierarchy'

  let(:random_map_id) { Carto::UUIDHelper.random_uuid }
  let(:random_layer_id) { Carto::UUIDHelper.random_uuid }
  let(:random_widget_id) { Carto::UUIDHelper.random_uuid }

  describe '#show' do
    it 'returns 401 for non-authenticated requests' do
      get_json widget_url(user_domain: @user1.username, map_id: random_map_id, map_layer_id: random_layer_id, id: random_widget_id), {}, http_json_headers do |response|
        response.status.should == 401
      end
    end

    it 'returns 404 for requests without matching map, layer or widget' do
      map_id = @map.id
      get_json widget_url(user_domain: @user1.username, map_id: random_map_id, map_layer_id: @layer.id, id: @widget.id, api_key: @user1.api_key), {}, http_json_headers do |response|
        response.status.should == 404
      end

      get_json widget_url(user_domain: @user1.username, map_id: map_id, map_layer_id: random_layer_id, id: @widget.id, api_key: @user1.api_key), {}, http_json_headers do |response|
        response.status.should == 404
      end

      get_json widget_url(user_domain: @user1.username, map_id: map_id, map_layer_id: @widget.layer_id, id: random_widget_id, api_key: @user1.api_key), {}, http_json_headers do |response|
        response.status.should == 404
      end
    end

    it 'returns the source widget content' do
      get_json widget_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key), {}, http_json_headers do |response|
        response.status.should == 200
        response_widget_should_match_widget(response.body, @widget)
      end
    end

    it 'returns options as json' do
      get widget_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key), {}, http_json_headers
      response.status.should == 200
      JSON.parse(response.body).class.should == Hash
      JSON.parse(response.body)['options'].class.should == Hash
    end

    it 'returns 403 if visualization is private and current user is not the owner' do
      get_json widget_url(user_domain: @user2.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user2.api_key), {}, http_json_headers do |response|
        response.status.should == 403
      end
    end

    it 'returns 403 if visualization is public and current user is not the owner' do
      Carto::Visualization.stubs(:privacy).returns('public')
      get_json widget_url(user_domain: @user2.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user2.api_key), {}, http_json_headers do |response|
        response.status.should == 403
      end
    end
  end

  describe '#create' do
    it 'creates a new widget' do
      payload = widget_payload
      post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key), payload, http_json_headers do |response|
        response.status.should == 201
        response_widget = response.body
        response_widget_should_match_payload(response_widget, payload)
        widget = Carto::Widget.find(response_widget[:id])
        response_widget_should_match_widget(response_widget, widget)
        widget.destroy
      end
    end

    it 'creates a new widget with order' do
      payload = widget_payload(order: 7)
      post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key), payload, http_json_headers do |response|
        response.status.should == 201
        response_widget = response.body
        response_widget_should_match_payload(response_widget, payload)
        widget = Carto::Widget.find(response_widget[:id])
        response_widget_should_match_widget(response_widget, widget)
        widget.destroy
      end
    end

    it 'creates a new widget without style' do
      payload = widget_payload.reject { |p| p == :style }
      post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key), payload, http_json_headers do |response|
        response.status.should == 201
        response_widget = response.body
        response_widget_should_match_payload(response_widget, payload)
        widget = Carto::Widget.find(response_widget[:id])
        response_widget_should_match_widget(response_widget, widget)
        widget.destroy
      end
    end

    it 'creates a new widget with source_id' do
      analysis = create(:analysis, visualization: @visualization, user_id: @user1.id)
      payload = widget_payload.merge(source: { id: analysis.natural_id })
      url = widgets_url(
        user_domain: @user1.username,
        map_id: @map.id,
        map_layer_id: @widget.layer_id,
        api_key: @user1.api_key)
      post_json url, payload, http_json_headers do |response|
        response.status.should eq 201
        response_widget = response.body
        response_widget[:source][:id].should eq analysis.natural_id
        widget = Carto::Widget.find(response_widget[:id])
        widget.source_id.should eq analysis.natural_id
        widget.destroy
      end
      analysis.destroy
    end

    it 'returns 404 for unknown map id' do
      post_json widgets_url(user_domain: @user1.username, map_id: random_uuid, map_layer_id: @widget.layer_id, api_key: @user1.api_key), widget_payload, http_json_headers do |response|
        response.status.should == 404
      end
    end

    it 'returns 422 if layer id do not match' do
      post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: random_uuid, api_key: @user1.api_key), widget_payload, http_json_headers do |response|
        response.status.should == 422
      end
    end

    it 'returns 422 if payload layer id do not match' do
      post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key), widget_payload(layer_id: random_uuid), http_json_headers do |response|
        response.status.should == 422
      end
    end

    it 'returns 422 if layer id do not match map' do
      other_map = create(:carto_map_with_layers, user_id: @user1.id)
      other_layer = other_map.data_layers.first
      other_layer.should_not be_nil

      post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: other_layer.id, api_key: @user1.api_key), widget_payload(layer_id: other_layer.id), http_json_headers do |response|
        response.status.should == 422
      end

      other_map.destroy
    end

    it 'returns 422 if missing source' do
      post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key), widget_payload(source: nil), http_json_headers do |response|
        response.status.should == 422
      end
    end

    it 'returns 403 if visualization is private and current user is not the owner' do
      post_json widgets_url(user_domain: @user2.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user2.api_key), widget_payload, http_json_headers do |response|
        response.status.should == 403
      end
    end

    it 'assigns consecutive orders for widgets for the same visualization' do
      # Note: First widget is already created in the layer hierarchy context
      @layer.widgets.reload.each(&:destroy)

      payload = widget_payload(layer_id: @layer.id)
      post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @layer.id, api_key: @user1.api_key), payload, http_json_headers do |response|
        response.status.should == 201
        response.body[:order].should == 0
      end
      post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @layer.id, api_key: @user1.api_key), payload, http_json_headers do |response|
        response.status.should == 201
        response.body[:order].should == 1
      end

      Carto::Widget.where(layer_id: @layer.id).destroy_all
    end
  end

  describe '#update' do
    it 'returns 422 if layer id does not match in url and payload' do
      put_json widget_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key), widget_payload(layer_id: random_uuid), http_json_headers do |response|
        response.status.should == 422
      end
    end

    it 'ignores payload id' do
      put_json widget_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key), widget_payload.merge(id: random_uuid), http_json_headers do |response|
        response.status.should == 200
      end
    end

    it 'returns 200 and updates the model' do
      analysis = create(:analysis, visualization: @visualization, user_id: @user1.id)
      new_order = @widget.order + 1
      new_type = "new #{@widget.type}"
      new_title = "new #{@widget.title}"
      new_options = @widget.options.merge(new: 'whatever')

      payload = widget_payload(
        order: new_order,
        type: new_type,
        title: new_title,
        options: new_options,
        source: { id: analysis.natural_id }
      )

      url = widget_url(
        user_domain: @user1.username,
        map_id: @map.id,
        map_layer_id: @widget.layer_id,
        id: @widget.id,
        api_key: @user1.api_key)

      put_json url, payload, http_json_headers do |response|
        response.status.should eq 200
        response_widget_should_match_payload(response.body, payload)

        Carto::Widget.find(response.body[:id])
        response_widget_should_match_widget(response.body, Carto::Widget.find(response.body[:id]))
      end
      analysis.destroy
    end
  end

  describe '#update_many' do
    it 'updates many' do
      widget2 = create(:widget, layer: @layer)

      payload = [serialize_widget(@widget).merge(title: 'wadus'), serialize_widget(widget2).merge(title: 'wadus2')]
      url = api_v3_maps_layers_update_many_widgets_url(user_domain: @user1.username,
                                                       map_id: @map.id,
                                                       api_key: @user1.api_key)
      put_json url, payload, http_json_headers do |response|
        response.status.should == 200
        response.body[0]['title'].should eq 'wadus'
        Carto::Widget.find(response.body[0]['id']).title.should eq 'wadus'
        response.body[1]['title'].should eq 'wadus2'
        Carto::Widget.find(response.body[1]['id']).title.should eq 'wadus2'
      end
    end

    it 'fails with 404 if widget is not found' do
      payload = [serialize_widget(@widget).merge(title: 'wadus')]
      @widget.destroy

      url = api_v3_maps_layers_update_many_widgets_url(user_domain: @user1.username,
                                                       map_id: @map.id,
                                                       api_key: @user1.api_key)

      put_json url, payload, http_json_headers do |response|
        response.status.should == 404
      end
    end

    it 'fails with 404 if widget does not belong to map' do
      payload = [serialize_widget(@widget).merge(title: 'wadus')]
      Carto::Widget.any_instance.stubs(:belongs_to_map?).with(@map.id).returns(false)

      url = api_v3_maps_layers_update_many_widgets_url(user_domain: @user1.username,
                                                       map_id: @map.id,
                                                       api_key: @user1.api_key)

      put_json url, payload, http_json_headers do |response|
        response.status.should == 404
        @widget.reload.title.should_not eq 'wadus'
      end
    end

    it 'fails with 404 if not writable by user' do
      payload = [serialize_widget(@widget).merge(title: 'wadus')]
      Carto::Widget.any_instance.stubs(:writable_by_user?).returns(false)

      url = api_v3_maps_layers_update_many_widgets_url(user_domain: @user1.username,
                                                       map_id: @map.id,
                                                       api_key: @user1.api_key)

      put_json url, payload, http_json_headers do |response|
        response.status.should == 403
        @widget.reload.title.should_not eq 'wadus'
      end
    end

    it 'fails if any of the widgets fails and doesn\'t update any' do
      widget2 = create(:widget, layer: @layer)
      Carto::Widget.stubs(:find).with(@widget.id).returns(@widget)
      Carto::Widget.stubs(:find).with(widget2.id).raises(ActiveRecord::RecordNotFound.new)

      payload = [serialize_widget(@widget).merge(title: 'wadus'), serialize_widget(widget2).merge(title: 'wadus2')]
      url = api_v3_maps_layers_update_many_widgets_url(user_domain: @user1.username,
                                                       map_id: @map.id,
                                                       api_key: @user1.api_key)
      put_json url, payload, http_json_headers do |response|
        response.status.should == 404
        Carto::Widget.unstub(:find)
        @widget.reload.title.should_not eq 'wadus'
        widget2.reload.title.should_not eq 'wadus2'
      end
    end

    def serialize_widget(w)
      payload = {
        id: w.id,
        layer_id: w.layer_id,
        type: w.type,
        title: w.title,
        options: w.options,
        style: w.style
      }
      payload[:order] = w.order if w.order
      payload[:source] = { id: w.source_id } if w.source_id

      payload
    end
  end

  describe '#delete' do
    it 'returns 200 and deletes the widget' do
      delete_json widget_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key), {}, http_json_headers do |response|
        response.status.should == 200
        Carto::Widget.where(id: @widget.id).first.should be_nil
      end
    end
  end
end
