# frozen_string_literal: false

require "hexapdf"
require "tempfile"

module Decidim
  # Example of service to add a signature to a pdf
  class PdfSignatureExample
    attr_accessor :pdf

    # Public: Initializes the service.
    # pdf - The pdf document to be signed
    def initialize(args = {})
      @pdf = args.fetch(:pdf)
    end

    # Public: PDF signed using a new certificate generated by the service
    def signed_pdf
      signed_file = Tempfile.new("signed_pdf")

      doc = HexaPDF::Document.new(io: StringIO.new(pdf))

      # Prepare the document for embedding of the digital signature
      data = nil # Used for storing the to-be-signed data
      signing_mechanism = lambda do |io, byte_range|
        # Store the to-be-signed data in the local variable data
        io.pos = byte_range[0]
        data = io.read(byte_range[1])
        io.pos = byte_range[2]
        data << io.read(byte_range[3])
        ""
      end
      doc.sign(signed_file.path, signature: signature_widget(doc), reason: caption, signature_size: 10_000, external_signing: signing_mechanism)

      signature = OpenSSL::PKCS7.sign(certificate, key, data,
                                      # [HexaPDF.demo_cert.sub_ca, HexaPDF.demo_cert.root_ca],
                                      [],
                                      OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der

      # Embed the signature
      HexaPDF::DigitalSignature::Signing.embed_signature(File.open(signed_file.path, "rb+"), signature)

      File.binread(signed_file.path)
    ensure
      signed_file.close
      signed_file.unlink
    end

    private

    def signature_widget(doc)
      form = doc.acro_form(create: true)
      form.signature_flag(:append_only)

      sig_field = form.create_signature_field("signature")

      widget = sig_field.create_widget(doc.pages[doc.pages.length - 1], Rect: [60, 50, 250, 620])
      widget.flag(:print)
      xobject = (widget[:AP] ||= {})[:N] ||= doc.add({ Type: :XObject, Subtype: :Form }) # Create the appearance for the widget
      xobject[:BBox] = [0, 0, widget[:Rect].width, widget[:Rect].height]
      xobject.canvas
             .font("Helvetica", size: 10)
             .text(caption, at: [10, 30])

      sig = doc.add({ Type: :Sig }) # set an empty signature and apply it to the field
      sig_field.field_value = sig
      sig
    end

    def certificate
      @certificate ||= OpenSSL::X509::Certificate.new.tap do |cert|
        cert.not_before = Time.current
        cert.not_after = 10.years.from_now

        cert.public_key = key.public_key
        cert.sign(key, OpenSSL::Digest.new("SHA256"))
      end
    end

    def key
      @key ||= OpenSSL::PKey::RSA.new(2048)
    end

    def caption
      @caption ||= "Digitally Signed By: #{signedby}\nContact: #{contact}\nLocation: #{location}\nDate: #{date.iso8601}"
    end

    def signedby
      "Test"
    end

    def location
      "Barcelona"
    end

    def contact
      "test@example.org"
    end

    def issuer
      "Decidim"
    end

    def date
      Time.current
    end
  end
end
