package regenmerge

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestMergeIntoFreshTreePreservesValueDrift exercises a templated
// config.go const literal change — TEMPLATED-VALUE-DRIFT must preserve
// the snapshot-side literal even when decl-sets match.
func TestMergeIntoFreshTreePreservesValueDrift(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	rel := "internal/config/config.go"
	templated := func(literal string) []byte {
		return []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
package config

const authPrefix = "` + literal + `"
`)
	}
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "config"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "config"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(snap, rel), templated("Token "), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), templated("Bearer "), 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	got, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.Contains(t, string(got), `"Token "`,
		"hand-edited literal must survive merge into fresh tree")
}

// TestMergeIntoFreshTreePreservesDeclSetAdditions covers hand-authored
// functions added to a templated client file — TEMPLATED-WITH-ADDITIONS
// must preserve the snapshot.
func TestMergeIntoFreshTreePreservesDeclSetAdditions(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	rel := "internal/client/queries.go"
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "client"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "client"), 0o755))

	pubBody := []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
package client

const placeholderQuery = ""

func GetTransactions() string { return "transactions" }
func GetCategories() string { return "categories" }
`)
	freshBody := []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
package client

const placeholderQuery = ""
`)
	require.NoError(t, os.WriteFile(filepath.Join(snap, rel), pubBody, 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), freshBody, 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	got, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.Contains(t, string(got), "GetTransactions", "added function survives")
	assert.Contains(t, string(got), "GetCategories", "added function survives")
}

func TestMergeIntoFreshTreePrunesFreshDeclCollidingWithPreservedFile(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))

	rootRel := "internal/cli/root.go"
	snapRoot := []byte(`// Generated by CLI Printing Press. DO NOT EDIT.
package cli

var version = "1.0.0"

func Execute() {}
func customRootHook() {}
func newVersionCliCmd() string { return version }
`)
	freshRoot := []byte(`// Generated by CLI Printing Press. DO NOT EDIT.
package cli

func Execute() {}
`)
	freshVersion := []byte(`package cli

var version = "1.0.0"

func newVersionCmd() string { return version }
`)
	require.NoError(t, os.WriteFile(filepath.Join(snap, rootRel), snapRoot, 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rootRel), freshRoot, 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "internal", "cli", "version.go"), freshVersion, 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	assert.NoFileExists(t, filepath.Join(fresh, "internal", "cli", "version.go"),
		"legacy root.go version layout must not gain a split-out version.go on reprint")

	gotRoot, err := os.ReadFile(filepath.Join(fresh, rootRel))
	require.NoError(t, err)
	assert.Contains(t, string(gotRoot), "customRootHook",
		"the hand-edited root file is still preserved")
	assert.Contains(t, string(gotRoot), `var version = "1.0.0"`,
		"the published runtime version value stays in root.go")
}

func TestPreserveMCPMainVersionValuesRemovesFreshVarWhenSnapshotHasNoMCPVersion(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(snap, "internal", "cli", "root.go"), []byte(`package cli

var version = "1.2.3"
`), 0o644))
	staleRel := "cmd/stale-pp-mcp/main.go"
	currentRel := "cmd/current-pp-mcp/main.go"
	for _, rel := range []string{staleRel, currentRel} {
		require.NoError(t, os.MkdirAll(filepath.Join(snap, filepath.Dir(rel)), 0o755))
		require.NoError(t, os.MkdirAll(filepath.Join(fresh, filepath.Dir(rel)), 0o755))
	}
	require.NoError(t, os.WriteFile(filepath.Join(snap, staleRel), []byte(`package main

func main() {}
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, staleRel), []byte(`package main

var version = "2.0.0"

func main() {
	_ = version
}
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(snap, currentRel), []byte(`package main

var version = "1.2.3"
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, currentRel), []byte(`package main

var version = "2.0.0"
`), 0o644))

	report := &MergeReport{Files: []FileClassification{
		{Path: staleRel},
		{Path: currentRel},
	}}
	require.NoError(t, preserveMCPMainVersionValues(snap, fresh, report))

	got, err := os.ReadFile(filepath.Join(fresh, currentRel))
	require.NoError(t, err)
	assert.Contains(t, string(got), `var version = "1.2.3"`,
		"a missing snapshot version in one MCP file must not skip later MCP files")
	got, err = os.ReadFile(filepath.Join(fresh, staleRel))
	require.NoError(t, err)
	assert.NotContains(t, string(got), `var version =`)
	assert.Contains(t, string(got), `_ = "1.2.3"`)
}

