# frozen_string_literal: true

require_relative 'test_helper'
require 'open3'

class TestValidate < Minitest::Test
  include TestHelpers

  def setup
    setup_temp_dirs
    @plugin_dir = File.join(@tmpdir, 'my-plugin')
  end

  def teardown
    teardown_temp_dirs
  end

  def build_valid_plugin(name: 'my-plugin', description: 'Does a thing', skills: nil)
    extra = skills ? { 'skills' => skills } : {}
    create_plugin_json(@plugin_dir, name: name, description: description, extra: extra)
    create_skill(@plugin_dir, 'my-skill')
    create_marketplace(plugins: [
                          { 'name' => name, 'source' => "./#{File.basename(@plugin_dir)}", 'description' => description }
                        ])
  end

  def make_validate_op(overrides = {})
    params = { plugin_path: @plugin_dir, marketplace_file: marketplace_path }.merge(overrides)
    PluginConfig::Operations::Validate.new(params)
  end

  # ─── Happy path ───────────────────────────────────────────────────────────

  def test_valid_plugin_passes_all_checks
    build_valid_plugin

    result = make_validate_op.call

    assert_equal 'ok', result[:status]
    assert result[:valid]
    assert_empty result[:issues]
    result[:checks].each_value do |check|
      assert_equal 'pass', check[:status], "Expected pass, got #{check.inspect}"
    end
  end

  # ─── Failure modes ────────────────────────────────────────────────────────

  def test_missing_plugin_json_fails
    FileUtils.mkdir_p(@plugin_dir)
    create_marketplace(plugins: [])

    result = make_validate_op.call

    refute result[:valid]
    assert_equal 'issues_found', result[:status]
    assert_equal 'fail', result[:checks][:plugin_json][:status]
  end

  def test_invalid_json_plugin_json_fails
    path = File.join(@plugin_dir, '.claude-plugin', 'plugin.json')
    FileUtils.mkdir_p(File.dirname(path))
    File.write(path, '{ not valid json')
    create_marketplace(plugins: [])

    result = make_validate_op.call

    refute result[:valid]
    assert_equal 'fail', result[:checks][:plugin_json][:status]
  end

  def test_missing_required_field_fails
    create_plugin_json(@plugin_dir, name: 'my-plugin', description: '')
    create_skill(@plugin_dir, 'my-skill')
    create_marketplace(plugins: [{ 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => '' }])

    result = make_validate_op.call

    refute result[:valid]
    assert_equal 'fail', result[:checks][:required_fields][:status]
  end

  def test_name_mismatch_with_directory_fails
    build_valid_plugin(name: 'totally-different-name')

    result = make_validate_op.call

    refute result[:valid]
    assert_equal 'fail', result[:checks][:name_matches_directory][:status]
  end

  def test_missing_marketplace_entry_fails
    create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
    create_skill(@plugin_dir, 'my-skill')
    create_marketplace(plugins: []) # no entry for my-plugin

    result = make_validate_op.call

    refute result[:valid]
    assert_equal 'fail', result[:checks][:marketplace_entry][:status]
  end

  def test_description_drift_fails
    create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing.')
    create_skill(@plugin_dir, 'my-skill')
    create_marketplace(plugins: [
                          { 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => 'Does a thing' }
                        ])

    result = make_validate_op.call

    refute result[:valid]
    assert_equal 'fail', result[:checks][:description_matches][:status]
  end

  def test_missing_skill_md_fails
    create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
    create_skill(@plugin_dir, 'my-skill', with_skill_md: false)
    create_marketplace(plugins: [
                          { 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => 'Does a thing' }
                        ])

    result = make_validate_op.call

    refute result[:valid]
    assert_equal 'fail', result[:checks][:skill_md_present][:status]
  end

  def test_skills_array_mismatch_fails
    build_valid_plugin(skills: %w[my-skill a-second-skill-not-on-disk])

    result = make_validate_op.call

    refute result[:valid]
    assert_equal 'fail', result[:checks][:skills_array_matches][:status]
  end

  def test_wrong_marketplace_source_fails
    create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
    create_skill(@plugin_dir, 'my-skill')
    create_marketplace(plugins: [
                          { 'name' => 'my-plugin', 'source' => './wrong-name', 'description' => 'Does a thing' }
                        ])

    result = make_validate_op.call

    refute result[:valid]
    assert_equal 'fail', result[:checks][:marketplace_source][:status]
  end

  def test_marketplace_source_resolves_path_for_nested_repo_layout
    # marketplace.json lives at <root>/.claude-plugin/marketplace.json, and plugins
    # live under <root>/plugins/<name> instead of directly under <root>.
    root = File.join(@tmpdir, 'nested-repo')
    plugin_dir = File.join(root, 'plugins', 'my-plugin')
    create_plugin_json(plugin_dir, name: 'my-plugin', description: 'Does a thing')
    create_skill(plugin_dir, 'my-skill')

    nested_marketplace_dir = File.join(root, '.claude-plugin')
    FileUtils.mkdir_p(nested_marketplace_dir)
    nested_marketplace_path = File.join(nested_marketplace_dir, 'marketplace.json')
    File.write(nested_marketplace_path, JSON.pretty_generate(
                 'name' => 'test-marketplace',
                 'plugins' => [
                   { 'name' => 'my-plugin', 'source' => './plugins/my-plugin', 'description' => 'Does a thing' }
                 ]
               ))

    result = PluginConfig::Operations::Validate.new(
      plugin_path: plugin_dir, marketplace_file: nested_marketplace_path
    ).call

    assert_equal 'pass', result[:checks][:marketplace_source][:status], result[:checks][:marketplace_source].inspect
  end

  # ─── Cross-repo: graceful skip when no marketplace can be resolved ────────

  def test_no_marketplace_resolvable_skips_marketplace_checks_without_failing
    create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
    create_skill(@plugin_dir, 'my-skill')
    # No marketplace_file override, and no default marketplace.json anywhere near @plugin_dir.

    result = PluginConfig::Operations::Validate.new(plugin_path: @plugin_dir).call

    assert_equal 'ok', result[:status]
    assert result[:valid]
    %i[marketplace_entry description_matches marketplace_source].each do |key|
      assert_equal 'skipped', result[:checks][key][:status], "Expected #{key} to be skipped, got #{result[:checks][key].inspect}"
    end
    %i[plugin_json required_fields name_matches_directory skill_md_present skills_array_matches].each do |key|
      assert_equal 'pass', result[:checks][key][:status]
    end
  end

  def test_explicit_missing_marketplace_file_still_fails
    create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
    create_skill(@plugin_dir, 'my-skill')
    nonexistent = File.join(@tmpdir, 'does-not-exist.json')

    result = PluginConfig::Operations::Validate.new(
      plugin_path: @plugin_dir, marketplace_file: nonexistent
    ).call

    refute result[:valid]
    assert_equal 'fail', result[:checks][:marketplace_entry][:status]
  end

  def test_default_marketplace_resolution_used_when_present
    create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
    create_skill(@plugin_dir, 'my-skill')
    default_dir = File.join(@tmpdir, '.claude-plugin')
    FileUtils.mkdir_p(default_dir)
    File.write(File.join(default_dir, 'marketplace.json'), JSON.pretty_generate(
                 'name' => 'test-marketplace',
                 'plugins' => [
                   { 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => 'Does a thing' }
                 ]
               ))

    result = PluginConfig::Operations::Validate.new(plugin_path: @plugin_dir).call

    assert_equal 'ok', result[:status]
    assert result[:valid]
    result[:checks].each_value { |check| assert_equal 'pass', check[:status] }
  end
