# frozen_string_literal: true

RSpec.describe ActiveSupport::TaggedLogging do
  class MyLogger < ActiveSupport::Logger
    def flush(*)
      info "[FLUSHED]"
    end
  end

  before do
    @output = StringIO.new
    @logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@output))
  end

  it "has a Formatter" do
    expect { described_class::Formatter }.to not_raise_error
  end

  it "sets logger.formatter if missing and extends it with a tagging API" do
    logger = Logger.new(StringIO.new)
    assert_nil logger.formatter

    other_logger = ActiveSupport::TaggedLogging.new(logger)
    assert_not_nil other_logger.formatter
    assert_respond_to other_logger.formatter, :tagged
  end

  it "tagged once" do
    @logger.tagged("BCX") { @logger.info "Funky time" }
    assert_equal "[BCX] Funky time\n", @output.string
  end

  it "tagged twice" do
    @logger.tagged("BCX") { @logger.tagged("Jason") { @logger.info "Funky time" } }
    assert_equal "[BCX] [Jason] Funky time\n", @output.string
  end

  it "tagged thrice at once" do
    @logger.tagged("BCX", "Jason", "New") { @logger.info "Funky time" }
    assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string
  end

  it "tagged with an array" do
    @logger.tagged(%w(BCX Jason New)) { @logger.info "Funky time" }
    assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string
  end

  it "tagged are flattened" do
    @logger.tagged("BCX", %w(Jason New)) { @logger.info "Funky time" }
    assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string
  end

  it "push and pop tags directly" do
    assert_equal %w(A B C), @logger.push_tags("A", ["B", "  ", ["C"]])
    @logger.info "a"
    assert_equal %w(C), @logger.pop_tags
    @logger.info "b"
    assert_equal %w(B), @logger.pop_tags(1)
    @logger.info "c"
    assert_equal [], @logger.clear_tags!
    @logger.info "d"
    assert_equal "[A] [B] [C] a\n[A] [B] b\n[A] c\nd\n", @output.string
  end

  it "does not strip message content" do
    @logger.info "  Hello"
    assert_equal "  Hello\n", @output.string
  end

  it "provides access to the logger instance" do
    @logger.tagged("BCX") { |logger| logger.info "Funky time" }
    assert_equal "[BCX] Funky time\n", @output.string
  end

  it "tagged once with blank and nil" do
    @logger.tagged(nil, "", "New") { @logger.info "Funky time" }
    assert_equal "[New] Funky time\n", @output.string
  end

  it "keeps each tag in their own thread" do
    @logger.tagged("BCX") do
      Thread.new do
        @logger.info "Dull story"
        @logger.tagged("OMG") { @logger.info "Cool story" }
      end.join
      @logger.info "Funky time"
    end
    assert_equal "Dull story\n[OMG] Cool story\n[BCX] Funky time\n", @output.string
  end

  it "keeps each tag in their own thread even when pushed directly" do
    Thread.new do
      @logger.push_tags("OMG")
      @logger.info "Cool story"
    end.join
    @logger.info "Funky time"
    assert_equal "[OMG] Cool story\nFunky time\n", @output.string
  end

  it "keeps each tag in their own instance" do
    other_output = StringIO.new
    other_logger = ActiveSupport::TaggedLogging.new(MyLogger.new(other_output))
    @logger.tagged("OMG") do
      other_logger.tagged("BCX") do
        @logger.info "Cool story"
        other_logger.info "Funky time"
      end
    end
    assert_equal "[OMG] Cool story\n", @output.string
    assert_equal "[BCX] Funky time\n", other_output.string
  end

  it "does not share the same formatter instance of the original logger" do
    other_logger = ActiveSupport::TaggedLogging.new(@logger)

    @logger.tagged("OMG") do
      other_logger.tagged("BCX") do
        @logger.info "Cool story"
        other_logger.info "Funky time"
      end
    end
    assert_equal "[OMG] Cool story\n[BCX] Funky time\n", @output.string
  end

  it "cleans up the taggings on flush" do
    @logger.tagged("BCX") do
      Thread.new do
        @logger.tagged("OMG") do
          @logger.flush
          @logger.info "Cool story"
        end
      end.join
    end
    assert_equal "[FLUSHED]\nCool story\n", @output.string
  end

  it "mixed levels of tagging" do
    @logger.tagged("BCX") do
      @logger.tagged("Jason") { @logger.info "Funky time" }
      @logger.info "Junky time!"
    end

    assert_equal "[BCX] [Jason] Funky time\n[BCX] Junky time!\n", @output.string
  end

  it "implicit logger instance" do
    @output = StringIO.new
    @logger = ActiveSupport::TaggedLogging.logger(@output)

    @logger.tagged("BCX") { @logger.info "Funky time" }
    assert_equal "[BCX] Funky time\n", @output.string
  end

  context "without block" do
    before do
      @output = StringIO.new
      @logger = ActiveSupport::TaggedLogging.new(Logger.new(@output))
    end

    it "tagged once" do
      @logger.tagged("BCX").info "Funky time"
      assert_equal "[BCX] Funky time\n", @output.string
    end

    it "tagged twice" do
      @logger.tagged("BCX").tagged("Jason").info "Funky time"
      assert_equal "[BCX] [Jason] Funky time\n", @output.string
    end

    it "tagged thrice at once" do
      @logger.tagged("BCX", "Jason", "New").info "Funky time"
      assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string
    end

    it "tagged are flattened" do
      @logger.tagged("BCX", %w(Jason New)).info "Funky time"
      assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string
    end

    it "tagged once with blank and nil" do
      @logger.tagged(nil, "", "New").info "Funky time"
      assert_equal "[New] Funky time\n", @output.string
    end

    it "shares tags across threads" do
      logger = @logger.tagged("BCX")

      Thread.new do
        logger.info "Dull story"
        logger.tagged("OMG").info "Cool story"
      end.join

      logger.info "Funky time"

      assert_equal "[BCX] Dull story\n[BCX] [OMG] Cool story\n[BCX] Funky time\n", @output.string
    end

    it "keeps each tag in their own instance" do
      other_output = StringIO.new
      other_logger = ActiveSupport::TaggedLogging.new(Logger.new(other_output))

      tagged_logger = @logger.tagged("OMG")
      other_tagged_logger = other_logger.tagged("BCX")
      tagged_logger.info "Cool story"
      other_tagged_logger.info "Funky time"

      assert_equal "[OMG] Cool story\n", @output.string
      assert_equal "[BCX] Funky time\n", other_output.string
    end

    it "does not share the same formatter instance of the original logger" do
      other_logger = ActiveSupport::TaggedLogging.new(@logger)

      tagged_logger = @logger.tagged("OMG")
      other_tagged_logger = other_logger.tagged("BCX")
      tagged_logger.info "Cool story"
      other_tagged_logger.info "Funky time"

      assert_equal "[OMG] Cool story\n[BCX] Funky time\n", @output.string
    end

    it "mixed levels of tagging" do
      logger = @logger.tagged("BCX")
      logger.tagged("Jason").info "Funky time"
      logger.info "Junky time!"

      assert_equal "[BCX] [Jason] Funky time\n[BCX] Junky time!\n", @output.string
    end

    it "keeps broadcasting functionality" do
      broadcast_output = StringIO.new
      broadcast_logger = ActiveSupport::BroadcastLogger.new(Logger.new(broadcast_output), @logger)
      logger_with_tags = ActiveSupport::TaggedLogging.new(broadcast_logger)

      tagged_logger = logger_with_tags.tagged("OMG")
      tagged_logger.info "Broadcasting..."

      assert_equal "[OMG] Broadcasting...\n", @output.string
      assert_equal "[OMG] Broadcasting...\n", broadcast_output.string
    end

    it "keeps formatter singleton class methods" do
      plain_output = StringIO.new
      plain_logger = Logger.new(plain_output)
      plain_logger.formatter = Logger::Formatter.new
      plain_logger.formatter.extend(Module.new {
        def crozz_method
        end
      })

      tagged_logger = ActiveSupport::TaggedLogging.new(plain_logger)
      assert_respond_to tagged_logger.formatter, :tagged
      assert_respond_to tagged_logger.formatter, :crozz_method
    end

    it "accepts non-String objects" do
      @logger.tagged("tag") { @logger.info [1, 2, 3] }
      assert_equal "[tag] [1, 2, 3]\n", @output.string
    end
  end
end
