# frozen_string_literal: true

module CodeOcean
  class FilesController < ApplicationController
    include CommonBehavior
    include FileParameters

    before_action :set_content_type_nosniff
    # Overwrite the CSP header and some default actions for the :render_protected_upload action
    content_security_policy false, only: :render_protected_upload
    skip_before_action :deny_access_from_render_host, only: :render_protected_upload
    skip_before_action :verify_authenticity_token, only: :render_protected_upload
    skip_before_action :set_sentry_context, only: :render_protected_upload
    skip_before_action :require_fully_authenticated_user!, only: :render_protected_upload

    # In case the .realpath cannot resolve a file (for example because it is no longer available)
    rescue_from Errno::ENOENT, with: :render_not_found

    def authorize!
      authorize(@file)
    end
    private :authorize!

    def show_protected_upload
      @file = CodeOcean::File.find(params[:id])
      authorize!
      # The `@file.name_with_extension` is assembled based on the user-selected file type, not on the actual file name stored on disk.
      raise Pundit::NotAuthorizedError if @embed_options[:disable_download] || @file.filepath != params[:filename] || @file.native_file.blank?

      real_location = Pathname(@file.native_file.current_path).realpath
      send_file(real_location, type: 'application/octet-stream', filename: @file.name_with_extension, disposition: 'attachment')
    end

    def render_protected_upload
      # Set @current_user with a new *learner* for Pundit checks
      @current_user = ExternalUser.new

      @file = authorize AuthenticatedUrlHelper.retrieve!(CodeOcean::File, request)

      # The `@file.name_with_extension` is assembled based on the user-selected file type, not on the actual file name stored on disk.
      raise Pundit::NotAuthorizedError unless @file.filepath == params[:filename] || @file.native_file.present?

      real_location = Pathname(@file.native_file.current_path).realpath
      send_file(real_location, type: @file.native_file.content_type, filename: @file.name_with_extension)
    end

    def create
      @file = CodeOcean::File.new(file_params)
      if @file.file_template_id
        content = FileTemplate.find(@file.file_template_id).content
        content.sub! '{{file_name}}', @file.name
        @file.content = content
      end
      authorize!
      create_and_respond(object: @file, path: proc { implement_exercise_path(@file.context.exercise) })
    end

    def create_and_respond(options = {})
      @object = options[:object]
      respond_to do |format|
        if @object.save
          yield if block_given?
          path = options[:path].try(:call) || @object
          respond_with_valid_object(format, notice: t('shared.object_created', model: @object.class.model_name.human),
            path:, status: :created)
        else
          filename = "#{@object.path || ''}/#{@object.name || ''}#{@object.file_type.try(:file_extension) || ''}"
          format.html do
            flash[:danger] = t('code_ocean/files.error.filename', name: filename)
            redirect_to options[:path], status: :see_other
          end
          format.json { render json: @object.errors, status: :unprocessable_content }
        end
      end
    end

    def destroy
      @file = CodeOcean::File.find(params[:id])
      authorize!
      destroy_and_respond(object: @file, path: @file.context)
    end

    def file_params
      if params[:code_ocean_file].present?
        params[:code_ocean_file].permit(file_attributes).merge(context_type: 'Submission',
          role: 'user_defined_file')
      end
    end
    private :file_params
  end
end