end

# ─── Entry point (real exit code contract) ───────────────────────────────────

class TestValidateEntryPoint < Minitest::Test
  include TestHelpers

  SCRIPT_PATH = File.expand_path('../plugin_config.rb', __dir__)

  def setup
    setup_temp_dirs
    @plugin_dir = File.join(@tmpdir, 'my-plugin')
  end

  def teardown
    teardown_temp_dirs
  end

  def run_script(*args)
    Open3.capture3('ruby', SCRIPT_PATH, *args)
  end

  def test_valid_plugin_exits_0
    create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
    create_skill(@plugin_dir, 'my-skill')
    create_marketplace(plugins: [
                          { 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => 'Does a thing' }
                        ])

    stdout, stderr, status = run_script(
      'validate',
      "--plugin_path=#{@plugin_dir}",
      "--marketplace_file=#{marketplace_path}"
    )

    assert status.success?, "Expected exit 0, got #{status.exitstatus}. stderr: #{stderr}"
    parsed = JSON.parse(stdout)
    assert_equal 'ok', parsed['status']
  end

  def test_invalid_plugin_exits_1
    create_marketplace(plugins: []) # no plugin.json created at all

    stdout, _stderr, status = run_script(
      'validate',
      "--plugin_path=#{@plugin_dir}",
      "--marketplace_file=#{marketplace_path}"
    )

    assert_equal 1, status.exitstatus
    parsed = JSON.parse(stdout)
    assert_equal 'issues_found', parsed['status']
  end

  def test_validate_is_cwd_independent_with_absolute_plugin_path_and_default_marketplace
    create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
    create_skill(@plugin_dir, 'my-skill')
    default_dir = File.join(@tmpdir, '.claude-plugin')
    FileUtils.mkdir_p(default_dir)
    File.write(File.join(default_dir, 'marketplace.json'), JSON.pretty_generate(
                 'name' => 'test-marketplace',
                 'plugins' => [
                   { 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => 'Does a thing' }
                 ]
               ))

    other_cwd = Dir.mktmpdir('elsewhere')
    begin
      stdout, stderr, status = Dir.chdir(other_cwd) do
        run_script('validate', "--plugin_path=#{File.expand_path(@plugin_dir)}")
      end

      assert status.success?, "Expected exit 0, got #{status.exitstatus}. stderr: #{stderr}"
      parsed = JSON.parse(stdout)
      assert_equal 'ok', parsed['status']
      assert parsed['valid']
    ensure
      FileUtils.rm_rf(other_cwd)
    end
  end
end
