
require_dependency 'carto/uuidhelper'

module Carto
  module Api
    class LayersController < ::Api::ApplicationController
      ssl_required :show, :layers_by_map, :custom_layers_by_user, :map_index, :user_index, :map_show, :user_show,
                   :map_create, :user_create, :map_update, :user_update, :map_destroy, :user_destroy

      before_filter :ensure_current_user, only: [:user_index, :user_show, :user_create, :user_update, :user_destroy]
      before_filter :load_user_layer, only: [:user_show, :user_destroy]
      before_filter :load_user_layers, only: [:user_update]

      before_filter :load_map, only: [:map_index, :map_show, :map_create, :map_update, :map_destroy]
      before_filter :ensure_writable_map, only: [:map_create, :map_update, :map_destroy]
      before_filter :load_map_layer, only: [:map_show, :map_destroy]
      before_filter :load_map_layers, only: [:map_update]

      rescue_from LoadError,
                  UnprocesableEntityError,
                  UnauthorizedError, with: :rescue_from_carto_error

      def map_index
        index(@map.layers)
      end

      def user_index
        index(@user.layers, owner: @user)
      end

      def map_show
        show(@map.user)
      end

      def user_show
        show(current_user)
      end

      def map_create
        layer = Carto::Layer.new(layer_attributes(params))
        validate_for_map(layer)

        save_layer(layer) do
          @map.layers << layer
          @map.process_privacy_in(layer)

          from_layer = Carto::Layer.where(id: params[:from_layer_id]).first if params[:from_layer_id]
          from_letter = params[:from_letter]
          update_layer_node_styles(layer, from_layer, from_letter)
        end
      end

      def user_create
        layer = Carto::Layer.new(layer_attributes(params))

        save_layer(layer) { @user.layers << layer }
      end

      def map_update
        update
      end

      def user_update
        update
      end

      def map_destroy
        destroy
      end

      def user_destroy
        destroy
      end

      private

      def validate_for_map(layer)
        unless @map.can_add_layer?(current_user, layer)
          raise UnprocesableEntityError.new('Cannot add more layers to this visualization')
        end
        unless @map.admits_layer?(layer)
          raise UnprocesableEntityError.new('Cannot add more layers of this type')
        end

        table_name = layer.options['table_name']
        user_name = layer.options['user_name']
        if user_name.present?
          table_name = user_name + '.' + table_name
        end

        if layer.data_layer?
          table_visualization = Helpers::TableLocator.new.get_by_id_or_name(
            table_name,
            current_user
          ).visualization
          unless table_visualization.has_read_permission?(current_user)
            raise UnauthorizedError.new('You do not have permission in the layer you are trying to add')
          end
        end
      end

      def index(layers, owner: nil)
        presented_layers = layers.map do |layer|
          Carto::Api::LayerPresenter.new(layer, viewer_user: current_user, user: owner || owner_user(layer)).to_poro
        end

        render_jsonp layers: presented_layers, total_entries: presented_layers.size
      end

      def show(owner)
        render_jsonp Carto::Api::LayerPresenter.new(@layer, viewer_user: current_user, user: owner).to_json
      end

      # Takes a block, executed after saving
      def save_layer(layer)
        if layer.save
          yield

          render_jsonp Carto::Api::LayerPresenter.new(layer, viewer_user: current_user).to_poro
        else
          log_error(message: 'Error creating layer', error_detail: layer.errors.full_messages)
          raise UnprocesableEntityError.new(layer.errors.full_messages)
        end
      end

      def update
        layers = @layers.map do |layer|
          layer_params = params[:layers].present? ? params[:layers].find { |p| p['id'] == layer.id } : params

          # don't allow to override table_name and user_name
          new_layer_options = layer_params[:options]
          ['table_name', 'user_name'].each do |key|
            if layer.options.include?(key)
              new_layer_options[key] = layer.options[key]
            else
              new_layer_options.delete(key)
            end
          end

          unless layer.update_attributes(layer_attributes(layer_params))
            raise UnprocesableEntityError.new(layer.errors.full_messages)
          end

          layer
        end

        if layers.count > 1
          render_jsonp(layers: layers.map { |l| Carto::Api::LayerPresenter.new(l, viewer_user: current_user).to_poro })
        else
          render_jsonp Carto::Api::LayerPresenter.new(layers[0], viewer_user: current_user).to_poro
        end
      rescue RuntimeError => e
        log_error(message: 'Error updating layer', exception: e)
        render_jsonp({ description: e.message }, 400)
      end

      def destroy
        @layer.destroy
        head :no_content
      end

      def layer_attributes(param)
        param.slice(:options, :kind, :infowindow, :tooltip, :order).permit!
      end

      def ensure_current_user
        user_id = uuid_parameter(:user_id)
        raise UnauthorizedError unless current_user.id == user_id
        @user = Carto::User.find(user_id)
      end

      def load_user_layer
        load_user_layers
        raise LoadError.new('Layer not found') unless @layers.length == 1
        @layer = @layers.first
      end

      def load_user_layers
        @layers = layers_ids.map { |id| @user.layers.find(id) }
      rescue ActiveRecord::RecordNotFound
        raise LoadError.new('Layer not found')
      end

      def load_map
        map_id = uuid_parameter(:map_id)

        # User must be owner or have permissions for the map's visualization
        @map = Carto::Map.find(map_id)
        vis = @map.visualization
        raise LoadError.new('Map not found') unless vis.try(:is_viewable_by_user?, current_user)
      rescue ActiveRecord::RecordNotFound
        raise LoadError.new('Map not found')
      end

      def ensure_writable_map
        raise UnauthorizedError unless @map.visualization.writable_by?(current_user)
      end

      def load_map_layer
        load_map_layers
        raise LoadError.new('Layer not found') unless @layers.length == 1
        @layer = @layers.first
      end

      def load_map_layers
        @layers = layers_ids.map { |id| @map.layers.find(id) }
      rescue ActiveRecord::RecordNotFound
        raise LoadError.new('Layer not found')
      end

      def layers_ids
        if params[:id]
          [params[:id]]
        elsif params[:layers]
          params[:layers].map { |l| l['id'] }
        else
          raise LoadError.new('Layer not found')
        end
      end

      def owner_user(layer)
        if current_user.nil? || @map.user.id != current_user.id
          # This keeps backwards compatibility with map user assignment. See #8974
          @map.user
        elsif layer.options && layer.options['user_name'].present?
          ::User.where(username: layer.options['user_name']).first
        else
          layer.user
        end
      end

      def update_layer_node_styles(to_layer, from_layer, from_letter)
        to_letter = to_layer.options['letter']
        to_source = to_layer.options['source']
        if from_layer.present? && from_letter.present? && to_letter.present? && to_source.present?
          move_layer_node_styles(from_layer, from_letter, to_layer, to_letter, to_source)
          update_source_layer_styles(from_layer, from_letter, to_letter, to_source)
        end
      rescue StandardError => e
        log_error(
          message: 'Error updating layer node styles',
          exception: e,
          from_layer: from_layer,
          from_letter: from_letter,
          to_layer: to_layer
        )
      end

      def move_layer_node_styles(from_layer, from_letter, to_layer, to_letter, to_source)
        source_node_number = to_source[1..-1].to_i
        nodes_to_move = from_layer.layer_node_styles.select do |lns|
          lns.source_id.starts_with?(from_letter) && lns.source_id[1..-1].to_i < source_node_number
        end

        nodes_to_move.each do |lns|
          # Move LayerNodeStyles from the old layer if given.
          lns.source_id = lns.source_id.gsub(from_letter, to_letter)
          to_layer.layer_node_styles << lns
        end
      end

      def update_source_layer_styles(from_layer, from_letter, to_letter, to_source)
        if from_letter != to_letter
          # Dragging middle node: rename the moved node
          node_id_to_fix = to_source.gsub(to_letter, from_letter)
          Carto::LayerNodeStyle.find_by(layer_id: from_layer.id, source_id: node_id_to_fix)
                               &.update(source_id: to_source)
        else
          # Dragging head node: remove unneeded old styles in the old layer
          from_layer.reload
          from_layer.layer_node_styles.select { |lns|
            lns.source_id.starts_with?(from_letter) && lns.source_id != to_source
          }.each(&:destroy)
        end
      end
    end
  end
end
