# For exporting to Excel CSV format
require 'csv'

class ChallengeSignupsController < ApplicationController
  include ExportsHelper

  before_action :users_only, except: [:summary]
  before_action :load_collection, except: [:index]
  before_action :load_challenge, except: [:index]
  before_action :load_signup_from_id, only: [:show, :edit, :update, :destroy, :confirm_delete]
  before_action :allowed_to_destroy, only: [:destroy, :confirm_delete]
  before_action :signup_owner_only, only: [:edit, :update]
  before_action :maintainer_or_signup_owner_only, only: [:show]
  before_action :check_signup_open, only: [:new, :create, :edit, :update]
  before_action :check_pseud_ownership, only: [:create, :update]
  before_action :check_signup_in_collection, only: [:show, :edit, :update, :destroy, :confirm_delete]

  def load_challenge
    @challenge = @collection.challenge
    no_challenge and return unless @challenge
  end

  def no_challenge
    flash[:error] = ts("What challenge did you want to sign up for?")
    redirect_to collection_path(@collection) rescue redirect_to '/'
    false
  end

  def check_signup_open
    signup_closed and return unless (@challenge.signup_open || @collection.user_is_maintainer?(current_user))
  end

  def signup_closed
    flash[:error] = ts("Sign-up is currently closed: please contact a moderator for help.")
    redirect_to @collection rescue redirect_to '/'
    false
  end

  def signup_closed_owner?
    @collection.challenge_type == "GiftExchange" && !@challenge.signup_open && @collection.user_is_owner?(current_user)
  end

  def signup_owner_only
    not_signup_owner and return unless @challenge_signup.pseud.user == current_user || signup_closed_owner?
  end

  def maintainer_or_signup_owner_only
    not_allowed(@collection) and return unless (@challenge_signup.pseud.user == current_user || @collection.user_is_maintainer?(current_user))
  end

  def not_signup_owner
    flash[:error] = ts("You can't edit someone else's sign-up!")
    redirect_to @collection
    false
  end

  def allowed_to_destroy
    @challenge_signup.user_allowed_to_destroy?(current_user) || not_allowed(@collection)
  end

  def load_signup_from_id
    @challenge_signup = ChallengeSignup.find(params[:id])
  end

  def check_pseud_ownership
    if params[:challenge_signup][:pseud_id] && (pseud = Pseud.find(params[:challenge_signup][:pseud_id]))
      # either you have to own the pseud, OR you have to be a mod editing after signups are closed and NOT changing the pseud
      unless current_user.pseuds.include?(pseud) || (@challenge_signup && @challenge_signup.pseud == pseud && signup_closed_owner?)
        flash[:error] = ts("You can't sign up with that pseud.")
        redirect_to root_path and return
      end
    end
  end

  def check_signup_in_collection
    unless @challenge_signup.collection_id == @collection.id
      flash[:error] = ts("Sorry, that sign-up isn't associated with that collection.")
      redirect_to @collection
    end
  end

  #### ACTIONS

  def index
    if params[:user_id] && (@user = User.find_by(login: params[:user_id]))
      if current_user == @user
        @challenge_signups = @user.challenge_signups.order_by_date
        render action: :index and return
      else
        flash[:error] = ts("You aren't allowed to see that user's sign-ups.")
        redirect_to '/' and return
      end
    else
      load_collection
      load_challenge if @collection
      return false unless @challenge
    end

    # using respond_to in order to provide Excel output
    # see ExportsHelper for export_csv method
    respond_to do |format|
      format.html {
          if @challenge.user_allowed_to_see_signups?(current_user)
            @challenge_signups = @collection.signups.joins(:pseud)
            if params[:query]
              @query = params[:query]
              @challenge_signups = @challenge_signups.where("pseuds.name LIKE ?", '%' + params[:query] + '%')
            end
            @challenge_signups = @challenge_signups.order("pseuds.name").paginate(page: params[:page], per_page: ArchiveConfig.ITEMS_PER_PAGE)
          elsif params[:user_id] && (@user = User.find_by(login: params[:user_id]))
            @challenge_signups = @collection.signups.by_user(current_user)
          else
            not_allowed(@collection)
          end
      }
      format.csv {
        if (@collection.gift_exchange? && @challenge.user_allowed_to_see_signups?(current_user)) ||
        (@collection.prompt_meme? && @collection.user_is_maintainer?(current_user))
          csv_data = self.send("#{@challenge.class.name.underscore}_to_csv")
          filename = "#{@collection.name}_signups_#{Time.now.strftime('%Y-%m-%d-%H%M')}.csv"
          send_csv_data(csv_data, filename)
        else
          flash[:error] = ts("You aren't allowed to see the CSV summary.")
          redirect_to collection_path(@collection) rescue redirect_to '/' and return
        end
      }
    end
  end

  def summary
    @summary = ChallengeSignupSummary.new(@collection)

    if @collection.signups.count < (ArchiveConfig.ANONYMOUS_THRESHOLD_COUNT/2)
      flash.now[:notice] = ts("Summary does not appear until at least %{count} sign-ups have been made!", count: ((ArchiveConfig.ANONYMOUS_THRESHOLD_COUNT/2)))
    elsif @collection.signups.count > ArchiveConfig.MAX_SIGNUPS_FOR_LIVE_SUMMARY
      # too many signups in this collection to show the summary page "live"
      modification_time = @summary.cached_time

      # The time is always written alongside the cache, so if the time is
      # missing, then the cache must be missing as well -- and we want to
      # generate it. We also want to generate it if signups are open and it was
      # last generated more than an hour ago.
      if modification_time.nil? ||
         (@collection.challenge.signup_open? && modification_time < 1.hour.ago)

        # Touch the cache so that we don't try to generate the summary a second
        # time on subsequent page loads.
        @summary.touch_cache

        # Generate the cache of the summary in the background.
        @summary.enqueue_for_generation
      end
    else
      # generate it on the fly
      @tag_type = @summary.tag_type
      @summary_tags = @summary.summary
      @generated_live = true
    end
  end

  def show
    unless @challenge_signup.valid?
      flash[:error] = ts("This sign-up is invalid. Please check your sign-ups for a duplicate or edit to fix any other problems.")
    end
  end

  protected
  def build_prompts
    notice = ""
    @challenge.class::PROMPT_TYPES.each do |prompt_type|
      num_to_build = params["num_#{prompt_type}"] ? params["num_#{prompt_type}"].to_i : @challenge.required(prompt_type)
      if num_to_build < @challenge.required(prompt_type)
        notice += ts("You must submit at least %{required} #{prompt_type}. ", required: @challenge.required(prompt_type))
        num_to_build = @challenge.required(prompt_type)
      elsif num_to_build > @challenge.allowed(prompt_type)
        notice += ts("You can only submit up to %{allowed} #{prompt_type}. ", allowed: @challenge.allowed(prompt_type))
        num_to_build = @challenge.allowed(prompt_type)
      elsif params["num_#{prompt_type}"]
        notice += ts("Set up %{num} #{prompt_type.pluralize}. ", num: num_to_build)
      end
      num_existing = @challenge_signup.send(prompt_type).count
      num_existing.upto(num_to_build-1) do
        @challenge_signup.send(prompt_type).build
      end
    end
    unless notice.blank?
      flash[:notice] = notice
    end
  end

  public
  def new
    if (@challenge_signup = ChallengeSignup.in_collection(@collection).by_user(current_user).first)
      flash[:notice] = ts("You are already signed up for this challenge. You can edit your sign-up below.")
      redirect_to edit_collection_signup_path(@collection, @challenge_signup)
    else
      @challenge_signup = ChallengeSignup.new
      build_prompts
    end
  end

  def edit
    build_prompts
  end

  def create
    @challenge_signup = ChallengeSignup.new(challenge_signup_params)

    @challenge_signup.pseud = current_user.default_pseud unless @challenge_signup.pseud
    @challenge_signup.collection = @collection
    # we check validity first to prevent saving tag sets if invalid
    if @challenge_signup.valid? && @challenge_signup.save
      flash[:notice] = ts('Sign-up was successfully created.')
      redirect_to collection_signup_path(@collection, @challenge_signup)
    else
      render action: :new
    end
  end

  def update
    if @challenge_signup.update(challenge_signup_params)
      flash[:notice] = ts('Sign-up was successfully updated.')
      redirect_to collection_signup_path(@collection, @challenge_signup)
    else
      render action: :edit
    end
  end

  def confirm_delete
  end

  def destroy
    unless @challenge.signup_open || @collection.user_is_maintainer?(current_user)
      flash[:error] = ts("You cannot delete your sign-up after sign-ups are closed. Please contact a moderator for help.")
    else
      @challenge_signup.destroy
      flash[:notice] = ts("Challenge sign-up was deleted.")
    end
    if @collection.user_is_maintainer?(current_user) && !@collection.prompt_meme?
      redirect_to collection_signups_path(@collection)
    elsif @collection.prompt_meme?
      redirect_to collection_requests_path(@collection)
    else
      redirect_to @collection
    end
  end


