# frozen_string_literal: true

require 'minitest/autorun'
require_relative 'code_identity'

class CodeIdentityTest < Minitest::Test
  def test_generated_programs_preserve_exact_bytes_for_varied_strings
    samples = [
      "Hello, World!\n", '', "no newline", "two lines\n\n",
      "carriage\r\nreturn", "quote: \" and slash: \\",
      '#{abort "interpolated"}', '%s %n %%', "nul\0byte",
      "café 🐈 e\u0301\n"
    ]

    samples.each do |text|
      generator = CodeIdentity.new(text)
      programs = generator.generate(12)
      assert_equal 12, programs.length
      assert_equal programs.length, programs.uniq.length
      programs.each do |source|
        stdout, stderr, code, signal = generator.observe(source)
        assert_equal text.b, stdout, source
        assert_equal '', stderr, source
        assert_equal 0, code, source
        assert_nil signal, source
      end
      assert generator.verify!(programs)
    end
  end

  def test_exhaustion_and_duplicate_or_cyclic_rules_terminate
    identity = ->(source) { source }
    generator = CodeIdentity.new('x', rules: [identity, identity])
    assert_equal 3, generator.generate(100).length
    assert_equal 3, CodeIdentity.new('x', rules: []).generate(100).length

    a = '"a"'
    b = '("a").reverse.reverse'
    cycle = ->(source) { source == a ? b : a }
    assert_equal 6, CodeIdentity.new('a', rules: [cycle]).generate(100).length
  end

  def test_limits_and_repeat_generation
    generator = CodeIdentity.new('x')
    [1, 2, 3, 4, 20, 101].each do |count|
      assert_equal count, generator.generate(count).length
    end
    assert_equal generator.generate(20), generator.generate(20)
    [0, -1, 2.5, '20', nil].each do |limit|
      assert_raises(ArgumentError) { generator.generate(limit) }
    end
  end

  def test_validation_and_snapshot_of_input
    assert_raises(ArgumentError) { CodeIdentity.new(nil) }
    assert_raises(ArgumentError) { CodeIdentity.new("\xFF".dup.force_encoding('UTF-8')) }
    text = +'original'
    generator = CodeIdentity.new(text)
    text.replace('changed')
    assert_equal 'print("original")', generator.original
  end

  def test_verifier_detects_missing_output_stderr_and_failure
    generator = CodeIdentity.new('x')
    ['print("y")', 'print("x"); warn "oops"', 'print("x"); exit 1'].each do |source|
      assert_raises(RuntimeError) { generator.verify!([source]) }
    end
  end

  def test_command_line_count_check_and_invalid_input
    script = File.join(__dir__, 'code_identity.rb')
    stdout, stderr, status = Open3.capture3(RbConfig.ruby, script, '-n', '4', '--check')
    assert status.success?, stderr
    assert_equal 4, stdout.lines.length
    assert_equal "Verified 4 programs.\n", stderr

    [['-n', '0'], ['-n', 'oops'], ['one', 'two']].each do |arguments|
      stdout, stderr, status = Open3.capture3(RbConfig.ruby, script, *arguments)
      assert_equal '', stdout
      refute_empty stderr
      assert_equal 1, status.exitstatus
    end
  end
end
