# frozen_string_literal: true

module Submissions
  module GenerateAuditTrail
    FONT_SIZE = 9
    TEXT_COLOR = '525252'
    FONT_PATH = '/fonts/GoNotoKurrent-Regular.ttf'
    FONT_BOLD_PATH = '/fonts/GoNotoKurrent-Bold.ttf'
    FONT_NAME = if File.exist?(FONT_PATH)
                  'GoNotoKurrent'
                else
                  'Helvetica'
                end

    CURRENCY_SYMBOLS = {
      'USD' => '$',
      'EUR' => '€',
      'GBP' => '£'
    }.freeze

    TESTING_FOOTER = GenerateResultAttachments::TESTING_FOOTER

    RTL_REGEXP = TextUtils::RTL_REGEXP
    MAX_IMAGE_HEIGHT = 100

    CHECKSUM_LIMIT = 30

    US_TIMEZONES = TimeUtils::US_TIMEZONES

    module_function

    # rubocop:disable Metrics
    def call(submission)
      account = submission.account

      last_submitter = submission.submitters.select(&:completed_at).max_by(&:completed_at)

      I18n.with_locale(last_submitter.metadata.fetch('lang', account.locale)) do
        document = build_audit_trail(submission)

        pkcs = Accounts.load_signing_pkcs(account)
        tsa_url = Accounts.load_timeserver_url(account)

        io = StringIO.new

        document.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})"

        if pkcs
          sign_params = {
            reason: sign_reason,
            **Submissions::GenerateResultAttachments.build_signing_params(last_submitter, pkcs, tsa_url)
          }

          document.sign(io, **sign_params)

          Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params)
        else
          document.write(io)
        end

        ActiveStorage::Attachment.create!(
          blob: ActiveStorage::Blob.create_and_upload!(
            io: io.tap(&:rewind), filename: "#{I18n.t('audit_log')} - " \
                                            "#{submission.name || submission.template&.name}.pdf"
          ),
          name: 'audit_trail',
          record: submission
        )
      end
    end

    def build_audit_trail(submission)
      account = submission.account
      verify_url = Rails.application.routes.url_helpers.settings_esign_url(
        **Docuseal.default_url_options, host: ENV.fetch('EMAIL_HOST', Docuseal.default_url_options[:host])
      )

      page_size =
        if TimeUtils.timezone_abbr(account.timezone, Time.current.beginning_of_year).in?(US_TIMEZONES)
          :Letter
        else
          :A4
        end

      composer = HexaPDF::Composer.new(skip_page_creation: true)

      if Docuseal.pdf_format == 'pdf/a-3b'
        composer.document.task(:pdfa, level: '3b')
      elsif FONT_NAME == 'GoNotoKurrent'
        composer.document.task(:pdfa)
      end

      composer.document.config['font.map'] = {
        'Helvetica' => {
          none: FONT_PATH,
          bold: FONT_BOLD_PATH
        },
        FONT_NAME => {
          none: FONT_PATH,
          bold: FONT_BOLD_PATH
        }
      }

      composer.document.config['font.on_missing_glyph'] =
        Submissions::GenerateResultAttachments.method(:on_missing_glyph).to_proc

      divider = HexaPDF::Layout::Box.create(
        margin: [0, 0, 15, 0],
        border: {
          width: [1, 0, 0, 0],
          color: %w[hp-gray-light]
        },
        height: 1
      )

      configs = submission.account.account_configs.where(key: [AccountConfig::WITH_AUDIT_VALUES_KEY,
                                                               AccountConfig::WITH_SIGNATURE_ID,
                                                               AccountConfig::WITH_FILE_LINKS_KEY,
                                                               AccountConfig::WITH_TIMESTAMP_SECONDS_KEY,
                                                               AccountConfig::WITH_AUDIT_SENDER_KEY,
                                                               AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY])

      last_submitter = submission.submitters.select(&:completed_at).max_by(&:completed_at)

      with_signature_id = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true
      with_file_links = configs.find { |c| c.key == AccountConfig::WITH_FILE_LINKS_KEY }&.value == true
      with_audit_values = configs.find { |c| c.key == AccountConfig::WITH_AUDIT_VALUES_KEY }&.value != false
      with_audit_sender = configs.find { |c| c.key == AccountConfig::WITH_AUDIT_SENDER_KEY }&.value == true
      with_submitter_timezone = configs.find { |c| c.key == AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY }&.value == true
      with_timestamp_seconds = configs.find { |c| c.key == AccountConfig::WITH_TIMESTAMP_SECONDS_KEY }&.value == true

      file_links_expire_at = Accounts.link_expires_at(submission.account) if with_file_links

      timezone = account.timezone
      timezone = last_submitter.timezone || account.timezone if with_submitter_timezone

      composer.page_style(:default, page_size:) do |canvas, style|
        box = canvas.context.box(:media)
        canvas.save_graphics_state do
          canvas.fill_color('FAF7F5')
                .rectangle(0, 0, box.width, 20)
                .rectangle(0, box.height - 20, box.width, 20)
                .fill

          maybe_add_background(canvas, submission, page_size)
        end

        if with_signature_id || submission.account.testing?
          canvas.save_graphics_state do
            document_id = Digest::MD5.hexdigest(submission.slug).upcase

            font = composer.document.fonts.add(FONT_NAME)

            text =
              if submission.account.testing?
                if with_signature_id
                  "#{TESTING_FOOTER} | ID: #{document_id}"
                else
                  TESTING_FOOTER
                end
              else
                "#{I18n.t('document_id')}: #{document_id}"
              end

            text = HexaPDF::Layout::TextFragment.create(
              text, font:, font_size: FONT_SIZE, underlays: [
                lambda do |canv, box|
                  canv.fill_color('white').rectangle(-1, 0, box.width + 2, box.height).fill
                end
              ]
            )

            HexaPDF::Layout::TextLayouter.new(font:, font_size: FONT_SIZE)
                                         .fit([text], 1000, 1000)
                                         .draw(canvas, 1, FONT_SIZE * 1.37)
          end
        end

        style.frame = style.create_frame(canvas.context, 50)
      end

      composer.style(:base, font: FONT_NAME, font_size: FONT_SIZE, fill_color: TEXT_COLOR, line_spacing: 1)
      composer.style(:link, fill_color: 'hp-blue-light', underline: true)

      composer.new_page

      composer.column(columns: 1) do |column|
        add_logo(column, submission)

        column.text(account.testing? ? I18n.t('testing_log_not_for_production_use') : I18n.t('audit_log'),
                    font_size: 16,
                    padding: [10, 0, 0, 0],
                    position: :float, text_align: :right)
      end

      composer.column(columns: 1) do |column|
        column.text("#{I18n.t('envelope_id')}: #{submission.id}", font_size: 12,
                                                                  padding: [15, 0, 8, 0],
                                                                  position: :float)

        if show_verify?(submission)
          column.formatted_text([{ link: verify_url, text: I18n.t('verify'), style: :link }],
                                font_size: 9, padding: [15, 0, 10, 0], position: :float, text_align: :right)
        end
      end

      composer.draw_box(divider)

      documents_data = select_attachments(last_submitter).map do |document|
        original_documents = submission.schema_documents.select do |e|
          e.uuid == (document.metadata['original_uuid'] || document.uuid)
        end.presence

        original_documents ||= submission.template.documents.select do |e|
          e.image? && submission.template_schema.any? do |item|
            item['attachment_uuid'] == e.uuid
          end
        end

        [
          composer.document.layout.formatted_text_box(
            [{ text: document.filename.to_s }]
          ),
          composer.document.layout.formatted_text_box(
            [
              { text: "#{I18n.t('original_sha256')}:\n", font: [FONT_NAME, { variant: :bold }] },
              original_documents.map { |d| d.metadata['sha256'] || d.checksum }.first(CHECKSUM_LIMIT).join("\n"),
              "\n",
              { text: "#{I18n.t('result_sha256')}:\n", font: [FONT_NAME, { variant: :bold }] },
              document.metadata['sha256'] || document.checksum,
              "\n",
              { text: "#{I18n.t('generated_at')}: ", font: [FONT_NAME, { variant: :bold }] },
              "#{I18n.l(document.created_at.in_time_zone(timezone), format: :long, locale: account.locale)} " \
              "#{TimeUtils.timezone_abbr(timezone, document.created_at)}"
            ], line_spacing: 1.3
          )
        ]
      end

      if documents_data.present?
        composer.table(documents_data, cell_style: { padding: [0, 0, 20, 0], border: { width: 0 } })

        composer.draw_box(divider)
      end

      submission.template_submitters.filter_map do |item|
        submitter = submission.submitters.find { |e| e.uuid == item['uuid'] }

        next if submitter.blank?

        submission_events = submission.submission_events.select { |e| e.submitter_id == submitter.id }

        delegated_event = submission_events.select(&:delegate_form?).max_by(&:event_timestamp)

        if delegated_event
          submission_events = submission_events.select { |e| e.event_timestamp > delegated_event.event_timestamp }
        end

        completed_event = submission_events.find(&:complete_form?) || SubmissionEvent.new

        click_email_event = submission_events.find(&:click_email?)

        verify_email_event = submission_events.find(&:email_verified?)

        verify_phone_event = submission_events.find(&:phone_verified?)

        is_id_verified = submission_events.any?(&:complete_verification?)

        is_kba_passed = submission_events.any?(&:complete_kba?)

        info_rows = [
          [
            composer.document.layout.formatted_text_box(
              [
                submission.template_submitters.size > 1 && { text: "#{item['name']}\n" },
                submitter.email && { text: "#{submitter.email}\n", font: [FONT_NAME, { variant: :bold }] },
                submitter.name && { text: "#{TextUtils.maybe_rtl_reverse(submitter.name)}\n" },
                submitter.phone && { text: "#{submitter.phone}\n" }
              ].compact_blank, line_spacing: 1.3, padding: [0, 20, 0, 0]
            )
          ],
          [
            composer.document.layout.formatted_text_box(
              [
                submitter.email && (click_email_event || verify_email_event) && {
                  text: "#{I18n.t('email_verification')}: #{I18n.t('verified')}\n"
                },
                submitter.phone && verify_phone_event && {
                  text: "#{I18n.t('phone_verification')}: #{I18n.t('verified')}\n"
                },
                is_id_verified && {
                  text: "#{I18n.t('identity_verification')}: #{I18n.t('verified')}\n"
                },
                is_kba_passed && {
                  text: "#{I18n.t('knowledge_based_authentication')}: #{I18n.t('passed')}\n"
                },
                completed_event.data['ip'] && { text: "IP: #{completed_event.data['ip']}\n" },
                completed_event.data['sid'] && { text: "#{I18n.t('session_id')}: #{completed_event.data['sid']}\n" },
                completed_event.data['ua'] && { text: "User agent: #{completed_event.data['ua']}\n" },
                submitter.timezone && { text: "Time zone: #{submitter.timezone.to_s.sub('Kiev', 'Kyiv')}\n" },
                "\n"
              ].compact_blank, line_spacing: 1.3, padding: [10, 20, 20, 0]
            )
          ]
        ]

        composer.table(info_rows, cell_style: { padding: [0, 0, 0, 0], border: { width: 0 } })

        submitter_field_counters = Hash.new { 0 }
        grouped_value_field_names = {}
        skip_grouped_field_uuids = {}

        submission.template_fields.each do |field|
          next unless field['type'].in?(%w[signature initials])

          submitter_field_counters[field['type']] += 1

          next if field['submitter_uuid'] != submitter.uuid

          value = submitter.values[field['uuid']]

          field_name = field['title'].presence || field['name'].presence ||
                       "#{I18n.t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}"

          if grouped_value_field_names[value]
            skip_grouped_field_uuids[field['uuid']] = true

            grouped_value_field_names[value] += ", #{field_name}"
          else
            grouped_value_field_names[value] = field_name
          end
        end

        submitter_field_counters = Hash.new { 0 }

        submission.template_fields.filter_map do |field|
          submitter_field_counters[field['type']] += 1

          next if field['submitter_uuid'] != submitter.uuid
          next if field['type'] == 'heading' || field['type'] == 'strikethrough'
          next if !with_audit_values && !field['type'].in?(%w[signature initials])
          next if skip_grouped_field_uuids[field['uuid']]

          value = submitter.values[field['uuid']]

          next if Array.wrap(value).compact_blank.blank?

          field_name = grouped_value_field_names[value].presence || field['title'].presence || field['name'].to_s

          field_type = field['type']

          if field_type == 'image' &&
             submitter.attachments.find { |a| a.uuid == value }.then { |a| !a.image? || a.content_type == 'image/heic' }
            field_type = 'file'
          end

          [
            composer.formatted_text_box(
              [
                {
                  text: TextUtils.maybe_rtl_reverse(field_name).upcase.presence ||
                        "#{I18n.t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}".upcase,
                  font_size: 6
                }
              ].compact_blank,
              text_align: field_name.to_s.match?(RTL_REGEXP) ? :right : :left,
              line_spacing: 1.3, padding: [0, 0, 2, 0]
            ),
            if field_type.in?(%w[image signature initials stamp kba]) &&
               (attachment = submitter.attachments.find { |a| a.uuid == value }) &&
               attachment.image?

              image =
                begin
                  ImageUtils.load_vips(attachment.download, content_type: attachment.content_type, autorot: true)
                rescue Vips::Error
                  next unless attachment.content_type.starts_with?('image/')
                  next if attachment.byte_size.zero?

                  raise
                end

              scale = [600.0 / image.width, 600.0 / image.height].min

              resized_image = image.resize([scale, 1].min)

              io =
                if field['type'] == 'image' && !resized_image.has_alpha?
                  StringIO.new(resized_image.colourspace(:srgb).write_to_buffer('.jpg', strip: true))
                else
                  StringIO.new(resized_image.write_to_buffer('.png', strip: true))
                end

              width = field['type'] == 'initials' ? 50 : 200
              height = resized_image.height * (width.to_f / resized_image.width)

              if height > MAX_IMAGE_HEIGHT
                width = (MAX_IMAGE_HEIGHT / height) * width
                height = MAX_IMAGE_HEIGHT
              end

              composer.image(io, width:, height:, margin: [5, 0, 10, 0])
              composer.formatted_text_box([{ text: '' }])
            elsif field_type.in?(%w[file payment image])
              if field['type'] == 'payment'
                unit = CURRENCY_SYMBOLS[field['preferences']['currency']] || field['preferences']['currency']

                price = ApplicationController.helpers.number_to_currency(field['preferences']['price'], unit:)

                composer.formatted_text_box([{ text: "#{I18n.t('paid_price', price:)}\n" }],
                                            padding: [0, 0, 10, 0])
              end

              composer.formatted_text_box(
                Array.wrap(value).map do |uuid|
                  attachment = submitter.attachments.find { |a| a.uuid == uuid }

                  link =
                    if with_file_links
                      ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: file_links_expire_at)
                    else
                      r.submissions_preview_url(submission.slug, **Docuseal.default_url_options)
                    end

                  { link:, text: "#{attachment.filename}\n", style: :link }
                end,
                padding: [0, 0, 10, 0]
              )
            elsif field_type == 'checkbox'
              composer.formatted_text_box([{ text: value.to_s.titleize }], padding: [0, 0, 10, 0])
            else
              if field['type'] == 'date'
                value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale,
                                                     timezone:)
              end

              value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number'

              value = value.join(', ') if value.is_a?(Array)

              if (mask = field.dig('preferences', 'mask').presence)
                value = TextUtils.mask_value(value, mask)
              end

              composer.formatted_text_box([{ text: TextUtils.maybe_rtl_reverse(value.to_s.presence || 'n/a') }],
                                          text_align: value.to_s.match?(RTL_REGEXP) ? :right : :left,
                                          padding: [0, 0, 10, 0])
            end
          ]
        end
      end

      composer.draw_box(divider)

      composer.text(I18n.t('event_log'), font_size: 12, padding: [10, 0, 20, 0])

      submitter_versions_index = submission.submitters.preload(:submitter_versions).to_h do |s|
        [s.id, s.submitter_versions.to_a.sort_by(&:created_at)]
      end

      events_data = submission.submission_events.sort_by(&:event_timestamp).filter_map do |event|
        next if event.event_type.in?(%w[bounce_email complaint_email])

        submitter = submission.submitters.find { |e| e.id == event.submitter_id }
        versions = submitter_versions_index[submitter.id] || []
        active_version = versions.find { |v| v.created_at > event.event_timestamp }

        submitter_name =
          if event.event_type.include?('sms') || event.event_type.include?('phone')
            event.data['phone'] || active_version&.phone || submitter.phone
          else
            active_version&.name || active_version&.email || active_version&.phone ||
              submitter.name || submitter.email || submitter.phone
          end

        text =
          if event.event_type == 'complete_verification'
            I18n.t('submission_event_names.complete_verification_by_html', provider: event.data['method'],
                                                                           submitter_name:)
          elsif event.event_type == 'invite_party' &&
                (invited_submitter = submission.submitters.find { |e| e.uuid == event.data['uuid'] }) &&
                (name = submission.template_submitters.find { |e| e['uuid'] == event.data['uuid'] }&.dig('name'))
            invited_submitter_name = [invited_submitter.name || invited_submitter.email || invited_submitter.phone,
                                      name].join(' ')
            I18n.t('submission_event_names.invite_party_by_html', invited_submitter_name:,
                                                                  submitter_name:)
          elsif with_audit_sender && (event.event_type == 'send_email' || event.event_type == 'send_sms')
            [
              I18n.t("submission_event_names.#{event.event_type}_to_html", submitter_name:),
              "<b>#{I18n.t(:from)}</b> #{submission.created_by_user.full_name} #{submission.created_by_user.email}"
            ].join("\n")
          elsif event.event_type == 'delegate_form'
            from = event.data['old_email'].presence ||
                   versions.rfind { |v| v.created_at <= event.event_timestamp }&.then { |v| v.name || v.phone }
            I18n.t('submission_event_names.delegate_form_by_html', from:, to: event.data['email'])
          elsif event.event_type.include?('send_')
            I18n.t("submission_event_names.#{event.event_type}_to_html", submitter_name:)
          else
            I18n.t("submission_event_names.#{event.event_type}_by_html", submitter_name:)
          end

        bold_text, normal_text = text.match(%r{<b>(.*?)</b>(.*)}).captures

        text_box = [{ text: bold_text, font: [FONT_NAME, { variant: :bold }] }, normal_text]

        if text.include?("\n")
          text_box = text.split("\n")[1..].reduce(text_box) do |acc, row|
            bold_text, normal_text = row.match(%r{<b>(.*?)</b>(.*)}).captures

            [*acc, "\n", { text: bold_text, font: [FONT_NAME, { variant: :bold }] }, normal_text]
          end
        end

        time_format = with_timestamp_seconds ? :detailed : :long

        [
          "#{I18n.l(event.event_timestamp.in_time_zone(timezone), format: time_format, locale: account.locale)} " \
          "#{TimeUtils.timezone_abbr(timezone, event.event_timestamp)}",
          composer.document.layout.formatted_text_box(text_box)
        ]
      end

      composer.table(events_data, cell_style: { padding: [0, 0, 12, 0], border: { width: 0 } }) if events_data.present?

      composer.document
    end

    def sign_reason
      'Signed with DocuSeal.com'
    end

    def select_attachments(submitter)
      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 maybe_add_background(_canvas, _submission, _page_size); end

    def show_verify?(submission)
      !submission.source.in?(%w[embed api])
    end

    def add_logo(column, _submission = nil)
      column.image(PdfIcons.logo_io, width: 40, height: 40, position: :float)

      column.formatted_text([{ text: 'DocuSeal',
                               link: Docuseal::PRODUCT_EMAIL_URL }],
                            font_size: 20,
                            font: [FONT_NAME, { variant: :bold }],
                            width: 100,
                            padding: [5, 0, 0, 8],
                            position: :float, text_align: :left)
    end

    def r
      Rails.application.routes.url_helpers
    end
    # rubocop:enable Metrics
  end
end
