# frozen_string_literal: true

module Submitters
  TRUE_VALUES = ['1', 'true', true].freeze

  FIELD_NAME_WEIGHTS = {
    'email' => 'A',
    'phone' => 'B',
    'name' => 'C',
    'values' => 'D'
  }.freeze

  UnableToSendCode = Class.new(StandardError)
  InvalidOtp = Class.new(StandardError)
  MaliciousFileExtension = Class.new(StandardError)
  ParamsError = Class.new(StandardError)

  DANGEROUS_EXTENSIONS = Set.new(%w[
    exe com bat cmd scr pif vbs vbe js jse wsf wsh msi msp
    hta cpl jar app deb rpm dmg pkg mpkg dll so dylib sys
    inf reg ps1 psm1 psd1 ps1xml psc1 pssc bat cmd vb vba
    sh bash zsh fish run out bin elf gadget workflow lnk scf
    url desktop application action workflow apk ipa xap appx
    appxbundle msix msixbundle diagcab diagpkg cpl msc ocx
    drv scr ins isp mst paf prf shb shs slk ws wsc inf1 inf2
  ].freeze)

  FILES_TTL = 5.minutes

  module_function

  def search(current_user, submitters, keyword)
    if Docuseal.fulltext_search?
      fulltext_search(current_user, submitters, keyword)
    else
      plain_search(submitters, keyword)
    end
  end

  def fulltext_search(current_user, submitters, keyword)
    return submitters if keyword.blank?

    submitters.where(
      id: SearchEntry.where(record_type: 'Submitter')
                     .where(account_id: current_user.account_id)
                     .where(*SearchEntries.build_tsquery(keyword))
                     .select(:record_id)
    )
  end

  def fulltext_search_field(current_user, submitters, keyword, field_name)
    keyword = keyword.delete("\0")

    return submitters.none if keyword.blank?

    weight = FIELD_NAME_WEIGHTS[field_name]

    return submitters.none if weight.blank?

    query =
      if keyword.match?(/\d/) && !keyword.match?(/\p{L}/)
        number = keyword.gsub(/\D/, '')

        sql =
          if number.length <= 2
            "ngram @@ ((quote_literal(?) || ':' || ?)::tsquery || (quote_literal(?) || ':' || ?)::tsquery)"
          else
            "tsvector @@ ((quote_literal(?) || ':*' || ?)::tsquery || (quote_literal(?) || ':*' || ?)::tsquery)"
          end

        [sql, number, weight, number.length > 1 ? number.delete_prefix('0') : number, weight]
      elsif keyword.match?(/[^\p{L}\d&@.-]/) || keyword.match?(/[.-]{2,}/)
        terms = TextUtils.transliterate(keyword.downcase).split(/\b/).map(&:squish).compact_blank.uniq

        if terms.size > 1
          SearchEntries.build_weights_tsquery(terms, weight)
        else
          SearchEntries.build_weights_wildcard_tsquery(keyword, weight)
        end
      else
        SearchEntries.build_weights_wildcard_tsquery(keyword, weight)
      end

    submitter_ids = SearchEntry.where(record_type: 'Submitter')
                               .where(account_id: current_user.account_id)
                               .where(*query)
                               .limit(500)
                               .pluck(:record_id)

    submitters.where(id: submitter_ids.first(100))
  end

  def plain_search(submitters, keyword)
    return submitters if keyword.blank?

    term = "%#{keyword.downcase}%"

    arel_table = Submitter.arel_table

    arel = arel_table[:email].lower.matches(term)
                             .or(arel_table[:phone].matches(term))
                             .or(arel_table[:name].lower.matches(term))

    submitters.where(arel)
  end

  def select_attachments_for_download(submitter)
    if AccountConfig.exists?(account_id: submitter.submission.account_id,
                             key: AccountConfig::COMBINE_PDF_RESULT_KEY,
                             value: true) &&
       submitter.submission.submitters.all?(&:completed_at?) &&
       submitter.submission.template_fields.none? { |f| f['type'] == 'verification' }
      return [submitter.submission.combined_document_attachment || Submissions::EnsureCombinedGenerated.call(submitter)]
    end

    original_documents = submitter.submission.schema_documents.preload(:blob)
    is_more_than_two_images = original_documents.many?(&:image?)

    submitter.documents.preload(:blob).reject do |attachment|
      is_more_than_two_images &&
        original_documents.find { |a| a.uuid == (attachment.metadata['original_uuid'] || attachment.uuid) }&.image?
    end
  end

  def create_attachment!(submitter, file, metadata: {})
    raise ParamsError, 'file param is missing' if file.blank?

    extension = File.extname(file.original_filename).delete_prefix('.').downcase

    if DANGEROUS_EXTENSIONS.include?(extension)
      raise MaliciousFileExtension, "File type '.#{extension}' is not allowed."
    end

    blob = ActiveStorage::Blob.create_and_upload!(io: file.tap(&:rewind).open,
                                                  filename: file.original_filename,
                                                  content_type: file.content_type,
                                                  metadata:)

    ActiveStorage::Attachment.create!(blob:, name: 'attachments', record: submitter)
  end

  def normalize_preferences(account, user, params)
    preferences = {}

    message_params = params['message'].presence || params.slice('subject', 'body').presence

    if message_params.present?
      email_message = EmailMessages.find_or_create_for_account_user(account, user,
                                                                    message_params['subject'],
                                                                    message_params['body'])
    end

    preferences['email_message_uuid'] = email_message.uuid if email_message
    preferences['send_email'] = params['send_email'].in?(TRUE_VALUES) if params.key?('send_email')
    preferences['send_sms'] = params['send_sms'].in?(TRUE_VALUES) if params.key?('send_sms')
    preferences['require_phone_2fa'] = params['require_phone_2fa'].in?(TRUE_VALUES) if params.key?('require_phone_2fa')
    preferences['require_email_2fa'] = params['require_email_2fa'].in?(TRUE_VALUES) if params.key?('require_email_2fa')
    preferences['bcc_completed'] = params['bcc_completed'] if params.key?('bcc_completed')
    preferences['reply_to'] = params['reply_to'] if params.key?('reply_to')
    preferences['go_to_last'] = params['go_to_last'] if params.key?('go_to_last')
    preferences['completed_redirect_url'] = params['completed_redirect_url'] if params.key?('completed_redirect_url')

    preferences
  end

  def send_signature_requests(submitters, delay_seconds: nil)
    submitters.each_with_index do |submitter, index|
      next if submitter.email.blank?
      next if submitter.declined_at?
      next if submitter.preferences['send_email'] == false

      if delay_seconds
        SendSubmitterInvitationEmailJob.perform_in((delay_seconds + index).seconds, 'submitter_id' => submitter.id)
      else
        SendSubmitterInvitationEmailJob.perform_async('submitter_id' => submitter.id)
      end
    end
  end

  def current_submitter_order?(submitter)
    submission = submitter.submission

    submitter_items = submission.template_submitters || submission.template.submitters

    before_items =
      if submitter_items.any? { |s| s['order'] }
        submitter_groups = submitter_items.group_by.with_index { |s, index| s['order'] || index }.sort_by(&:first)

        current_group_index = submitter_groups.find_index { |_, group| group.any? { |s| s['uuid'] == submitter.uuid } }

        submitter_groups.first(current_group_index).flat_map(&:last)
      else
        submitter_items.first(submitter_items.find_index { |e| e['uuid'] == submitter.uuid })
      end

    before_items.all? do |item|
      submitter = submission.submitters.find { |e| e.uuid == item['uuid'] }

      submitter.nil? || submitter.completed_at?
    end
  end

  def build_document_filename(submitter, blob, filename_format)
    return blob.filename.to_s if filename_format.blank?

    filename = ReplaceEmailVariables.call(filename_format, submitter:)

    filename = filename.gsub('{document.name}', blob.filename.base)
    filename = filename.gsub(' - {submission.status}') do
      if submitter.submission.submitters.all?(&:completed_at?)
        status =
          if submitter.submission.template_fields.any? { |f| f['type'] == 'signature' }
            I18n.t(:signed)
          else
            I18n.t(:completed)
          end

        " - #{status}"
      end
    end

    filename = filename.gsub(
      '{submission.completed_at}',
      I18n.l(submitter.completed_at.in_time_zone(submitter.account.timezone), format: :short)
    )

    "#{filename}.#{blob.filename.extension}"
  end

  def send_shared_link_email_verification_code(submitter, request:)
    RateLimit.call("send-otp-code-#{request.remote_ip}", limit: 2, ttl: 45.seconds, enabled: true)

    TemplateMailer.otp_verification_email(submitter.submission.template, email: submitter.email).deliver_later!
  rescue RateLimit::LimitApproached
    Rollbar.warning("Limit verification code for template: #{submitter.submission.template.id}") if defined?(Rollbar)

    raise UnableToSendCode, I18n.t('too_many_attempts')
  end

  def verify_link_otp!(otp, submitter)
    return false if otp.blank?

    RateLimit.call("verify-2fa-code-#{Digest::MD5.base64digest(submitter.email)}",
                   limit: 2, ttl: 45.seconds, enabled: true)

    link_2fa_key = [submitter.email.downcase.squish, submitter.submission.template.slug].join(':')

    raise InvalidOtp, I18n.t(:invalid_code) unless EmailVerificationCodes.verify(otp, link_2fa_key)

    true
  end

  def build_document_urls(submitter, ttl: FILES_TTL)
    filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
                                                          key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value

    select_attachments_for_download(submitter).map do |attachment|
      ActiveStorage::Blob.proxy_path(
        attachment.blob,
        expires_at: ttl.from_now.to_i,
        filename: build_document_filename(submitter, attachment.blob, filename_format)
      )
    end
  end

  def build_combined_url(submitter, ttl: FILES_TTL)
    return if submitter.submission.submitters.exists?(completed_at: nil)
    return if submitter.submission.submitters.order(:completed_at).last != submitter

    attachment = submitter.submission.combined_document_attachment
    attachment ||= Submissions::EnsureCombinedGenerated.call(submitter)

    filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
                                                          key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value

    ActiveStorage::Blob.proxy_path(
      attachment.blob,
      expires_at: ttl.from_now.to_i,
      filename: build_document_filename(submitter, attachment.blob, filename_format)
    )
  end

  def populate_completed_is_first
    Account.find_each do |account|
      submissions_index = {}

      CompletedSubmitter.where(account_id: account.id).order(:account_id, :completed_at).each do |cs|
        submissions_index[cs.submission_id] ||= cs.submitter_id

        cs.update_columns(is_first: submissions_index[cs.submission_id] == cs.submitter_id)
      rescue ActiveRecord::RecordNotUnique
        CompletedSubmitter.where(submission_id: cs.submission_id).update_all(is_first: false)

        cs.update_columns(is_first: submissions_index[cs.submission_id] == cs.submitter_id)
      end
    end
  end
end
