require_dependency 'carto/uuidhelper'
require_relative '../builder/builder_users_module'

module Carto
  module Api
    class AnalysesController < ::Api::ApplicationController
      include Carto::UUIDHelper
      include Carto::Builder::BuilderUsersModule

      ssl_required :show, :create, :update, :destroy

      before_filter :builder_users_only
      before_filter :load_visualization
      before_filter :check_user_can_add_analysis, only: [:show, :create, :update, :destroy]
      before_filter :load_analysis, only: [:show, :update, :destroy]

      rescue_from StandardError, with: :rescue_from_standard_error
      rescue_from Carto::LoadError, with: :rescue_from_carto_error
      rescue_from Carto::UnauthorizedError, with: :rescue_from_carto_error
      rescue_from Carto::UnprocesableEntityError, with: :rescue_from_carto_error

      def show
        render_jsonp(AnalysisPresenter.new(@analysis).to_poro)
      end

      def create
        natural_id = analysis_definition_from_request['id']

        analysis = Carto::Analysis.find_by_natural_id(@visualization.id, natural_id)
        if analysis
          analysis.analysis_definition = analysis_definition_from_request
        else
          analysis = Carto::Analysis.new(
            visualization_id: @visualization.id,
            user_id: current_user.id,
            analysis_definition: analysis_definition_from_request
          )
        end
        analysis.save!
        render_jsonp(AnalysisPresenter.new(analysis).to_poro, 201)
      end

      def update
        new_definition = analysis_definition_from_request
        new_root_node = Carto::AnalysisNode.new(new_definition.deep_symbolize_keys)
        modified_node_ids = find_modified_nodes(@analysis.analysis_node, new_root_node)
        affected_node_ids = find_affected_nodes(modified_node_ids)

        @analysis.analysis_definition = new_definition
        @analysis.save!
        purge_layer_node_style_history(affected_node_ids)

        render_jsonp(AnalysisPresenter.new(@analysis).to_poro, 200)
      end

      def destroy
        @analysis.destroy
        render_jsonp(AnalysisPresenter.new(@analysis).to_poro, 200)
      end

      private

      def purge_layer_node_style_history(node_ids)
        Carto::LayerNodeStyle.from_visualization_and_source(@visualization, node_ids).delete_all
      end

      def find_affected_nodes(modified_node_ids)
        all_visualization_nodes = @visualization.analyses.map(&:analysis_node).map(&:descendants).flatten
        all_visualization_nodes.select { |node|
          node.descendants.any? { |descendant| modified_node_ids.include?(descendant.id) }
        }.map(&:id)
      end

      def find_modified_nodes(old_root, new_root)
        old_nodes = old_root.descendants
        new_nodes = new_root.descendants

        old_ids = old_nodes.map(&:id)
        new_ids = new_nodes.map(&:id)

        kept_ids = old_ids & new_ids
        kept_ids.select do |node_id|
          old_node = old_nodes.find { |n| n.id == node_id }
          new_node = new_nodes.find { |n| n.id == node_id }
          old_node.non_child_params != new_node.non_child_params
        end
      end

      def analysis_definition_from_request
        analysis_json = json_post(request.raw_post)

        if analysis_json.nil? || analysis_json.empty?
          raise Carto::UnprocesableEntityError.new("Empty analysis")
        end

        analysis_definition = analysis_json['analysis_definition']
        if analysis_definition.nil? || analysis_definition.empty? || analysis_definition.class == String
          raise Carto::UnprocesableEntityError.new("Invalid analysis definition")
        end

        analysis_definition
      end

      def json_post(raw_post = request.raw_post)
        @json_post ||= (raw_post.present? ? JSON.parse(raw_post) : nil)
      rescue StandardError => e
        # Malformed JSON is not our business
        CartoDB.notify_warning_exception(e)
        raise UnprocesableEntityError.new("Malformed JSON: #{raw_post}")
      end

      def load_visualization
        visualization_id = params[:visualization_id]

        if payload_visualization_id.present? && payload_visualization_id != visualization_id
          raise UnprocesableEntityError.new("url vis (#{visualization_id}) != payload (#{payload_visualization_id})")
        end

        @visualization = Carto::Visualization.where(id: visualization_id).first if visualization_id
        raise LoadError.new("Visualization not found: #{visualization_id}") unless @visualization
      end

      def payload_visualization_id
        json_post.present? ? json_post['visualization_id'] : nil
      end

      def payload_analysis_id
        json_post.present? ? json_post['id'] : nil
      end

      def check_user_can_add_analysis
        if @visualization.user_id != current_user.id
          raise Carto::UnauthorizedError.new("#{current_user.id} doesn't own visualization #{@visualization.id}")
        end
      end

      def load_analysis
        if payload_analysis_id.present? && payload_analysis_id != params[:id]
          raise UnprocesableEntityError.new("url analysis (#{params[:id]}) != payload (#{payload_analysis_id})")
        end

        unless params[:id].nil?
          @analysis = Carto::Analysis.where(id: params[:id]).first if uuid?(params[:id])

          if @analysis.nil?
            @analysis = Carto::Analysis.find_by_natural_id(@visualization.id, params[:id])
          end
        end

        raise Carto::LoadError.new("Analysis not found: #{params[:id]}") unless @analysis
      end
    end
  end
end
