class PackagesController < ApplicationController
  # Allow legacy /uploadfile URL without CSRF protection
  protect_from_forgery except: :legacy_uploadfile

  def list
    @packages = query_packages.paginate(page: params[:page], per_page: 6)
    @view_style = :list
    render :browse
  end

  def grid
    @packages = query_packages.paginate(page: params[:page], per_page: 24)
    @view_style = :grid
    render :browse
  end

  def details
    @package = Package.find_by(name: params[:name])

    # Highlight a certain screenshot (by ID). This is used by the logs.slim
    # view to link to a certain screenshot of a package.
    @highlight_id = params[:highlight].to_i if params[:highlight]

    if @package.nil?
      @packagename = params[:name]
      render 'notfound', status: 404
    else
      @page = params[:page]
      # @screenshots = screenshots_visible_to_user(@package).paginate(page: @page, per_page: 6)
      @screenshots = @package.screenshots.accessible_by(current_ability, :view).paginate(
        page: @page, per_page: 6
      )
    end
  end

  # Show upload form for new images
  def upload
    @package = Package.find_by!(name: params[:name])
  end

  # POST target of the screenshots upload form.
  # Receives uploaded images. Checks if they are valid. Asks for description.
  # This action saves the screenshots already if they are valid. The user is
  # then given the chance to comment on and delete the screenshots again.
  def upload_receive
    @package = Package.find_by!(name: params[:name])
    @valid_images = []
    @invalid_images = []

    # If Javascript is disabled a user may submit an empty selection of images.
    if params[:file].nil?
      flash[:error] = 'You have not selected any images.'
      redirect_to upload_path
      return
    end

    all_errors = []

    files = params[:file]

    # Turn into array if a single image was uploaded through AJAX
    files = [files] if files.class != Array

    files.each do |img|
      new_screenshot = @package.screenshots.new(simage: img)

      # Check if the image was valid
      if new_screenshot.valid?
        # new_screenshot.uploaderhash = session.id.to_s
        new_screenshot.uploaderip = request.remote_ip
        new_screenshot.version = @package.version
        # ActiveStorage does not yet create a file checksum automatically.
        # Let's do that. It helps detect duplicate uploads later.
        new_screenshot.image_fingerprint = Digest::MD5.hexdigest(File.read(img.path))

        # Check that this screenshot is not a duplicate for this package
        if @package.screenshots.where(image_fingerprint: new_screenshot.image_fingerprint).any?
          auditlog "Duplicate image with fingerprint #{new_screenshot.image_fingerprint} found. Rejecting.",
                   package: @package
          all_errors << "Your file #{img.original_filename} is a duplicate. Sorry."
        else
          # TODO: Can the upload get approved automatically?

          # Create a pseudo user account for the user.
          # The user won't know that an account is created.
          # But this makes it easier to track who screenshots belong to.
          unless user_signed_in?
            sign_in User.create_pseudo_user
            auditlog 'New pseudo user for anonymous upload created and logged in.',
                     package: @package, screenshot: new_screenshot
          end
          new_screenshot.user = current_user
          new_screenshot.approve! if can?(:approve, new_screenshot)

          # Pre-render screenshot in different sizes
          new_screenshot.simage_derivatives!

          new_screenshot.save!
          auditlog "Screenshot #{new_screenshot.id} uploaded successfully.",
                   screenshot: new_screenshot, package: @package

          @valid_images.push new_screenshot
        end
      else
        errors = new_screenshot.errors[:simage]
        auditlog "Screenshot #{img.original_filename} invalid (#{errors}).",
                 package: @package
        # @invalid_images.push img.original_filename
        # raise
        all_errors << "Your file #{img.original_filename} #{errors.join(' and ')}."
      end
    end

    flash[:error] = all_errors if all_errors.any?

    # # Redirect back to upload form if all uploads were invalid
    # unless @valid_images.any?
    #   auditlog "No valid images uploaded. Back to upload form."
    #   redirect_to(upload_path, error: all_errors) and return
    # end

    # Show a list of invalid uploads by default. Or redirect to the review page
    # if all uploads were okay.
    # redirect_to upload_review_path unless @invalid_images

    # TODO
    # if @invalid_images…
    # ' #{image.image_file_name} (#{image.errors[:image].join(' and ')})

    # Inform the admins about the upload
    AdminMailer.with(package: @package).new_uploads_email.deliver_now if @valid_images.any?

    # Rails does not allow dots in the URL. So we cannot use the 'respond_to'
    # and 'format' ways to handle parameters. Instead the 'returns' parameters
    # is set in routes.rb to signal that this method was called by AJAX.
    if params[:returns] == :json
      # TODO: send all_errors back as JSON and make Javascript display it in #messages
      render json: { errors: all_errors.join(' ') }
    else
      redirect_to package_path
    end
  end

  # Legacy action to upload an image along with metadata.
  # This was used in Debshots 1.x as the default upload method.
  # This method allows that old-style way to upload screenshots.
  # It is probably used by screenshotting tools.
  #
  # Parameters:
  # - packagename
  # - version
  # - description
  # - file
  def legacy_uploadfile
    @package = Package.find_by!(name: params[:packagename])

    new_screenshot = @package.screenshots.new(image: params[:file])

    # Check if the image was valid
    if new_screenshot.valid?
      # new_screenshot.uploaderhash = session[:token]
      new_screenshot.uploaderip = session[:ip]
      new_screenshot.version = @package.version
      new_screenshot.save
      auditlog "Screenshot #{new_screenshot.id} uploaded successfully from legacy upload form.",
               screenshot: new_screenshot, package: @package
      redirect_to package_path(params[:packagename])
    else
      auditlog "Screenshot upload rejected. Errors: #{new_screenshot.errors.to_a}",
               package: @package
      head :not_acceptable
    end
  end

  def hide_screenshot
    # Is the user allowed to hide this screenshot?
    @screenshot = Screenshot.find(params[:id])

    # Check if the user is allowed to change this screenshot
    # - Is this the user's own screenshot? (anonymous)
    if can? :hide, @screenshot
      auditlog "Screenshot #{@screenshot.id} hidden",
               package: @screenshot.package
      @screenshot.hide!
      flash['notice'] = 'Screenshot hidden.'
      redirect_back(fallback_location: package_path(name: @screenshot.package.name))
    else
      head :forbidden
    end
  end

  def unhide_screenshot
    # Is the user allowed to unhide this screenshot?
    @screenshot = Screenshot.find(params[:id])

    # Check if the user is allowed to change this screenshot
    # - Is this the user's own screenshot? (anonymous)
    if can? :unhide, @screenshot
      auditlog "Screenshot #{@screenshot.id} un-hidden",
               package: @screenshot.package
      @screenshot.unhide!
      flash['notice'] = 'Screenshot un-hidden.'
      redirect_back(fallback_location: package_path(name: @screenshot.package.name))
    else
      head :forbidden
    end
  end

  def delete_screenshot
    # Is the user allowed to delete the screenshot?
    @screenshot = Screenshot.find(params[:id])

    # Check if the user is allowed to change this screenshot
    # - Is this the user's own screenshot? (anonymous)
    if can? :destroy, @screenshot
      auditlog "Screenshot #{@screenshot.id} deleted",
               package: @screenshot.package
      @screenshot.destroy

      # Increase the rejection counter for the user (social scoring)
      # if the screenshot is new and pending approval
      if !@screenshot.approved && @screenshot.user
        @screenshot.user.rejected_screenshots += 1
        @screenshot.user.save!
      end

      flash['notice'] = 'Screenshot deleted.'
      redirect_back(fallback_location: package_path(name: @screenshot.package.name))
    else
      head :forbidden
    end
  end

  def approve_screenshot
    @screenshot = Screenshot.find(params[:id])

    unless can? :approve, @screenshot
      head :forbidden
      return
    end

    @screenshot.approve!
    auditlog 'Screenshot approved',
             package: @screenshot.package, screenshot: @screenshot

    if @screenshot.user
      # Increase the approval counter for the user (social scoring)
      @screenshot.user.approved_screenshots += 1
      @screenshot.user.save!
    end

    flash['notice'] = 'Screenshot approved.'
    redirect_back(fallback_location: package_path(name: @screenshot.package.name))

    # head :forbidden
  end

  # Returns either…
  # - 160x120 thumbnail image
  # - 320x240 small image
  # - full-size screenshot
  #
  # If the package is not found it returns a dummy image along with status 404.
  # If the package is found but has no screenshots then it also returns a
  # dummy image along with status 404.
  #
  # Return a specific screenshot (if the screenshot_id parameter is given)
  # or just the first (newest) one.
  def send_image
    size = params[:size] # :small, :large or :thumb
    @package = Package.find_by(name: params[:name]) # package name
    @screenshot_id = params[:screenshot_id] # screenshot ID (optional)
    unless @package
      Rails.logger.debug 'no such package -> 404'
      screenshot404 if %i[large small].include?(size)
      thumbnail404 if size == :thumb
      return
    end

    @image = nil

    if params[:screenshot_id]
      # Called as /screenshot/:name/:screenshot_id
      # TODO: 'name' is useless here
      Rails.logger.debug "Called as /screenshot/#{params[:name]}/#{params[:screenshot_id]}"
      @image = Screenshot.find(params[:screenshot_id])
      unless can? :view, @image
        return screenshot403
      end
    elsif params[:version]
      # Called as /screenshot-with-version/:name/:version
      # or        /thumbnail-with-version/:name/:version
      # TODO: permissions check!?
      @image = @package.best_screenshot_for_version(params[:version])
    else
      # Called as /screenshot/:name
      @image = @package.screenshots.accessible_by(current_ability, :view).first
    end

    # Return a 404 if the package has no screenshots or the image was not found
    if @image
      send_file(File.join(@image.simage.storage.directory, @image.simage(size).id),
                disposition: 'inline')
    else
      Rails.logger.debug 'no such image -> 404'
      case size
      when :large
        screenshot404
      when :small
        screenshot404
      when :thumb
        Rails.logger.debug 'thumb404'
        thumbnail404
      end
    end
  end

  # Receives a form with a simple text field 'description' so that users can update
  # the description of their screenshot.
  def update_screenshot_description
    # TODO: Check permissions to do that
    @screenshot = Screenshot.find(params[:id])
    # @screenshot.description = params[:description]
    @screenshot.update params_screenshot_description
    @screenshot.save!
    flash['notice'] = 'Description updated.'
    redirect_back(fallback_location: package_path(name: @screenshot.package.name))
  end

  # Show an HTML partial with reviews of this package from the Ubuntu API
  # def reviews
  #   expires_in 1.day, public: true
  #   # @reviews = Package.find_by_name!(params[:name]).ubuntu_reviews
  #   @reviews = get_ubuntu_reviews params[:name]
  #   render '_reviews', layout: false
  # end

  private

  # Send a dummy thumbnail reading "No screenshot available. Sorry."
  def thumbnail404
    send_file Rails.root.join('public/images/dummy/thumbnail404.png'), type: 'image/png',
                                                                       disposition: 'inline', status: 404
  end

  # Send a dummy screenshot reading "No screenshot available. Sorry."
  def screenshot404
    send_file Rails.root.join('public/images/dummy/screenshot404.png'), type: 'image/png',
                                                                        disposition: 'inline', status: 404
  end

  # Send a dummy screenshot reading "No screenshot available. Sorry."
  def screenshot403
    send_file Rails.root.join('public/images/dummy/screenshot403.png'), type: 'image/png',
                                                                        disposition: 'inline', status: 404
  end

  # Return packages matching the criteria given by parameters
  def query_packages
    packages = Package # .order(visits: :desc)

    # text search
    if params[:search].present?
      logger.debug "Searching for #{params[:search]}"
      packages = packages.general_search(params[:search])
    end

    case params[:show]
    when 'with'
      # Enrich the result with the screenshots readable by the current user (CanCanCan)
      packages = packages.with_public_screenshots
      logger.debug 'Limiting packages to those with screenshots'
    when 'without'
      packages = packages.without_screenshots
      logger.debug 'Limiting packages to those without screenshots'
    end

    packages
  end

  # Store a random identifier and the client's IP address in the session
  # for later identification.
  # def create_user_token
  #   session[:token] ||= SecureRandom.hex
  #   session[:ip] ||= request.remote_ip
  # end

  # Get reviews of this package from the Ubuntu API
  # def get_ubuntu_reviews(packagename)
  #   # Use the URL defined in the configuration to get a JSON string
  #   url = Rails.configuration.ubuntu_reviews_api_url % packagename
  #   logger.debug "Loading Ubuntu reviews for package #{packagename} from #{url}"

  #   body = open(url).read
  #   # Turn JSON into a Ruby data structure
  #   json = JSON.parse(body)
  #   # Only show english reviews
  #   # TODO: Support further languages
  #   json = json.select { |x| x['language'] == 'en' }
  #   # Sort by 'usefulness_total' (how many people found this review useful)
  #   json.sort { |x, y| y['usefulness_total'].to_i <=> x['usefulness_total'].to_i }
  # end

  # def params_screenshot_description
  #   params.require(:screenshot).permit(:description)
  # end

  # def screenshots_visible_to_user(package)
  #   package.screenshots.accessible_by(current_ability, :view).order('created_at DESC')
  # end
end
