require_relative "test_helper"
require "digest"

class MigratorTest < Minitest::Test
  include AdrTestHelpers

  FULL_SOURCE = <<~MD
    # 0001 — PocketBase backend

    ## Status

    Accepted

    ## Context

    Need a backend.

    ## Decision

    PocketBase.

    ## Consequences

    Single binary.

    ## Alternatives considered

    Rails — too heavy.
  MD

  PARTIAL_SOURCE = <<~MD
    # bin/lint-fix

    Status: Confirmed
    Decided: 2026-02-01

    ## Context

    Lint config drifted per repo.

    ## Decision

    One lint entry point.

    ## Why

    Consistency across repos.
  MD

  BARE_SOURCE = <<~MD
    # Something happened

    Status: Accepted

    We did a thing, narratively.
  MD

  def project(sources)
    Dir.mktmpdir do |root|
      decisions = File.join(root, "docs", "decisions")
      FileUtils.mkdir_p(decisions)
      sources.each { |name, body| File.write(File.join(decisions, name), body) }
      repo = Adr::Repository.new(root: root)
      migrator = Adr::Migrator.new(repository: repo, detector: Adr::Detector.new(root: root))
      yield root, repo, migrator
    end
  end

  def test_sources_stay_byte_identical
    project("0001-full.md" => FULL_SOURCE, "partial.md" => PARTIAL_SOURCE) do |root, _repo, migrator|
      before = Dir.glob(File.join(root, "docs", "decisions", "*"))
                  .to_h { |p| [p, Digest::SHA256.file(p).hexdigest] }
      migrator.migrate!
      before.each { |path, sha| assert_equal sha, Digest::SHA256.file(path).hexdigest }
    end
  end

  def test_unambiguous_fields_fill_mechanically_high_confidence
    project("0001-full.md" => FULL_SOURCE) do |_root, repo, migrator|
      manifest = migrator.migrate!
      entry = repo.entries.first
      record = entry.record
      assert_equal "Accepted", record.status
      assert_equal "PocketBase backend", record.title
      assert_equal "Need a backend.", record.sections["Context"]
      assert_equal "Rails — too heavy.", record.sections["Alternatives rejected"]
      assert_equal "high", record.frontmatter["migration_confidence"]
      assert_equal "docs/decisions/0001-full.md", record.frontmatter["migration_source"]
      assert_empty manifest["migrated"].first["llm_fields"]
    end
  end

  def test_interpretive_fields_get_pending_markers_and_manifest
    project("partial.md" => PARTIAL_SOURCE) do |_root, repo, migrator|
      manifest = migrator.migrate!
      record = repo.entries.first.record
      assert_equal "Accepted", record.status, "Confirmed maps mechanically to Accepted"
      assert_equal Date.new(2026, 2, 1), record.date
      assert_equal Adr::Migrator::PENDING_MARKER, record.sections["Consequences"]
      assert_equal Adr::Migrator::PENDING_MARKER, record.sections["Alternatives rejected"]
      assert_equal "medium", record.frontmatter["migration_confidence"]
      entry = manifest["migrated"].first
      assert_equal %w[consequences alternatives], entry["llm_fields"]
      assert_includes entry["source_material"], "Consistency across repos."
    end
  end

  def test_fallback_heading_mapping_caps_confidence_at_medium
    source = <<~MD
      # Topic

      Status: Accepted

      ## Decision

      Do the thing.

      ## Why

      Because reasons.

      ## Consequences

      Some.

      ## Alternatives rejected

      None seriously.
    MD
    project("topic.md" => source) do |_root, repo, migrator|
      migrator.migrate!
      record = repo.entries.first.record
      assert_equal "Because reasons.", record.sections["Context"], "Why maps to Context"
      assert_equal "medium", record.frontmatter["migration_confidence"],
                   "heuristic heading mapping is flagged"
    end
  end

  def test_missing_core_fields_get_not_stated_markers_and_low_confidence
    project("bare.md" => BARE_SOURCE) do |_root, repo, migrator|
      migrator.migrate!
      record = repo.entries.first.record
      assert_equal Adr::Migrator::NOT_STATED_MARKER, record.sections["Decision"]
      assert_equal Adr::Migrator::NOT_STATED_MARKER, record.sections["Context"]
      assert_equal "low", record.frontmatter["migration_confidence"]
    end
  end

  def test_apply_fills_only_touches_pending_fields
    project("partial.md" => PARTIAL_SOURCE, "bare.md" => BARE_SOURCE) do |root, repo, migrator|
      manifest = migrator.migrate!
      pending_file = manifest["migrated"].find { |m| m["source"].include?("partial") }["file"]
      bare_file = manifest["migrated"].find { |m| m["source"].include?("bare") }["file"]

      migrator.apply_fills!(
        pending_file => { "consequences" => "Fewer lint debates.",
                          "alternatives" => "Per-repo configs — rejected, drift." },
        bare_file => { "decision" => "INVENTED", "context" => "INVENTED" }
      )

      filled = Adr::Record.parse(File.read(File.join(root, pending_file)))
      assert_equal "Fewer lint debates.", filled.sections["Consequences"]
      assert_equal "medium", filled.frontmatter["migration_confidence"], "LLM-filled stays flagged"

      bare = Adr::Record.parse(File.read(File.join(root, bare_file)))
      assert_equal Adr::Migrator::NOT_STATED_MARKER, bare.sections["Decision"],
                   "core fields are never LLM-inventable"
      assert_equal "low", bare.frontmatter["migration_confidence"]
    end
  end

  def test_report_lists_files_confidence_sources_and_flag_rate
    project("0001-full.md" => FULL_SOURCE, "bare.md" => BARE_SOURCE) do |root, _repo, migrator|
      manifest = migrator.migrate!
      report = File.read(File.join(root, "docs", "adr", "migration-report.md"))
      assert_includes report, "docs/decisions/0001-full.md"
      assert_includes report, "| low |"
      assert_includes report, "| high |"
      assert_includes report, "flag rate: 50.0%"
      assert_equal 0.5, manifest["flag_rate"]
    end
  end

  def test_unrecognized_content_is_reported_never_converted
    project("notes.md" => "# Notes\n\nchatter\n") do |root, repo, migrator|
      manifest = migrator.migrate!
      assert_empty repo.entries
      assert_equal "docs/decisions/notes.md", manifest["unrecognized"].first["source"]
      assert_includes File.read(File.join(root, "docs", "adr", "migration-report.md")),
                      "docs/decisions/notes.md"
    end
  end

  def test_migration_is_idempotent_across_reruns
    project("0001-full.md" => FULL_SOURCE) do |_root, repo, migrator|
      migrator.migrate!
      migrator.migrate!
      assert_equal 1, repo.entries.size
    end
  end

  def test_index_is_regenerated_with_migrated_adrs
    project("0001-full.md" => FULL_SOURCE) do |_root, repo, migrator|
      migrator.migrate!
      assert_includes File.read(repo.index_path), "PocketBase backend"
    end
  end
end