func TestPreserveMCPMainVersionValuesFallsBackToFreshLiteral(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	rel := "cmd/stale-pp-mcp/main.go"
	require.NoError(t, os.MkdirAll(filepath.Join(snap, filepath.Dir(rel)), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, filepath.Dir(rel)), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(snap, rel), []byte(`package main

func main() {}
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), []byte(`package main

var version = "2.0.0"

func main() {
	_ = version
}
`), 0o644))

	report := &MergeReport{Files: []FileClassification{{Path: rel}}}
	require.NoError(t, preserveMCPMainVersionValues(snap, fresh, report))

	got, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.NotContains(t, string(got), `var version =`)
	assert.Contains(t, string(got), `_ = "2.0.0"`)
}

func TestInlineStringVarReferencesAndRemoveSkipsLocalBindings(t *testing.T) {
	t.Parallel()

	dir := t.TempDir()
	path := filepath.Join(dir, "main.go")
	require.NoError(t, os.WriteFile(path, []byte(`package main

var version = "0.0.0-dev"

func mcpVersion() string { return "local" }

func localParameter(version string) string {
	return version
}

func localShortDecl() string {
	version := mcpVersion()
	return version
}

func main() {
	_ = version
}
`), 0o644))

	require.NoError(t, inlineStringVarReferencesAndRemove(path, "version", `"1.2.3"`))

	got, err := os.ReadFile(path)
	require.NoError(t, err)
	gotSrc := string(got)
	assert.NotContains(t, gotSrc, `var version =`)
	assert.Contains(t, gotSrc, `func localParameter(version string) string`)
	assert.Contains(t, gotSrc, `version := mcpVersion()`)
	assert.Contains(t, gotSrc, `_ = "1.2.3"`)
}

func TestMergeIntoFreshTreePrunesOnlyCollidingNameFromMultiNameValueSpec(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))

	require.NoError(t, os.WriteFile(filepath.Join(snap, "internal", "cli", "root.go"), []byte(`// Generated by CLI Printing Press. DO NOT EDIT.
package cli

var version = "1.0.0"
func customRootHook() {}
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "internal", "cli", "root.go"), []byte(`// Generated by CLI Printing Press. DO NOT EDIT.
package cli
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "internal", "cli", "version.go"), []byte(`package cli

var version, buildChannel = "1.0.0", "stable"
`), 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	gotVersion, err := os.ReadFile(filepath.Join(fresh, "internal", "cli", "version.go"))
	require.NoError(t, err)
	assert.NotContains(t, string(gotVersion), "version,")
	assert.NotContains(t, string(gotVersion), `"1.0.0"`)
	assert.Contains(t, string(gotVersion), "buildChannel")
	assert.Contains(t, string(gotVersion), `"stable"`)
}

func TestMergeIntoFreshTreeLeavesIotaConstBlockWhenCollisionWouldShiftValues(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))

	require.NoError(t, os.WriteFile(filepath.Join(snap, "internal", "cli", "root.go"), []byte(`// Generated by CLI Printing Press. DO NOT EDIT.
package cli

const Bar = 99
func customRootHook() {}
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "internal", "cli", "root.go"), []byte(`// Generated by CLI Printing Press. DO NOT EDIT.
package cli
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "internal", "cli", "constants.go"), []byte(`package cli

const (
	Foo = iota
	Bar
	Baz
)
`), 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	gotConstants, err := os.ReadFile(filepath.Join(fresh, "internal", "cli", "constants.go"))
	require.NoError(t, err)
	assert.Contains(t, string(gotConstants), "Foo = iota")
	assert.Contains(t, string(gotConstants), "Bar")
	assert.Contains(t, string(gotConstants), "Baz",
		"the whole const block must stay intact rather than shifting inherited iota values")
}

func TestMergeIntoFreshTreeLeavesImplicitConstBlockWhenCollisionWouldOrphanValue(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))

	require.NoError(t, os.WriteFile(filepath.Join(snap, "internal", "cli", "root.go"), []byte(`// Generated by CLI Printing Press. DO NOT EDIT.
package cli

const Version = "1.0.0"
func customRootHook() {}
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "internal", "cli", "root.go"), []byte(`// Generated by CLI Printing Press. DO NOT EDIT.
package cli
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "internal", "cli", "constants.go"), []byte(`package cli

const (
	Version = "1.0.0"
	DefaultVersion
)
`), 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	gotConstants, err := os.ReadFile(filepath.Join(fresh, "internal", "cli", "constants.go"))
	require.NoError(t, err)
	assert.Contains(t, string(gotConstants), `Version = "1.0.0"`)
	assert.Contains(t, string(gotConstants), "DefaultVersion",
		"implicit const repetition must not be orphaned by pruning its explicit predecessor")
}

func TestMergeIntoFreshTreePrunesUnusedImportAfterFreshDeclCollision(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))

	require.NoError(t, os.WriteFile(filepath.Join(snap, "internal", "cli", "root.go"), []byte(`// Generated by CLI Printing Press. DO NOT EDIT.
package cli

var version = "1.0.0"
func customRootHook() {}
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "internal", "cli", "root.go"), []byte(`// Generated by CLI Printing Press. DO NOT EDIT.
package cli
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "internal", "cli", "version.go"), []byte(`package cli

import (
	"fmt"
	"strings"
)

var version = fmt.Sprint("1.0.0")

func buildChannel() string {
	return strings.ToUpper("stable")
}
`), 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	gotVersion, err := os.ReadFile(filepath.Join(fresh, "internal", "cli", "version.go"))
	require.NoError(t, err)
	assert.NotContains(t, string(gotVersion), `"fmt"`,
		"imports used only by pruned declarations should be removed")
	assert.Contains(t, string(gotVersion), `"strings"`,
		"imports still used by surviving declarations should remain")
	assert.Contains(t, string(gotVersion), "strings.ToUpper")
}

func TestMergeIntoFreshTreePreservesStoreExtrasEdits(t *testing.T) {
	t.Parallel()

	snap, fresh, rel := writeStoreExtrasFixture(t)
	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	assertStoreExtrasMigrationPreserved(t, fresh, rel)
}

func TestMergeIntoFreshTreeNovelOnlyPreservesStoreExtrasEdits(t *testing.T) {
	t.Parallel()

	snap, fresh, rel := writeStoreExtrasFixture(t)
	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true, NovelOnly: true}))

	assertStoreExtrasMigrationPreserved(t, fresh, rel)
}

func writeStoreExtrasFixture(t *testing.T) (string, string, string) {
	t.Helper()

	snap, fresh := makeMergeFixture(t)
	rel := "internal/store/extras.go"
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "store"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "store"), 0o755))

	freshBody := []byte(`package store

import (
	"context"
	"database/sql"
	"fmt"
)

func (s *Store) migrateExtras(ctx context.Context, conn *sql.Conn) error {
	migrations := []string{
		// Add CREATE TABLE IF NOT EXISTS statements here.
	}
	for _, m := range migrations {
		if _, err := conn.ExecContext(ctx, m); err != nil {
			return fmt.Errorf("extra migration failed: %w", err)
		}
	}
	return nil
}
`)
	snapBody := []byte(`package store

import (
	"context"
	"database/sql"
	"fmt"
)

func (s *Store) migrateExtras(ctx context.Context, conn *sql.Conn) error {
	migrations := []string{
		` + "`" + `CREATE TABLE IF NOT EXISTS novel_events (id TEXT PRIMARY KEY)` + "`" + `,
	}
	for _, m := range migrations {
		if _, err := conn.ExecContext(ctx, m); err != nil {
			return fmt.Errorf("extra migration failed: %w", err)
		}
	}
	return nil
}
`)
	require.NoError(t, os.WriteFile(filepath.Join(snap, rel), snapBody, 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), freshBody, 0o644))

	return snap, fresh, rel
}

func assertStoreExtrasMigrationPreserved(t *testing.T, fresh, rel string) {
	t.Helper()
	got, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.Contains(t, string(got), "novel_events",
		"agent-authored store extras migrations must survive regen merge")
}

// TestMergeIntoFreshTreeReinjectsLostAddCommand covers a hand-added
// cmd.AddCommand line in a parent-command file — LostRegistrations must
// re-inject the call into the freshly emitted host.
func TestMergeIntoFreshTreeReinjectsLostAddCommand(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))

	rel := "internal/cli/transactions.go"
	pubBody := []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
package cli

func newTransactionsCmd(flags *rootFlags) *cobra.Command {
	cmd := &cobra.Command{}
	cmd.AddCommand(newTransactionsListCmd(flags))
	cmd.AddCommand(newHandAddedCmd(flags))
	return cmd
}
`)
	freshBody := []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
package cli

func newTransactionsCmd(flags *rootFlags) *cobra.Command {
	cmd := &cobra.Command{}
	cmd.AddCommand(newTransactionsListCmd(flags))
	return cmd
}
`)
	require.NoError(t, os.WriteFile(filepath.Join(snap, rel), pubBody, 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), freshBody, 0o644))
	// Pub also has a hand-written constructor file so the referent-existence
	// check passes.
	novelRel := "internal/cli/hand_added.go"
	novel := []byte(`package cli

func newHandAddedCmd(flags *rootFlags) *cobra.Command { return nil }
`)
	require.NoError(t, os.WriteFile(filepath.Join(snap, novelRel), novel, 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	got, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.Contains(t, string(got), "newHandAddedCmd",
		"hand-added AddCommand must be re-injected into fresh's transactions.go")
	assert.Contains(t, string(got), "newTransactionsListCmd",
		"existing template-emitted AddCommand survives")

	// Novel constructor file preserved.
	novelGot, err := os.ReadFile(filepath.Join(fresh, novelRel))
	require.NoError(t, err)
	assert.Contains(t, string(novelGot), "newHandAddedCmd")
}

// TestMergeIntoFreshTreeMergesGoModRequires exercises R9: hand-added
// require for novel-package deps survives.
func TestMergeIntoFreshTreeMergesGoModRequires(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	pubGoMod := []byte(`module example.com/snap

go 1.22

require (
	github.com/spf13/cobra v1.8.0
	modernc.org/sqlite v1.30.0
)
`)
	freshGoMod := []byte(`module example.com/snap

go 1.22

require (
	github.com/spf13/cobra v1.8.0
)
`)
	require.NoError(t, os.WriteFile(filepath.Join(snap, "go.mod"), pubGoMod, 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "go.mod"), freshGoMod, 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	got, err := os.ReadFile(filepath.Join(fresh, "go.mod"))
	require.NoError(t, err)
	assert.Contains(t, string(got), "modernc.org/sqlite",
		"hand-added require must survive merge into fresh's go.mod")
}

func TestMergeIntoFreshTreeRewritesImportsAfterWritingMergedGoMod(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	pubGoMod := []byte(`module github.com/acme/library/demo-pp-cli

go 1.22

require (
	github.com/spf13/cobra v1.8.0
	modernc.org/sqlite v1.30.0
)
`)
	freshGoMod := []byte(`module demo-pp-cli

go 1.22

require github.com/spf13/cobra v1.8.0
`)
	const rel = "internal/cli/root.go"
	freshRoot := []byte(`package cli

import "demo-pp-cli/internal/client"

var _ = client.New
`)
	require.NoError(t, os.WriteFile(filepath.Join(snap, "go.mod"), pubGoMod, 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, "go.mod"), freshGoMod, 0o644))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), freshRoot, 0o644))

	report := &MergeReport{GoMod: &GoModMerge{}}
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	gotGoMod, err := os.ReadFile(filepath.Join(fresh, "go.mod"))
	require.NoError(t, err)
	assert.Contains(t, string(gotGoMod), "module github.com/acme/library/demo-pp-cli")
	assert.Contains(t, string(gotGoMod), "modernc.org/sqlite")

	gotRoot, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.Contains(t, string(gotRoot), `"github.com/acme/library/demo-pp-cli/internal/client"`)
	assert.NotContains(t, string(gotRoot), `"demo-pp-cli/internal/client"`)
	assert.True(t, report.GoMod.Merged)
}

// TestMergeIntoFreshTreeSweepsNonClassifiedFiles exercises R8: README and
// other non-Go files survive merge.
func TestMergeIntoFreshTreeSweepsNonClassifiedFiles(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	require.NoError(t, os.WriteFile(filepath.Join(snap, "README.md"), []byte("hand edited\n"), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(snap, "Makefile"), []byte("# custom\n"), 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	got, err := os.ReadFile(filepath.Join(fresh, "README.md"))
	require.NoError(t, err)
	assert.Equal(t, "hand edited\n", string(got))

	got, err = os.ReadFile(filepath.Join(fresh, "Makefile"))
	require.NoError(t, err)
	assert.Equal(t, "# custom\n", string(got))
}

func TestMergeIntoFreshTreePreservesManuscripts(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	rel := filepath.Join(".manuscripts", "20260523-171100", "research.json")
	path := filepath.Join(snap, rel)
	require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
	require.NoError(t, os.WriteFile(path, []byte(`{"status":"accepted"}`), 0o640))
	wantModTime := time.Date(2026, 5, 23, 17, 11, 0, 0, time.UTC)
	require.NoError(t, os.Chtimes(path, wantModTime, wantModTime))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true}))

	gotPath := filepath.Join(fresh, rel)
	got, err := os.ReadFile(gotPath)
	require.NoError(t, err)
	assert.JSONEq(t, `{"status":"accepted"}`, string(got))

	info, err := os.Stat(gotPath)
	require.NoError(t, err)
	assert.Equal(t, os.FileMode(0o640), info.Mode().Perm())
	assert.True(t, info.ModTime().Equal(wantModTime), "mtime should survive force-regen sweep")
}

// TestMergeIntoFreshTreeNovelOnlySkipsValueDrift verifies that the
// cross-spec fallback path skips heuristic preservation.
func TestMergeIntoFreshTreeNovelOnlySkipsValueDrift(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	rel := "internal/config/config.go"
	templated := func(literal string) []byte {
		return []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
package config

const authPrefix = "` + literal + `"
`)
	}
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "config"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "config"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(snap, rel), templated("Token "), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), templated("Bearer "), 0o644))

	// Novel hand-written file should still survive in NovelOnly mode.
	novelRel := "internal/cli/handcrafted.go"
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(snap, novelRel), []byte("package cli\n\nfunc Handcrafted() {}\n"), 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true, NovelOnly: true}))

	got, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.Contains(t, string(got), `"Bearer "`,
		"NovelOnly mode keeps fresh's literal value (skips TEMPLATED-VALUE-DRIFT preservation)")

	novelGot, err := os.ReadFile(filepath.Join(fresh, novelRel))
	require.NoError(t, err)
	assert.Contains(t, string(novelGot), "Handcrafted",
		"NovelOnly preserves novel hand-written files")
}

