class Job < ApplicationRecord
  attr_accessor :current_user

  belongs_to :customer, class_name: "User", optional: true, counter_cache: :customer_jobs_count, inverse_of: :customer_jobs
  belongs_to :operator, class_name: "User", optional: true, counter_cache: :operator_jobs_count, inverse_of: :operator_jobs
  belongs_to :creator, class_name: "User", optional: true, counter_cache: :created_jobs_count, inverse_of: :created_jobs
  belongs_to :cashier, class_name: "User", optional: true, counter_cache: :cashed_jobs_count, inverse_of: :cashed_jobs

  has_one_attached :pdf, dependent: :purge

  validates_presence_of :customer_firstname, :customer_lastname
  validates_presence_of :cost_center, if: :intern
  validates_presence_of :pdf, unless: :created_by_operator?
  validates :privacy_policy, acceptance: true, unless: :created_by_operator?
  validates :number_of_plans_a0, :number_of_plans_a1, :number_of_plans_a2, :number_of_plans_a3, :costum_qm_plan, numericality: { greater_than_or_equal_to: 0 }
  validate :acceptable_pdf, unless: :created_by_operator?


  before_save :update_status_changed_at, if: :will_save_change_to_status?
  before_save :update_user_status_infos, if: -> { will_save_change_to_status? || new_record? }

  before_save :set_cost_qm
  before_save :calc_cost, if: :printed_pages_changes?

  before_validation :set_customer_infos, unless: :created_by_operator?, on: :create

  # TODO: works only when job is created. Should move analyzer to activestorage :
  # https://discuss.rubyonrails.org/t/active-storage-in-production-lessons-learned-and-in-depth-look-at-how-it-works/83289
  # https://redgreen.no/2021/01/24/custom-analyzer-for-activestorage.html
  # Possible alternative
  # https://github.com/shrinerb/shrine
  after_create_commit :analyze_pdf

  # NOTE: Multiple status if paing before brinting?
  enum :status, {
    open: "open",
    printing: "printing",
    pickup: "pickup",
    paid: "paid",
    canceled: "canceled"
  }

  AVAILABLE_PAGE_FORMATS = [ :a0, :a1, :a2, :a3 ]

  # scope :created_as_operator, -> { where created_as_operator: true }
  # scope :created_as_customer, -> { where created_as_operator: false }
  scope :not_canceled, -> { !canceled }

  # NOTE: only named status are returned because of WHERE/IN clause for the enum values
  scope :in_status_order, -> { in_order_of(:status, %w[open printing pickup paid canceled]) }

  scope :created_today, -> { created_on_day(Time.now) }
  scope :created_on_day, lambda { |date|
                           where("created_at >= ? AND created_at <= ?", date.beginning_of_day, date.end_of_day)
                         }
  scope :updated_today, -> { updated_on_day(Time.now) }
  scope :updated_on_day, lambda { |date|
                            where("updated_at >= ? AND updated_at <= ?", date.beginning_of_day, date.end_of_day)
                          }
  scope :status_changed_today, -> { status_changed_on_day(Time.now) }
  scope :status_changed_on_day, lambda { |date|
                            where("status_changed_at >= ? AND status_changed_at <= ?", date.beginning_of_day, date.end_of_day)
                          }
  scope :created_by_customer, -> { not(:created_by_operator) }

  # Returns all jobs with status: open print pickup and jobs from today with status: paid canceled
  # paid: only updated_at today
  # canceled: only updated_at today
  def self.currently_working_on
    # NOTE: use Time.now instead of Date.today to take the timezone into account
    where(status: %i[open printing pickup])
      .or(Job.where(status: %i[paid canceled])
      .where("status_changed_at >= ?", Time.now.beginning_of_day))
      # .in_status_order
      .order(created_at: :asc)
      # .order(:customer_firstname, :customer_lastname)
      .with_attached_pdf # scope from activestorage for .includes(pdf_attachment: :blob)
    # .references(:pdf_attachment, :blob) # creates big join table
  end

  def self.paidcanceled
    Job.where(status: %i[paid canceled])
  end

  def customer_fullname
    [ customer_firstname, " ", customer_lastname ].join
  end

  def acceptable_pdf
    return unless pdf.attached?

    acceptable_types = [ "application/pdf" ]

    errors.add(:pdf, "is too big") unless pdf.blob.byte_size <= 100.megabyte
    errors.add(:pdf, "must be a PDF") unless acceptable_types.include?(pdf.content_type)
  end

  def able_to_cancel?
    open? || printing?
    # TODO: different check for operator and admin
  end

  # cancel job only if it is still open
  def canceled!
    if able_to_cancel?
      self.status = :canceled
      self.printed_at = nil
      self.paid_at = nil
      save
    end
  end

  def increment_page(din)
    return false unless AVAILABLE_PAGE_FORMATS.include?(din.to_sym)
    public_send("number_of_plans_#{din}=", public_send("number_of_plans_#{din}") + 1) if respond_to? "number_of_plans_#{din}="
    save
  end

  def decrement_page(din)
    return false unless AVAILABLE_PAGE_FORMATS.include?(din.to_sym)
    public_send("number_of_plans_#{din}=", public_send("number_of_plans_#{din}") - 1) if respond_to? "number_of_plans_#{din}="
    save
  end

  def self.report_to_csv(jobs)
    columns_readable = [ "ID", "Kunde Vorname", "Kunde Nachname", "Kassierer Vorname", "Kassierer Nachname", "bezahlt am", "Betrag" ]
    CSV.generate(col_sep: ";") do |csv|
      csv << columns_readable
      jobs.each do |job|
        # csv << job.attributes.values_at(*columns)
        csv << [ job.id, job.customer_firstname, job.customer_lastname, job.cashier_firstname, job.cashier_lastname, job.paid_at.localtime.strftime("%Y-%m-%d"), job.cost.to_s + " €"  ]
      end
    end
  end

  def self.ransackable_attributes(auth_object = nil)
    [ "created_at", "id", "customer_firstname", "customer_lastname", "pdf.", "created_by_operator", "number_of_plans_a0", "number_of_plans_a1", "number_of_plans_a2", "number_of_plans_a3", "costum_qm_plan", "cost", "status" ]
  end

  def self.ransackable_associations(auth_object = nil)
    [ "pdf_blob" ]
  end

  private

  def printed_pages_changes?
    costum_qm_plan_changed? || number_of_plans_a0_changed? || number_of_plans_a1_changed? || number_of_plans_a2_changed? || number_of_plans_a3_changed?
  end

  def update_status_changed_at
    self.status_changed_at = Time.now
  end

  def calc_cost
    self.cost = (number_of_plans_a0 * cost_qm) + (number_of_plans_a1 * cost_qm / 2) +
                (number_of_plans_a2 * cost_qm / 4) + (number_of_plans_a3 * cost_qm / 8) +
                (costum_qm_plan * cost_qm).round(2)
  end

  def set_cost_qm
    # TODO: get cost from global settings
    self.cost_qm = 7
  end

  def analyze_pdf
    # return unless pdf.attached? && pdf.new_record?
    self.number_of_plans_a0 = 0
    self.number_of_plans_a1 = 0
    self.number_of_plans_a2 = 0
    self.number_of_plans_a3 = 0
    self.costum_qm_plan = 0

    # TODO: add any check if attachment has changed

    # pdfs.each do |pdf|
    if pdf.blob
      pdf.blob.open do |file|
        # file = ActiveStorage::Blob.service.path_for(pdf.key).to_s
        pdf_analyzer = Services::PdfAnalyzer.new(file)
        pdf_analyzer.analyze
        self.number_of_plans_a0 += pdf_analyzer.pages_a0
        self.number_of_plans_a1 += pdf_analyzer.pages_a1
        self.number_of_plans_a2 += pdf_analyzer.pages_a2
        self.number_of_plans_a3 += pdf_analyzer.pages_a3
        self.costum_qm_plan += pdf_analyzer.costum_qm
      end
    end
    # end
    calc_cost
    save
  end

  def set_customer_infos
    self.customer = current_user unless self.customer
    self.customer_firstname = customer.firstname
    self.customer_lastname = customer.lastname
  end

  def set_operator_infos
    self.operator = current_user unless self.operator
    self.operator_firstname = operator.firstname
    self.operator_lastname = operator.lastname
  end

  def clear_operator_infos
    self.operator = nil
    self.operator_firstname = nil
    self.operator_lastname = nil
  end

  def operator_infos_set?
    self.operator && self.operator_firstname && self.operator_lastname
  end

  def set_cashier_infos
    self.paid_at = Time.now
    self.cashier = current_user unless self.cashier
    self.cashier_firstname = cashier.firstname
    self.cashier_lastname = cashier.lastname
  end

  def clear_cashier_infos
    self.paid_at = nil
    self.cashier = nil
    self.cashier_firstname = nil
    self.cashier_lastname = nil
  end


  def reset_operator_and_cashier_infos
    clear_operator_infos
    clear_cashier_infos
  end

  def update_user_status_infos
    case status.to_sym
    when :open
      reset_operator_and_cashier_infos
      self.printed_at = nil
      self.paid_at = nil
    when :printing
      set_operator_infos
      clear_cashier_infos
      self.printed_at = nil
    when :pickup
      set_operator_infos unless operator_infos_set?
      clear_cashier_infos
      self.printed_at = Time.now unless self.printed_at
    when :canceled
      clear_operator_infos
      clear_cashier_infos
      self.printed_at = nil
    when :paid
      set_operator_infos unless operator_infos_set?
      self.printed_at = Time.now unless self.printed_at
      set_cashier_infos
      self.paid_at = Time.now
    end
  end

  def reset_to_status_open?
    status_changed? && open?
  end
end
