# typed: false
require "rails_helper"

RSpec.describe "Domain::User counter caches", type: :model do
  let(:user) { create(:domain_user_fa_user) }
  let(:post) { create(:domain_post_fa_post) }

  describe "user_post_creations_count" do
    it "increments when a post creation is added" do
      expect {
        Domain::UserPostCreation.create!(user: user, post: post)
      }.to change { user.reload.user_post_creations.size }.from(0).to(1)
    end

    it "decrements when a post creation is removed" do
      creation = Domain::UserPostCreation.create!(user: user, post: post)
      expect { creation.destroy }.to change {
        user.reload.user_post_creations.size
      }.from(1).to(0)
    end

    it "provides direct access to the counts" do
      posts = create_list(:domain_post_fa_post, 3)
      posts.each { |p| Domain::UserPostCreation.create!(user: user, post: p) }

      # Counter in database should be updated automatically
      expect(user.reload.user_post_creations.size).to eq(3)

      # Calling the method should use the cached value
      expect(user.user_post_creations.size).to eq(3)
    end

    it "resets to the correct count" do
      posts = create_list(:domain_post_fa_post, 3)
      posts.each { |p| Domain::UserPostCreation.create!(user: user, post: p) }

      # Manually set to wrong value
      user.update_column(:user_post_creations_count, 0)
      expect(user.read_attribute(:user_post_creations_count)).to eq(0)

      # Reset counter
      Domain::User.reset_counters(user.id, :user_post_creations)
      expect(user.reload.user_post_creations_count).to eq(3)
    end
  end

  describe "user_post_favs_count" do
    it "increments when a post fav is added" do
      expect { Domain::UserPostFav.create!(user: user, post: post) }.to change {
        user.reload.user_post_favs.size
      }.from(0).to(1)
    end

    it "decrements when a post fav is removed" do
      fav = Domain::UserPostFav.create!(user: user, post: post)
      expect { fav.destroy }.to change { user.reload.user_post_favs.size }.from(
        1,
      ).to(0)
    end

    it "provides direct access to the counts" do
      posts = create_list(:domain_post_fa_post, 3)
      posts.each { |p| Domain::UserPostFav.create!(user: user, post: p) }

      # Counter in database should be updated automatically
      expect(user.reload.user_post_favs.size).to eq(3)

      # Calling the method should use the cached value
      expect(user.user_post_favs.size).to eq(3)
    end
  end

  describe "user_user_follows_from_count" do
    let(:other_user) { create(:domain_user_fa_user) }

    it "increments when a follow is added" do
      expect {
        Domain::UserUserFollow.create!(from: user, to: other_user)
      }.to change { user.reload.user_user_follows_from.size }.from(0).to(1)
    end

    it "decrements when a follow is removed" do
      follow = Domain::UserUserFollow.create!(from: user, to: other_user)
      expect { follow.destroy }.to change {
        user.reload.user_user_follows_from.size
      }.from(1).to(0)
    end

    it "provides direct access to the counts" do
      users = create_list(:domain_user_fa_user, 3)
      users.each { |u| Domain::UserUserFollow.create!(from: user, to: u) }

      # Counter in database should be updated automatically
      expect(user.reload.user_user_follows_from.size).to eq(3)

      # Calling the method should use the cached value
      expect(user.user_user_follows_from.size).to eq(3)
    end
  end

  describe "user_user_follows_to_count" do
    let(:other_user) { create(:domain_user_fa_user) }

    it "increments when a follow is added" do
      expect {
        Domain::UserUserFollow.create!(from: other_user, to: user)
      }.to change { user.reload.user_user_follows_to.size }.from(0).to(1)
    end

    it "decrements when a follow is removed" do
      follow = Domain::UserUserFollow.create!(from: other_user, to: user)
      expect { follow.destroy }.to change {
        user.reload.user_user_follows_to.size
      }.from(1).to(0)
    end

    it "provides direct access to the counts" do
      users = create_list(:domain_user_fa_user, 3)
      users.each { |u| Domain::UserUserFollow.create!(from: u, to: user) }

      # Counter in database should be updated automatically
      expect(user.reload.user_user_follows_to.size).to eq(3)

      # Calling the method should use the cached value
      expect(user.user_user_follows_to.size).to eq(3)
    end
  end

  describe "counter cache consistency" do
    it "maintains separate counts for creations and favs" do
      # Create 2 posts and fav 3 posts
      created_posts = create_list(:domain_post_fa_post, 2)
      faved_posts = create_list(:domain_post_fa_post, 3)

      created_posts.each do |p|
        Domain::UserPostCreation.create!(user: user, post: p)
      end
      faved_posts.each { |p| Domain::UserPostFav.create!(user: user, post: p) }

      user.reload
      expect(user.user_post_creations_count).to eq(2)
      expect(user.user_post_favs_count).to eq(3)
    end
  end

  it "falls back to sql COUNT when the counter cache is not set" do
    user = create(:domain_user_fa_user)
    post = create(:domain_post_fa_post, creator: user)

    # creation of a post should update the counter cache
    user.reload
    expect(user.user_post_creations_count).to eq(1)
    expect(user.user_post_favs_count).to be_nil

    # insert a fav without updating the counter cache, so the counter cache is nil
    Domain::UserPostFav::FaUserPostFav.insert_all!(
      [{ user_id: user.id, post_id: post.id }],
    )

    user.reload
    expect(user.user_post_favs_count).to be_nil
    expect(user.user_post_favs.size).to eq(1)
    expect(user.user_post_favs.count).to eq(1)

    # recompute the value of the counter cache
    Domain::User.reset_counters(user.id, :user_post_favs)

    user.reload
    expect(user.user_post_favs_count).to eq(1)
    expect(user.user_post_favs.size).to eq(1)
    expect(user.user_post_favs.count).to eq(1)
  end

  it "sets the right value when there was already an existing model" do
    user = create(:domain_user_fa_user)

    # create a post as if it was there prior to the counter cache existing
    create(:domain_post_fa_post, creator: user)
    user.update_column(:user_post_creations_count, nil)

    user.reload
    user = Domain::User.find(user.id)
    expect(user.user_post_creations_count).to eq(nil)
    expect(user.user_post_creations.length).to eq(1)
    expect(user.user_post_creations.count).to eq(1)

    # create a second post
    create(:domain_post_fa_post, creator: user)
    user.reload
    expect(user.user_post_creations_count).to eq(2)
    expect(user.user_post_creations.size).to eq(2)
    expect(user.user_post_creations.length).to eq(2)
    expect(user.user_post_creations.count).to eq(2)

    # and a third to test incrementing
    puts "creating a third post"
    create(:domain_post_fa_post, creator: user)
    user.reload
    expect(user.user_post_creations.count).to eq(3)
    expect(user.user_post_creations_count).to eq(3)
    expect(user.user_post_creations.length).to eq(3)
    expect(user.user_post_creations.size).to eq(3)
  end

  it "works with user_user_follows" do
    user = create(:domain_user_fa_user)
    other_user_1 = create(:domain_user_fa_user)
    other_user_2 = create(:domain_user_fa_user)

    [user, other_user_1, other_user_2].each do |u|
      expect(u.user_user_follows_from_count).to be_nil
      expect(u.user_user_follows_from.size).to eq(0)
      expect(u.user_user_follows_to_count).to be_nil
      expect(u.user_user_follows_to.size).to eq(0)
    end

    Domain::UserUserFollow.create!(from: user, to: other_user_1)
    user.reload
    expect(user.user_user_follows_from_count).to eq(1)
    expect(user.user_user_follows_from.size).to eq(1)
    expect(other_user_1.user_user_follows_to_count).to eq(1)
    expect(other_user_1.user_user_follows_to.size).to eq(1)

    Domain::UserUserFollow.create!(from: user, to: other_user_2)
    user.reload
    expect(user.user_user_follows_from_count).to eq(2)
    expect(user.user_user_follows_from.size).to eq(2)

    Domain::UserUserFollow.create!(from: other_user_1, to: user)
    user.reload
    expect(user.user_user_follows_to_count).to eq(1)
    expect(user.user_user_follows_to.size).to eq(1)
  end
end