func TestMergeIntoFreshTreeNovelOnlyPreservesHandAuthoredNovelCommandTest(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	rel := "internal/cli/call_test.go"
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(snap, rel), []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
// Novel command scaffold tests. Keep the wiring smoke test and add behavior cases as needed.

package cli

import "testing"

func TestNovelCallHelpWires(t *testing.T) {}

func TestNovelCallBehavior(t *testing.T) {
	if got := "real case"; got == "" {
		t.Fatal("missing assertion")
	}
}
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// cli-printing-press: novel-scaffold-test
// Novel command scaffold tests. Keep the wiring smoke test and add behavior cases as needed.

package cli

import "testing"

func TestNovelCallHelpWires(t *testing.T) {}
`), 0o644))

	// This covers the old-to-new transition: the snapshot is an old DO NOT EDIT
	// scaffold with operator-authored tests, while the fresh file is the new
	// marker-based editable scaffold. Once both sides are markerless novel files,
	// the classifier returns VerdictNovel and preserves without this helper.
	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	assert.Equal(t, VerdictTemplatedWithAdditions, verdictMap(report)[rel])
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true, NovelOnly: true}))

	got, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.Contains(t, string(got), "TestNovelCallBehavior",
		"NovelOnly force-regen must preserve hand-authored tests replacing a markerless novel command scaffold")
}

func TestMergeIntoFreshTreeNovelOnlyPreservesHandAuthoredNovelCommandBody(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	rel := "internal/cli/call.go"
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(snap, rel), []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Novel command scaffold. Implement the RunE body; generate --force preserves this file.

package cli

import (
	"fmt"

	"github.com/spf13/cobra"
)

func newNovelCallCmd(flags *rootFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "call",
		Short: "Call an actor.",
		Annotations: map[string]string{"mcp:read-only": "true"},
		RunE: func(cmd *cobra.Command, args []string) error {
			fmt.Fprintln(cmd.OutOrStdout(), "implemented")
			return nil
		},
	}
	return cmd
}
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Novel command scaffold. Implement the RunE body; generate --force preserves this file.

package cli

import (
	"fmt"

	"github.com/spf13/cobra"
)

func newNovelCallCmd(flags *rootFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "call",
		Short: "Call an actor.",
		Annotations: map[string]string{"mcp:read-only": "true"},
		RunE: func(cmd *cobra.Command, args []string) error {
			if dryRunOK(flags) {
				return nil
			}
			return fmt.Errorf("TODO: implement novel feature %q", "call")
		},
	}
	return cmd
}
`), 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true, NovelOnly: true}))

	got, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.Contains(t, string(got), `"implemented"`,
		"NovelOnly force-regen must preserve a hand-authored novel command body")
	assert.NotContains(t, string(got), "TODO: implement novel feature",
		"NovelOnly force-regen must not overwrite an implementation with the scaffold")
}

