# frozen_string_literal: true
class CustomWizard::TemplateValidator
  include HasErrors
  include ActiveModel::Model

  def initialize(data, opts = {})
    @data = data
    @opts = opts
    @subscription = CustomWizard::Subscription.new
  end

  def perform
    data = @data

    check_id(data, :wizard)
    check_required(data, :wizard)
    validate_after_signup
    validate_after_time
    validate_subscription(data, :wizard)

    return false if errors.any?

    data[:steps].each do |step|
      check_required(step, :step)
      validate_subscription(step, :step)
      validate_liquid_template(step, :step)

      if step[:fields].present?
        step[:fields].each do |field|
          validate_subscription(field, :field)
          check_required(field, :field)
          validate_liquid_template(field, :field)
          validate_guests(field, :field)
        end
      end
    end

    if data[:actions].present?
      data[:actions].each do |action|
        validate_subscription(action, :action)
        check_required(action, :action)
        validate_liquid_template(action, :action)
        validate_guests(action, :action)
      end
    end

    !errors.any?
  end

  def self.required
    { wizard: %w[id name steps], step: ["id"], field: %w[id type], action: %w[id type] }
  end

  private

  def check_required(object, type)
    self.class.required[type].each do |property|
      if object[property].blank?
        errors.add :base, I18n.t("wizard.validation.required", property: property)
      end
    end
  end

  def validate_subscription(object, type)
    object.keys.each do |property|
      value = object[property]

      if !@subscription.includes?(type, property.to_sym, value)
        errors.add :base,
                   I18n.t("wizard.validation.subscription", type: type.to_s, property: property)
      end
    end
  end

  def check_id(object, type)
    if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(object[:id])
      errors.add :base, I18n.t("wizard.validation.conflict", wizard_id: object[:id])
    end
  end

  def validate_guests(object, type)
    guests_permitted =
      @data[:permitted] &&
        @data[:permitted].any? { |m| m["output"]&.include?(CustomWizard::Wizard::GUEST_GROUP_ID) }
    return unless guests_permitted

    if type === :action && CustomWizard::Action::REQUIRES_USER.include?(object[:type])
      errors.add :base, I18n.t("wizard.validation.not_permitted_for_guests", object_id: object[:id])
    end

    if type === :field && CustomWizard::Field::REQUIRES_USER.include?(object[:type])
      errors.add :base, I18n.t("wizard.validation.not_permitted_for_guests", object_id: object[:id])
    end
  end

  def validate_after_signup
    return unless ActiveRecord::Type::Boolean.new.cast(@data[:after_signup])

    other_after_signup =
      CustomWizard::Template
        .list(setting: "after_signup")
        .select { |template| template["id"] != @data[:id] }

    if other_after_signup.any?
      errors.add :base,
                 I18n.t("wizard.validation.after_signup", wizard_id: other_after_signup.first["id"])
    end
  end

  def validate_after_time
    return unless ActiveRecord::Type::Boolean.new.cast(@data[:after_time])

    if ActiveRecord::Type::Boolean.new.cast(@data[:after_signup])
      errors.add :base, I18n.t("wizard.validation.after_signup_after_time")
      return
    end

    wizard = CustomWizard::Wizard.create(@data[:id]) if !@opts[:create]
    current_time = wizard.present? ? wizard.after_time_scheduled : nil
    new_time = @data[:after_time_scheduled]

    begin
      active_time = Time.parse(new_time.present? ? new_time : current_time).utc
    rescue ArgumentError
      invalid_time = true
    end

    if invalid_time || active_time.blank? || active_time < Time.now.utc
      errors.add :base, I18n.t("wizard.validation.after_time")
    end

    group_names = @data[:after_time_groups]
    if group_names.present?
      group_names.each do |group_name|
        unless Group.exists?(name: group_name)
          errors.add :base, I18n.t("wizard.validation.after_time_group", group_name: group_name)
        end
      end
    end
  end

  def validate_liquid_template(object, type)
    %w[description raw_description placeholder preview_template post_template].each do |field|
      if template = object[field]
        result = is_liquid_template_valid?(template)

        unless "valid" == result
          error =
            I18n.t(
              "wizard.validation.liquid_syntax_error",
              attribute: "#{object[:id]}.#{field}",
              message: result,
            )
          errors.add :base, error
        end
      end
    end
  end

  def is_liquid_template_valid?(template)
    begin
      Liquid::Template.parse(template)
      "valid"
    rescue Liquid::SyntaxError => error
      error.message
    end
  end
end
