module ForestLiana
  class AssociationsController < ForestLiana::ApplicationController
    if Rails::VERSION::MAJOR < 4
      before_filter :find_resource, except: :count
      before_filter :find_association, except: :count
    else
      before_action :find_resource, except: :count
      before_action :find_association, except: :count
    end

    def index
      begin
        getter = HasManyGetter.new(@resource, @association, params, forest_user)
        getter.perform

        respond_to do |format|
          format.json { render_jsonapi(getter) }
          format.csv { render_csv(getter, @association.klass) }
        end
      rescue => error
        FOREST_REPORTER.report error
        FOREST_LOGGER.error "Association Index error: #{error}\n#{format_stacktrace(error)}"
        internal_server_error
      end
    end

    def count
      find_resource
      find_association
      begin
        getter = HasManyGetter.new(@resource, @association, params, forest_user)
        getter.count

        render serializer: nil, json: { count: getter.records_count }
      rescue => error
        FOREST_REPORTER.report error
        FOREST_LOGGER.error "Association Index Count error: #{error}\n#{format_stacktrace(error)}"
        internal_server_error
      end
    end

    def update
      begin
        updater = BelongsToUpdater.new(@resource, @association, params)
        updater.perform

        if updater.errors
          render serializer: nil, json: ForestAdmin::JSONAPI::Serializer.serialize_errors(
            updater.errors), status: 422
        else
          head :no_content
        end
      rescue => error
        FOREST_REPORTER.report error
        FOREST_LOGGER.error "Association Update error: #{error}\n#{format_stacktrace(error)}"
        internal_server_error
      end
    end

    def associate
      begin
        associator = HasManyAssociator.new(@resource, @association, params)
        associator.perform

        head :no_content
      rescue => error
        FOREST_REPORTER.report error
        FOREST_LOGGER.error "Association Associate error: #{error}\n#{format_stacktrace(error)}"
        internal_server_error
      end
    end

    def dissociate
      begin
        dissociator = HasManyDissociator.new(@resource, @association, params, forest_user)
        dissociator.perform

        head :no_content
      rescue ActiveRecord::RecordNotDestroyed => error
        render json: { errors: [{ status: :bad_request, detail: error.message }] }, status: :bad_request
      rescue => error
        FOREST_REPORTER.report error
        FOREST_LOGGER.error "Association Dissociate error: #{error}\n#{format_stacktrace(error)}"
        internal_server_error
      end
    end

    private

    def find_resource
      @resource = SchemaUtils.find_model_from_collection_name(params[:collection])

      if @resource.nil? || !@resource.ancestors.include?(ActiveRecord::Base)
        render serializer: nil, json: {status: 404}, status: :not_found
      end
    end

    def find_association
      # Rails 3 wants a :sym argument.
      @association = @resource.reflect_on_association(
        params[:association_name].try(:to_sym))

      # Only accept "many" associations
      if @association.nil? ||
        ([:belongs_to, :has_one].include?(@association.macro) &&
         params[:action] == 'index')
        render serializer: nil, json: {status: 404}, status: :not_found
      end
    end

    def resource_params
      ResourceDeserializer.new(@resource, params[:resource], true).perform
    end

    def is_sti_model?
      @is_sti_model ||= (@association.klass.inheritance_column.present? &&
        @association.klass.columns.any? { |column| column.name == @association.klass.inheritance_column })
    end

    def get_record record
      is_sti_model? ? record.becomes(@association.klass) : record
    end

    def render_jsonapi getter
      fields_to_serialize = fields_per_model(params[:fields], @association.klass)
      records = getter.records.map { |record| get_record(record) }

      includes = getter.includes_for_serialization
      if fields_to_serialize && includes.length > 0
        association_name = ForestLiana.name_for(@association.klass)
        fields_to_serialize[association_name] += ",#{includes.join(',')}"
      end

      json = serialize_models(
        records,
        {
          include: includes,
          fields: fields_to_serialize,
          params: params
        },
        getter.search_query_builder.fields_searched
      )

      render serializer: nil, json: json
    end

    def get_collection
      model_association = @resource.reflect_on_association(params[:association_name].to_sym).klass
      collection_name = ForestLiana.name_for(model_association)
      @collection ||= ForestLiana.apimap.find { |collection| collection.name.to_s == collection_name }
    end
  end
end