func TestMergeIntoFreshTreeNovelOnlyRefreshesUntouchedNovelCommandScaffold(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	rel := "internal/cli/call.go"
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(snap, rel), []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Novel command scaffold. Implement the RunE body; generate --force preserves this file.

package cli

import (
	"fmt"

	"github.com/spf13/cobra"
)

func newNovelCallCmd(flags *rootFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "call",
		Short: "Old scaffold text.",
		RunE: func(cmd *cobra.Command, args []string) error {
			return fmt.Errorf("TODO: implement novel feature %q", "call")
		},
	}
	return cmd
}
`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Novel command scaffold. Implement the RunE body; generate --force preserves this file.

package cli

import (
	"fmt"

	"github.com/spf13/cobra"
)

func newNovelCallCmd(flags *rootFlags) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "call",
		Short: "Fresh scaffold text.",
		RunE: func(cmd *cobra.Command, args []string) error {
			return fmt.Errorf("TODO: implement novel feature %q", "call")
		},
	}
	return cmd
}
`), 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true, NovelOnly: true}))

	got, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.Contains(t, string(got), "Fresh scaffold text.",
		"NovelOnly force-regen should keep fresh output for untouched scaffolds")
	assert.NotContains(t, string(got), "Old scaffold text.")
}

func TestMergeIntoFreshTreeNovelOnlyDoesNotPreserveOrdinaryGeneratedTestDrift(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	rel := "internal/cli/root_test.go"
	templated := func(body string) []byte {
		return []byte(`// Copyright 2026 owner. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
package cli

import "testing"

func TestRoot(t *testing.T) {
` + body + `
}
`)
	}
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(fresh, "internal", "cli"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(snap, rel), templated(`	t.Fatal("operator edit")`), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(fresh, rel), templated(`	t.Log("fresh generated test")`), 0o644))

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	assert.Equal(t, VerdictTemplatedBodyDrift, verdictMap(report)[rel])
	require.NoError(t, MergeIntoFreshTree(snap, fresh, report, Options{Force: true, NovelOnly: true}))

	got, err := os.ReadFile(filepath.Join(fresh, rel))
	require.NoError(t, err)
	assert.Contains(t, string(got), "fresh generated test")
	assert.NotContains(t, string(got), "operator edit",
		"NovelOnly must not loosen DO NOT EDIT ownership for ordinary generated tests")
}

