# frozen_string_literal: true

RSpec.describe ProblemCheck do
  around do |example|
    ScheduledCheck = Class.new(described_class) { self.perform_every = 30.minutes }
    RealtimeCheck = Class.new(described_class)
    InlineCheck = Class.new(described_class) { self.inline = true }
    PluginCheck = Class.new(described_class)
    DisabledCheck = Class.new(described_class) { self.enabled = false }
    MultiTargetCheck = Class.new(described_class) { self.targets = -> { %w[foo bar] } }
    FailingCheck =
      Class.new(described_class) do
        def call
          problem
        end

        def translation_key
          "failing_check"
        end
      end
    PassingCheck =
      Class.new(described_class) do
        def call
          no_problem
        end

        def translation_key
          "passing_check"
        end
      end

    stub_const(
      described_class,
      "CORE_PROBLEM_CHECKS",
      [
        ScheduledCheck,
        RealtimeCheck,
        InlineCheck,
        DisabledCheck,
        MultiTargetCheck,
        FailingCheck,
        PassingCheck,
      ],
      &example
    )

    Object.send(:remove_const, ScheduledCheck.name)
    Object.send(:remove_const, RealtimeCheck.name)
    Object.send(:remove_const, InlineCheck.name)
    Object.send(:remove_const, DisabledCheck.name)
    Object.send(:remove_const, MultiTargetCheck.name)
    Object.send(:remove_const, PluginCheck.name)
    Object.send(:remove_const, FailingCheck.name)
    Object.send(:remove_const, PassingCheck.name)
  end

  let(:scheduled_check) { ScheduledCheck }
  let(:realtime_check) { RealtimeCheck }
  let(:inline_check) { InlineCheck }
  let(:enabled_check) { RealtimeCheck }
  let(:disabled_check) { DisabledCheck }
  let(:multi_target_check) { MultiTargetCheck }
  let(:plugin_check) { PluginCheck }
  let(:failing_check) { FailingCheck }
  let(:passing_check) { PassingCheck }

  describe ".[]" do
    it { expect(described_class[:scheduled_check]).to eq(scheduled_check) }
    it { expect(described_class[:foo]).to eq(nil) }
  end

  describe ".identifier" do
    it { expect(scheduled_check.identifier).to eq(:scheduled_check) }
  end

  describe ".checks" do
    it { expect(described_class.checks).to include(scheduled_check, realtime_check, inline_check) }
  end

  describe ".scheduled" do
    it { expect(described_class.scheduled).to include(scheduled_check) }
    it { expect(described_class.scheduled).not_to include(realtime_check) }
    it { expect(described_class.scheduled).not_to include(inline_check) }
  end

  describe ".realtime" do
    it { expect(described_class.realtime).to include(realtime_check) }
    it { expect(described_class.realtime).not_to include(scheduled_check) }
    it { expect(described_class.realtime).not_to include(inline_check) }
  end

  describe ".scheduled?" do
    it { expect(scheduled_check).to be_scheduled }
    it { expect(realtime_check).to_not be_scheduled }
    it { expect(inline_check).to_not be_scheduled }
  end

  describe ".realtime?" do
    it { expect(realtime_check).to be_realtime }
    it { expect(scheduled_check).to_not be_realtime }
    it { expect(inline_check).to_not be_realtime }
  end

  describe ".inline?" do
    it { expect(inline_check).to be_inline }
    it { expect(realtime_check).to_not be_inline }
    it { expect(scheduled_check).to_not be_inline }
  end

  describe ".enabled?" do
    it { expect(enabled_check).to be_enabled }
    it { expect(disabled_check).not_to be_enabled }
  end

  describe ".targeted?" do
    it { expect(scheduled_check).not_to be_targeted }
    it { expect(multi_target_check).to be_targeted }
  end

  describe "plugin problem check registration" do
    before { DiscoursePluginRegistry.register_problem_check(PluginCheck, stub(enabled?: enabled)) }

    after { DiscoursePluginRegistry.reset! }

    context "when the plugin is enabled" do
      let(:enabled) { true }

      it { expect(described_class.checks).to include(plugin_check) }
    end

    context "when the plugin is disabled" do
      let(:enabled) { false }

      it { expect(described_class.checks).not_to include(plugin_check) }
    end
  end

  describe "#run" do
    context "when check is failing" do
      it { expect { failing_check.new.run }.to change { ProblemCheckTracker.failing.count }.by(1) }
    end

    context "when check is passing" do
      it { expect { passing_check.new.run }.to change { ProblemCheckTracker.passing.count }.by(1) }
    end

    context "when targeted check is initialized with no target" do
      context "when a tracker exists" do
        before do
          ProblemCheckTracker.create!(
            identifier: "multi_target_check",
            target: ProblemCheck::NO_TARGET,
          )
        end

        it "deletes the tracker" do
          expect { multi_target_check.new.run }.to change { ProblemCheckTracker.count }.by(-1)
        end
      end

      context "when a tracker does not exist" do
        it "does nothing" do
          expect { multi_target_check.new.run }.not_to change { ProblemCheckTracker.count }
        end
      end
    end

    context "when targeted check has an outdated target" do
      before { ProblemCheckTracker.create!(identifier: "multi_target_check", target: "baz") }

      it "deletes the tracker" do
        expect { multi_target_check.new("baz").run }.to change { ProblemCheckTracker.count }.by(-1)
      end
    end
  end

  describe "#cleanup_trackers" do
    before do
      ProblemCheckTracker.create!(identifier: "multi_target_check", target: "foo")
      ProblemCheckTracker.create!(identifier: "multi_target_check", target: "bar")
      ProblemCheckTracker.create!(identifier: "multi_target_check", target: "baz")
    end

    it "deletes trackers with non-existing targets" do
      expect { multi_target_check.cleanup_trackers }.to change {
        ProblemCheckTracker.pluck(:target)
      }.from(contain_exactly("foo", "bar", "baz")).to(contain_exactly("foo", "bar"))
    end

    context "when targets returns an empty array" do
      before { MultiTargetCheck.targets = -> { [] } }
      after { MultiTargetCheck.targets = -> { %w[foo bar] } }

      it "does not delete any trackers" do
        expect { multi_target_check.cleanup_trackers }.not_to change { ProblemCheckTracker.count }
      end
    end
  end
end
