# frozen_string_literal: true
require_dependency 'wizard/step'
require_dependency 'wizard/field'
require_dependency 'wizard/step_updater'
require_dependency 'wizard/builder'

UserHistory.actions[:custom_wizard_step] = 1000

class CustomWizard::Wizard
  include ActiveModel::SerializerSupport

  attr_accessor :id,
                :name,
                :background,
                :theme_id,
                :save_submissions,
                :multiple_submissions,
                :after_time,
                :after_time_scheduled,
                :after_signup,
                :required,
                :prompt_completion,
                :restart_on_revisit,
                :resume_on_revisit,
                :permitted,
                :needs_categories,
                :needs_groups,
                :steps,
                :step_ids,
                :field_ids,
                :first_step,
                :start,
                :actions,
                :action_ids,
                :user,
                :submissions

  attr_reader   :all_step_ids

  def initialize(attrs = {}, user = nil)
    @user = user
    attrs = attrs.with_indifferent_access

    @id = attrs['id']
    @name = attrs['name']
    @background = attrs['background']
    @save_submissions = cast_bool(attrs['save_submissions'])
    @multiple_submissions = cast_bool(attrs['multiple_submissions'])
    @prompt_completion = cast_bool(attrs['prompt_completion'])
    @restart_on_revisit = cast_bool(attrs['restart_on_revisit'])
    @resume_on_revisit = cast_bool(attrs['resume_on_revisit'])
    @after_signup = cast_bool(attrs['after_signup'])
    @after_time = cast_bool(attrs['after_time'])
    @after_time_scheduled = attrs['after_time_scheduled']
    @required = cast_bool(attrs['required'])
    @permitted = attrs['permitted'] || nil
    @needs_categories = false
    @needs_groups = false
    @theme_id = attrs['theme_id']

    if attrs['theme'].present?
      theme = ::Theme.find_by(name: attrs['theme'])
      @theme_id = theme.id if theme
    end

    @first_step = nil
    @steps = []

    if attrs['steps'].present?
      @step_ids = @all_step_ids = attrs['steps'].map { |s| s['id'] }

      @field_ids = []
      attrs['steps'].each do |step|
        if step['fields'].present?
          step['fields'].each do |field|
            @field_ids << field['id']
          end
        end
      end
    end

    @actions = attrs['actions'] || []
    @action_ids = @actions.map { |a| a['id'] }
  end

  def cast_bool(val)
    val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val)
  end

  def create_step(step_id)
    ::CustomWizard::Step.new(step_id)
  end

  def append_step(step)
    step = create_step(step) if step.is_a?(String)

    yield step if block_given?

    steps << step
    step.wizard = self
    step.index = (steps.size == 1 ? 0 : steps.size) if step.index.nil?
  end

  def update!
    update_step_order
    update_step_ids
    update_field_ids
    update_action_ids

    @submissions = nil
    @current_submission = nil

    true
  end

  def update_step_order
    steps.sort_by!(&:index)

    steps.each_with_index do |step, index|
      if index === 0
        @first_step = step
        @start = step.id
      else
        last_step = steps[index - 1]
        last_step.next = step
        step.previous = last_step
      end

      step.index = index

      if index === (steps.length - 1)
        step.conditional_final_step = true
      end

      if index === (all_step_ids.length - 1)
        step.last_step = true
      end

      if step.previous && step.previous.id === last_completed_step_id
        @start = step.id
      end
    end
  end

  def last_completed_step_id
    if user && unfinished? && last_completed_step = ::UserHistory.where(
        acting_user_id: user.id,
        action: ::UserHistory.actions[:custom_wizard_step],
        context: id,
        subject: all_step_ids
      ).order("created_at").last

      last_completed_step.subject
    else
      nil
    end
  end

  def find_step(step_id)
    steps.select { |step| step.id === step_id }.first
  end

  def create_updater(step_id, submission)
    step = @steps.find { |s| s.id == step_id }
    wizard = self
    CustomWizard::StepUpdater.new(user, wizard, step, submission)
  end

  def unfinished?
    return nil if !user

    most_recent = ::UserHistory.where(
      acting_user_id: user.id,
      action: ::UserHistory.actions[:custom_wizard_step],
      context: id,
    ).distinct.order('updated_at DESC').first

    if most_recent && most_recent.subject == "reset"
      false
    elsif most_recent
      most_recent.subject != steps.last.id
    else
      true
    end
  end

  def completed?
    return nil if !user

    history = ::UserHistory.where(
      acting_user_id: user.id,
      action: ::UserHistory.actions[:custom_wizard_step],
      context: id
    )

    if after_time
      history = history.where("updated_at > ?", after_time_scheduled)
    end

    completed = history.distinct.order(:subject).pluck(:subject)
    (step_ids - completed).empty?
  end

  def permitted?
    return false unless user
    return true if user.admin? || permitted.blank?

    mapper = CustomWizard::Mapper.new(
      inputs: permitted,
      user: user,
      opts: {
        with_type: true,
        multiple: true
      }
    ).perform

    return true if mapper.blank?

    mapper.all? do |m|
      if m[:type] === 'assignment'
        [*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
        GroupUser.exists?(group_id: m[:result], user_id: user.id)
      elsif m[:type] === 'validation'
        m[:result]
      else
        true
      end
    end
  end

  def can_access?
    return false unless user
    return true if user.admin
    permitted? && (multiple_submissions || !completed?)
  end

  def reset
    ::UserHistory.create(
      action: ::UserHistory.actions[:custom_wizard_step],
      acting_user_id: user.id,
      context: id,
      subject: "reset"
    )
  end

  def categories
    @categories ||= ::Site.new(Guardian.new(user)).categories
  end

  def groups
    @groups ||= ::Site.new(Guardian.new(user)).groups
  end

  def update_step_ids
    @step_ids = steps.map(&:id)
  end

  def update_field_ids
    @field_ids = steps.map { |step| step.fields.map { |field| field.id } }.flatten
  end

  def update_action_ids
    @action_ids = []

    @actions.each do |action|
      if action['run_after'].blank? ||
         action['run_after'] === 'wizard_completion' ||
         step_ids.include?(action['run_after'])

        @action_ids << action['id']
      end
    end
  end

  def submissions
    return nil unless user.present?
    @submissions ||= CustomWizard::Submission.list(self, user_id: user.id)
  end

  def current_submission
    @current_submission ||= begin
      if submissions.present?
        unsubmitted = submissions.select { |submission| !submission.submitted_at }
        unsubmitted.present? ? unsubmitted.first : CustomWizard::Submission.new(self)
      else
        CustomWizard::Submission.new(self)
      end
    end
  end

  def cleanup_on_complete!
    remove_user_redirect

    if current_submission.present?
      current_submission.submitted_at = Time.now.iso8601
      current_submission.save
    end

    update!
  end

  def cleanup_on_skip!
    remove_user_redirect

    if current_submission.present?
      current_submission.remove
    end

    reset
  end

  def remove_user_redirect
    if id == user.redirect_to_wizard
      user.custom_fields.delete('redirect_to_wizard')
      user.save_custom_fields(true)
    end
  end

  def self.create(wizard_id, user = nil)
    if template = CustomWizard::Template.find(wizard_id)
      new(template.to_h, user)
    else
      false
    end
  end

  def self.list(user, template_opts: {}, not_completed: false)
    return [] unless user

    CustomWizard::Template.list(template_opts).reduce([]) do |result, template|
      wizard = new(template, user)
      result.push(wizard) if wizard.can_access? && (
        !not_completed || !wizard.completed?
      )
      result
    end
  end

  def self.after_signup(user)
    wizards = list(
      user,
      template_opts: {
        setting: 'after_signup',
        order: "(value::json ->> 'permitted') IS NOT NULL DESC"
      }
    )
    wizards.any? ? wizards.first : false
  end

  def self.prompt_completion(user)
    wizards = list(
      user,
      template_opts: {
        setting: 'prompt_completion',
        order: "(value::json ->> 'permitted') IS NOT NULL DESC"
      },
      not_completed: true
    )
    if wizards.any?
      wizards.map do |w|
        {
          id: w.id,
          name: w.name
        }
      end
    else
      false
    end
  end

  def self.set_user_redirect(wizard_id, user)
    wizard = self.create(wizard_id, user)

    if wizard.permitted?
      user.custom_fields['redirect_to_wizard'] = wizard_id
      user.save_custom_fields(true)
    else
      false
    end
  end

  def self.set_wizard_redirect(user, wizard_id, url)
    wizard = self.create(wizard_id, user)

    if wizard.permitted?
      submission = wizard.current_submission
      submission.redirect_to = url
      submission.save
    else
      false
    end
  end
end