// TestMergeIntoFreshTreeRefusesSnapshotSymlinks verifies symlink safety in
// the preserve path.
func TestMergeIntoFreshTreeRefusesSnapshotSymlinks(t *testing.T) {
	t.Parallel()

	snap, fresh := makeMergeFixture(t)
	rel := "internal/cli/sneaky.go"
	require.NoError(t, os.MkdirAll(filepath.Join(snap, "internal", "cli"), 0o755))
	target := filepath.Join(snap, "external.go")
	require.NoError(t, os.WriteFile(target, []byte("package cli\n"), 0o644))
	if err := os.Symlink(target, filepath.Join(snap, rel)); err != nil {
		t.Skipf("symlink unavailable: %v", err)
	}

	report, err := Classify(snap, fresh, Options{Force: true})
	require.NoError(t, err)
	err = MergeIntoFreshTree(snap, fresh, report, Options{Force: true})
	require.Error(t, err)
	assert.Contains(t, strings.ToLower(err.Error()), "symlink")
}

func makeMergeFixture(t *testing.T) (snap, fresh string) {
	t.Helper()
	dir := t.TempDir()
	snap = filepath.Join(dir, "snap")
	fresh = filepath.Join(dir, "fresh")
	require.NoError(t, os.MkdirAll(snap, 0o755))
	require.NoError(t, os.MkdirAll(fresh, 0o755))
	return snap, fresh
}
