# frozen_string_literal: true

class PostTiming < ActiveRecord::Base
  belongs_to :topic
  belongs_to :user

  validates_presence_of :post_number
  validates_presence_of :msecs

  def self.pretend_read(topic_id, actual_read_post_number, pretend_read_post_number)
    # This is done in SQL cause the logic is quite tricky and we want to do this in one db hit
    #
    DB.exec("INSERT INTO post_timings(topic_id, user_id, post_number, msecs)
              SELECT :topic_id, user_id, :pretend_read_post_number, 1
              FROM post_timings pt
              WHERE topic_id = :topic_id AND
                    post_number = :actual_read_post_number AND
                    NOT EXISTS (
                        SELECT 1 FROM post_timings pt1
                        WHERE pt1.topic_id = pt.topic_id AND
                              pt1.post_number = :pretend_read_post_number AND
                              pt1.user_id = pt.user_id
                    )
             ",
                pretend_read_post_number: pretend_read_post_number,
                topic_id: topic_id,
                actual_read_post_number: actual_read_post_number
            )

    TopicUser.ensure_consistency!(topic_id)
  end

  def self.record_new_timing(args)
    row_count = DB.exec("INSERT INTO post_timings (topic_id, user_id, post_number, msecs)
              SELECT :topic_id, :user_id, :post_number, :msecs
              ON CONFLICT DO NOTHING",
            args)

    # concurrency is hard, we are not running serialized so this can possibly
    # still happen, if it happens we just don't care, its an invalid record anyway
    return if row_count == 0
    Post.where(['topic_id = :topic_id and post_number = :post_number', args]).update_all 'reads = reads + 1'

    return if Topic.exists?(id: args[:topic_id], archetype: Archetype.private_message)
    UserStat.where(user_id: args[:user_id]).update_all 'posts_read_count = posts_read_count + 1'
  end

  # Increases a timer if a row exists, otherwise create it
  def self.record_timing(args)
    rows = DB.exec(<<~SQL, args)
      UPDATE post_timings
       SET msecs = msecs + :msecs
       WHERE topic_id = :topic_id
        AND user_id = :user_id
        AND post_number = :post_number
    SQL

    record_new_timing(args) if rows == 0
  end

  def self.destroy_last_for(user, topic_id)
    topic = Topic.find(topic_id)
    post_number = user.staff? ? topic.highest_staff_post_number : topic.highest_post_number

    last_read = post_number - 1

    PostTiming.transaction do
      PostTiming.where("topic_id = ? AND user_id = ? AND post_number > ?", topic.id, user.id, last_read).delete_all
      if last_read < 1
        last_read = nil
      end

      TopicUser.where(user_id: user.id, topic_id: topic.id).update_all(
        highest_seen_post_number: last_read,
        last_read_post_number: last_read
      )

      topic.posts.find_by(post_number: post_number).decrement!(:reads)

      if topic.private_message?
        set_minimum_first_unread_pm!(topic: topic, user_id: user.id, date: topic.updated_at)
      else
        set_minimum_first_unread!(user_id: user.id, date: topic.updated_at)
      end
    end
  end

  def self.destroy_for(user_id, topic_ids)
    PostTiming.transaction do
      PostTiming
        .where('user_id = ? and topic_id in (?)', user_id, topic_ids)
        .delete_all

      TopicUser
        .where('user_id = ? and topic_id in (?)', user_id, topic_ids)
        .delete_all

      Post.where(topic_id: topic_ids).update_all('reads = reads - 1')

      date = Topic.listable_topics.where(id: topic_ids).minimum(:updated_at)

      if date
        set_minimum_first_unread!(user_id: user_id, date: date)
      end
    end
  end

  def self.set_minimum_first_unread_pm!(topic:, user_id:, date:)
    if topic.topic_allowed_users.exists?(user_id: user_id)
      UserStat.where("first_unread_pm_at > ? AND user_id = ?", date, user_id)
        .update_all(first_unread_pm_at: date)
    else
      DB.exec(<<~SQL, date: date, user_id: user_id, topic_id: topic.id)
      UPDATE group_users gu
      SET first_unread_pm_at = :date
      FROM (
        SELECT
          gu2.user_id,
          gu2.group_id
        FROM group_users gu2
        INNER JOIN topic_allowed_groups tag ON tag.group_id = gu2.group_id AND tag.topic_id = :topic_id
        WHERE gu2.user_id = :user_id
      ) Y
      WHERE gu.user_id = Y.user_id AND gu.group_id = Y.group_id
      SQL
    end
  end

  def self.set_minimum_first_unread!(user_id:, date:)
    DB.exec(<<~SQL, date: date, user_id: user_id)
      UPDATE user_stats
      SET first_unread_at = :date
      WHERE first_unread_at > :date AND
            user_id = :user_id
    SQL
  end

  MAX_READ_TIME_PER_BATCH = 60 * 1000.0

  def self.process_timings(current_user, topic_id, topic_time, timings, opts = {})
    UserStat.update_time_read!(current_user.id)

    max_time_per_post = ((Time.now - current_user.created_at) * 1000.0)
    max_time_per_post = MAX_READ_TIME_PER_BATCH if max_time_per_post > MAX_READ_TIME_PER_BATCH

    highest_seen = 1
    new_posts_read = 0

    join_table = []

    i = timings.length
    while i > 0
      i -= 1
      timings[i][1] = max_time_per_post if timings[i][1] > max_time_per_post
      timings.delete_at(i) if timings[i][0] < 1
    end

    timings.each_with_index do |(post_number, time), index|

      join_table << "SELECT #{topic_id.to_i} topic_id, #{post_number.to_i} post_number,
                     #{current_user.id.to_i} user_id, #{time.to_i} msecs, #{index} idx"

        highest_seen = post_number.to_i > highest_seen ?
                       post_number.to_i : highest_seen
    end

    if join_table.length > 0
      sql = <<~SQL
      UPDATE post_timings t
      SET msecs = LEAST(t.msecs::bigint + x.msecs, 2^31 - 1)
      FROM (#{join_table.join(" UNION ALL ")}) x
      WHERE x.topic_id = t.topic_id AND
            x.post_number = t.post_number AND
            x.user_id = t.user_id
      RETURNING x.idx
SQL

      existing = Set.new(DB.query_single(sql))

      sql = <<~SQL
      SELECT 1 FROM topics
      WHERE deleted_at IS NULL AND
        archetype = 'regular' AND
        id = :topic_id
      SQL

      is_regular = DB.exec(sql, topic_id: topic_id) == 1
      new_posts_read = timings.size - existing.size if is_regular

      timings.each_with_index do |(post_number, time), index|
        unless existing.include?(index)
          PostTiming.record_new_timing(topic_id: topic_id,
                                       post_number: post_number,
                                       user_id: current_user.id,
                                       msecs: time)
        end
      end
    end

    total_changed = 0
    if timings.length > 0
      total_changed = Notification.mark_posts_read(current_user, topic_id, timings.map { |t| t[0] })
    end

    topic_time = max_time_per_post if topic_time > max_time_per_post

    TopicUser.update_last_read(current_user, topic_id, highest_seen, new_posts_read, topic_time, opts)
    TopicGroup.update_last_read(current_user, topic_id, highest_seen)

    if total_changed > 0
      current_user.reload
      current_user.publish_notifications_state
    end
  end
end

# == Schema Information
#
# Table name: post_timings
#
#  topic_id    :integer          not null
#  post_number :integer          not null
#  user_id     :integer          not null
#  msecs       :integer          not null
#
# Indexes
#
#  index_post_timings_on_user_id  (user_id)
#  post_timings_summary           (topic_id,post_number)
#  post_timings_unique            (topic_id,post_number,user_id) UNIQUE
#