protected

  def request_to_array(type, request)
    any_types = TagSet::TAG_TYPES.select {|type| request && request.send("any_#{type}")}
    any_types.map! { |type| ts("Any %{type}", type: type.capitalize) }
    tags = request.nil? ? [] : request.tag_set.tags.map {|tag| tag.name}
    rarray = [(tags + any_types).join(", ")]

    if @challenge.send("#{type}_restriction").optional_tags_allowed
      rarray << (request.nil? ? "" : request.optional_tag_set.tags.map {|tag| tag.name}.join(", "))
    end

    if @challenge.send("#{type}_restriction").title_allowed
      rarray << (request.nil? ? "" : sanitize_field(request, :title))
    end

    if @challenge.send("#{type}_restriction").description_allowed
      description = (request.nil? ? "" : sanitize_field(request, :description))
      # Didn't find a way to get Excel 2007 to accept line breaks
      # withing a field; not even when the row delimiter is set to
      # \r\n and linebreaks within the field are only \n. :-(
      #
      # Thus stripping linebreaks.
      rarray << description.gsub(/[\n\r]/, " ")
    end

    rarray << (request.nil? ? "" : request.url) if
      @challenge.send("#{type}_restriction").url_allowed

    return rarray
  end


  def gift_exchange_to_csv
    header = ["Pseud", "Email", "Sign-up URL"]

    %w(request offer).each do |type|
      @challenge.send("#{type.pluralize}_num_allowed").times do |i|
        header << "#{type.capitalize} #{i+1} Tags"
        header << "#{type.capitalize} #{i+1} Optional Tags" if
          @challenge.send("#{type}_restriction").optional_tags_allowed
        header << "#{type.capitalize} #{i+1} Title" if
          @challenge.send("#{type}_restriction").title_allowed
        header << "#{type.capitalize} #{i+1} Description" if
          @challenge.send("#{type}_restriction").description_allowed
        header << "#{type.capitalize} #{i+1} URL" if
          @challenge.send("#{type}_restriction").url_allowed
      end
    end

    csv_array = []
    csv_array << header

    @collection.signups.each do |signup|
      row = [signup.pseud.name, signup.pseud.user.email,
             collection_signup_url(@collection, signup)]

      %w(request offer).each do |type|
        @challenge.send("#{type.pluralize}_num_allowed").times do |i|
          row += request_to_array(type, signup.send(type.pluralize)[i])
        end
      end
      csv_array << row
    end

    csv_array
  end


  def prompt_meme_to_csv
    header = ["Pseud", "Sign-up URL", "Tags"]
    header << "Optional Tags" if @challenge.request_restriction.optional_tags_allowed
    header << "Title" if @challenge.request_restriction.title_allowed
    header << "Description" if @challenge.request_restriction.description_allowed
    header << "URL" if @challenge.request_restriction.url_allowed

    csv_array = []
    csv_array << header
    @collection.prompts.where(type: "Request").each do |request|
      row =
        if request.anonymous?
          ["(Anonymous)", ""]
        else
          [request.challenge_signup.pseud.name,
           collection_signup_url(@collection, request.challenge_signup)]
        end
      csv_array << (row + request_to_array("request", request))
    end

    csv_array
  end

  private

  def challenge_signup_params
    params.require(:challenge_signup).permit(
      :pseud_id,
      requests_attributes: nested_prompt_params,
      offers_attributes: nested_prompt_params
    )
  end

  def nested_prompt_params
    [
      :id,
      :title,
      :url,
      :any_fandom,
      :any_character,
      :any_relationship,
      :any_freeform,
      :any_category,
      :any_rating,
      :any_archive_warning,
      :anonymous,
      :description,
      :_destroy,
      tag_set_attributes: [
        :id,
        :updated_at,
        :character_tagnames,
        :relationship_tagnames,
        :freeform_tagnames,
        :category_tagnames,
        :rating_tagnames,
        :archive_warning_tagnames,
        :fandom_tagnames,
        character_tagnames: [],
        relationship_tagnames: [],
        freeform_tagnames: [],
        category_tagnames: [],
        rating_tagnames: [],
        archive_warning_tagnames: [],
        fandom_tagnames: [],
      ],
      optional_tag_set_attributes: [
        :tagnames
      ]
    ]
  end
end
