class Shot < ApplicationRecord
  include ShotPresenter
  include Airtablable
  include Jsonable
  include DateParseable

  DAILY_LIMIT = 50
  LIST_ATTRIBUTES = %i[id coffee_bag_id start_time profile_title user_id bean_weight drink_weight drink_tds drink_tds drink_ey espresso_enjoyment barista bean_brand bean_type duration grinder_model grinder_setting roast_level roast_date].freeze

  belongs_to :user, optional: true, touch: true
  belongs_to :coffee_bag, optional: true
  belongs_to :canonical_coffee_bag, optional: true
  has_one :information, class_name: "ShotInformation", dependent: :destroy, inverse_of: :shot
  has_many :shared_shots, dependent: :destroy
  has_many :shot_tags, dependent: :destroy
  has_many :tags, through: :shot_tags

  has_one_attached :image do |attachable|
    attachable.variant :display, resize_to_limit: [1000, 500], format: :jpeg, saver: {strip: true}
    attachable.variant :thumb, resize_to_limit: [200, 200], format: :jpeg, saver: {strip: true}
  end

  validates :start_time, :sha, :user, presence: true
  validate :daily_limit, on: :create

  before_validation :refresh_coffee_bag_fields, if: -> { coffee_bag_id_changed? || canonical_coffee_bag_id_changed? }
  broadcasts_to ->(shot) { [shot.user, :shots] }, inserts_by: :prepend, locals: {user_override: true}
  after_create_commit :send_web_push_notification

  scope :visible, -> { where(public: true) }
  scope :visible_or_owned_by_id, ->(user_id) { user_id ? visible.or(where(user_id:)) : visible }
  scope :for_list, -> { select(LIST_ATTRIBUTES).includes(:tags, coffee_bag: :roaster) }
  scope :by_start_time, -> { order(start_time: :desc) }
  scope :premium, -> { where(created_at: ..1.month.ago) }
  scope :non_premium, -> { where(created_at: 1.month.ago..) }

  def self.from_file(user, file_content)
    return Shot.new(user:) if file_content.blank?

    Parsers::Base.parser_for(file_content).build_shot(user)
  end

  def metadata
    super.presence || {}
  end

  def refresh_coffee_bag_fields
    if coffee_bag
      self.bean_brand = coffee_bag&.roaster&.name
      self.bean_type = coffee_bag&.name
      self.roast_date = coffee_bag&.roast_date&.strftime(user.date_format_string)
      self.roast_level = coffee_bag&.roast_level
      self.canonical_coffee_bag_id = coffee_bag&.canonical_coffee_bag_id
    elsif canonical_coffee_bag
      self.bean_brand = canonical_coffee_bag&.canonical_roaster&.name
      self.bean_type = canonical_coffee_bag&.name
    end
  end

  def related_shots(limit: 15)
    query = self.class.where(user:).where.not(id:).limit(limit)
    query = query.non_premium unless Current.user&.premium?
    query.where(start_time: start_time..).order(:start_time) + [self] + query.where(start_time: ..start_time).order(start_time: :desc)
  end

  def parsed_roast_date
    date_format_string = user&.date_format_string || User::DATE_FORMATS["dd.mm.yyyy"]
    parse_date(roast_date, date_format_string)
  end

  def iso8601_roast_date_time
    parsed_roast_date&.strftime("%Y-%m-%dT%H:%M:%SZ")
  end

  def tag_list=(value)
    return unless user.premium?

    self.tags = value.split(",").map { it.squish.gsub(/[^\w\s-]/, "").downcase }.compact_blank.uniq.map { |name| user.tags.find_or_create_by(name:) }.select(&:persisted?)
  end

  def tag_list
    tags.pluck(:name).sort.join(",")
  end

  private

  def daily_limit
    return if user.premium?
    return if self.class.where(user_id:).where("start_time > NOW() - INTERVAL '1 day'").count < DAILY_LIMIT

    errors.add(:base, :over_daily_limit, message: "You've reached your daily limit of #{DAILY_LIMIT} shots. Please consider upgrading to a premium account.")
  end

  def send_web_push_notification
    user&.push_subscriptions&.each do |push_subscription|
      WebPushJob.perform_later(push_subscription, title: "New Shot", body: "See your new #{profile_title} shot 👀", path: "/shots/#{id}")
    end
  end
end

# == Schema Information
#
# Table name: shots
#
#  id                      :uuid             not null, primary key
#  barista                 :string
#  bean_brand              :string
#  bean_notes              :text
#  bean_type               :string
#  bean_weight             :string
#  drink_ey                :string
#  drink_tds               :string
#  drink_weight            :string
#  duration                :float
#  espresso_enjoyment      :integer
#  espresso_notes          :text
#  grinder_model           :string
#  grinder_setting         :string
#  metadata                :jsonb
#  private_notes           :text
#  profile_title           :string
#  public                  :boolean
#  roast_date              :string
#  roast_level             :string
#  sha                     :string
#  start_time              :datetime
#  created_at              :datetime         not null
#  updated_at              :datetime         not null
#  airtable_id             :string
#  canonical_coffee_bag_id :uuid
#  coffee_bag_id           :uuid
#  user_id                 :uuid
#
# Indexes
#
#  index_shots_on_airtable_id              (airtable_id)
#  index_shots_on_bean_brand               (bean_brand) USING gin
#  index_shots_on_bean_type                (bean_type) USING gin
#  index_shots_on_canonical_coffee_bag_id  (canonical_coffee_bag_id)
#  index_shots_on_coffee_bag_id            (coffee_bag_id)
#  index_shots_on_created_at               (created_at)
#  index_shots_on_sha                      (sha)
#  index_shots_on_start_time               (start_time)
#  index_shots_on_user_id                  (user_id)
#
# Foreign Keys
#
#  fk_rails_...  (canonical_coffee_bag_id => canonical_coffee_bags.id)
#  fk_rails_...  (coffee_bag_id => coffee_bags.id)
#  fk_rails_...  (user_id => users.id)
#
