package nfpm

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

	"github.com/goreleaser/goreleaser/v2/internal/artifact"
	"github.com/goreleaser/goreleaser/v2/internal/pipe"
	"github.com/goreleaser/goreleaser/v2/internal/skips"
	"github.com/goreleaser/goreleaser/v2/internal/testctx"
	"github.com/goreleaser/goreleaser/v2/internal/testlib"
	"github.com/goreleaser/goreleaser/v2/pkg/config"
	"github.com/goreleaser/goreleaser/v2/pkg/context"
	"github.com/goreleaser/nfpm/v2"
	"github.com/goreleaser/nfpm/v2/files"
	"github.com/stretchr/testify/require"
)

func TestDescription(t *testing.T) {
	require.NotEmpty(t, Pipe{}.String())
}

func TestRunPipeNoFormats(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(),
		config.Project{
			NFPMs: []config.NFPM{
				{},
			},
		},
		testctx.WithCurrentTag("v1.0.1"),
		testctx.WithVersion("1.0.1"))

	require.NoError(t, Pipe{}.Default(ctx))
	testlib.AssertSkipped(t, Pipe{}.Run(ctx))
}

func TestRunPipeError(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		Dist: t.TempDir(),
		NFPMs: []config.NFPM{
			{
				Formats: []string{"deb"},
				NFPMOverridables: config.NFPMOverridables{
					FileNameTemplate: "{{.ConventionalFileName}}",
				},
			},
		},
	})

	ctx.Artifacts.Add(&artifact.Artifact{
		Name:   "mybin",
		Path:   "testdata/testfile.txt",
		Goarch: "amd64",
		Goos:   "linux",
		Type:   artifact.Binary,
		Extra: map[string]any{
			artifact.ExtraID: "foo",
		},
	})
	require.NoError(t, Pipe{}.Default(ctx))
	require.EqualError(t, Pipe{}.Run(ctx), "nfpm failed for _0.0.0~rc0_amd64.deb: package name must be provided")
}

func TestRunPipeInvalidFormat(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "nope",
		NFPMs: []config.NFPM{
			{
				Bindir:  "/usr/bin",
				Formats: []string{"nope"},
				Builds:  []string{"foo"},
				NFPMOverridables: config.NFPMOverridables{
					PackageName:      "foo",
					FileNameTemplate: defaultNameTemplate,
				},
			},
		},
	}, testctx.WithVersion("1.2.3"), testctx.WithCurrentTag("v1.2.3"))

	for _, goos := range []string{"linux", "darwin"} {
		for _, goarch := range []string{"amd64", "386"} {
			ctx.Artifacts.Add(&artifact.Artifact{
				Name:   "mybin",
				Path:   "testdata/testfile.txt",
				Goarch: goarch,
				Goos:   goos,
				Type:   artifact.Binary,
				Extra: map[string]any{
					artifact.ExtraID: "foo",
				},
			})
		}
	}
	require.Contains(t, Pipe{}.Run(ctx).Error(), `no packager registered for the format nope`)
}

func TestRunPipe(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	binPath := filepath.ToSlash(filepath.Join(dist, "mybin", "mybin"))
	foohPath := filepath.ToSlash(filepath.Join(dist, "foo.h"))
	foosoPath := filepath.ToSlash(filepath.Join(dist, "foo.so"))
	fooaPath := filepath.ToSlash(filepath.Join(dist, "foo.a"))
	for _, name := range []string{binPath, foosoPath, foohPath, fooaPath} {
		f, err := os.Create(name)
		require.NoError(t, err)
		require.NoError(t, f.Close())
	}
	libPrefix := `/usr/lib
      {{- if eq .Arch "amd64" }}{{if eq .Format "rpm"}}_rpm{{end}}64{{- end -}}
	`
	now := time.Now().Truncate(time.Second).UTC()
	fileInfo := config.FileInfo{
		MTime: "{{.CommitDate}}",
	}
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		Env: []string{
			"PRO=pro",
			"DESC=templates",
			"EXT=.sh",
		},
		NFPMs: []config.NFPM{
			{
				ID:          "someid",
				Bindir:      "/usr/bin",
				Builds:      []string{"default", "lib1", "lib2", "lib3"},
				Formats:     []string{"deb", "rpm", "apk", "termux.deb", "archlinux", "ipk"},
				Section:     "somesection",
				Priority:    "standard",
				Description: "Some description with {{ .Env.DESC }}",
				License:     "MIT",
				Maintainer:  "me@me",
				Vendor:      "asdf",
				Homepage:    "https://goreleaser.com/{{ .Env.PRO }}",
				Changelog:   "./testdata/changelog.yaml",
				MTime:       "{{.CommitDate}}",
				GoAmd64:     []string{"v1", "v2", "v3", "v4"},
				Libdirs: config.Libdirs{
					Header:   libPrefix + "/headers",
					CArchive: libPrefix + "/c-archives",
					CShared:  libPrefix + "/c-shareds",
				},
				NFPMOverridables: config.NFPMOverridables{
					FileNameTemplate: defaultNameTemplate + "-{{ .Release }}-{{ .Epoch }}",
					PackageName:      "foo",
					Dependencies:     []string{"make"},
					Recommends:       []string{"svn"},
					Suggests:         []string{"bzr"},
					Replaces:         []string{"fish"},
					Conflicts:        []string{"git"},
					Provides:         []string{"ash"},
					Release:          "10",
					Epoch:            "20",
					Scripts: config.NFPMScripts{
						PreInstall:  "./testdata/pre_install{{.Env.EXT}}",
						PostInstall: "./testdata/post_install{{.Env.EXT}}",
						PreRemove:   "./testdata/pre_remove{{.Env.EXT}}",
						PostRemove:  "./testdata/post_remove{{.Env.EXT}}",
					},
					Contents: []config.NFPMContent{
						{
							Destination: "/var/log/foobar",
							Type:        "dir",
							FileInfo:    fileInfo,
						},
						{
							Source:      "./testdata/testfile.txt",
							Destination: "/usr/share/testfile.txt",
							FileInfo:    fileInfo,
						},
						{
							Source:      "./testdata/testfile.txt",
							Destination: "/etc/nope.conf",
							Type:        "config",
							FileInfo:    fileInfo,
						},
						{
							Destination: "/etc/mydir",
							Type:        "dir",
							FileInfo:    fileInfo,
						},
						{
							Source:      "./testdata/testfile.txt",
							Destination: "/etc/nope-rpm.conf",
							FileInfo:    fileInfo,
							Type:        "config",
							Packager:    "rpm",
						},
						{
							Source:      "/etc/nope.conf",
							Destination: "/etc/nope2.conf",
							FileInfo:    fileInfo,
							Type:        "symlink",
						},
						{
							Source:      "./testdata/testfile-{{ .Arch }}{{.Amd64}}{{.Arm}}{{.Mips}}.txt",
							Destination: "/etc/nope3_{{ .ProjectName }}.conf",
							FileInfo:    fileInfo,
						},
						{
							Source:      "./testdata/folder",
							Destination: "/etc/folder",
							FileInfo:    fileInfo,
						},
					},
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"), func(ctx *context.Context) {
		ctx.Git.CommitDate = now
	})

	for _, goos := range []string{"linux", "darwin", "ios", "android", "aix"} {
		for _, goarch := range []string{"amd64", "386", "arm64", "arm", "mips", "ppc64"} {
			if goos == "ios" && goarch != "arm64" {
				continue
			}
			if goarch == "ppc64" && goos != "aix" {
				continue
			}
			switch goarch {
			case "arm":
				for _, goarm := range []string{"6", "7"} {
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:   "subdir/mybin",
						Path:   binPath,
						Goarch: goarch,
						Goos:   goos,
						Goarm:  goarm,
						Type:   artifact.Binary,
						Extra: map[string]any{
							artifact.ExtraID: "default",
						},
					})
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:   "foo.h",
						Path:   foohPath,
						Goarch: goarch,
						Goos:   goos,
						Goarm:  goarm,
						Type:   artifact.Header,
						Extra: map[string]any{
							artifact.ExtraID: "lib1",
						},
					})
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:   "foo.so",
						Path:   foosoPath,
						Goarch: goarch,
						Goos:   goos,
						Goarm:  goarm,
						Type:   artifact.CShared,
						Extra: map[string]any{
							artifact.ExtraID: "lib2",
						},
					})
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:   "foo.a",
						Path:   fooaPath,
						Goarch: goarch,
						Goos:   goos,
						Goarm:  goarm,
						Type:   artifact.CArchive,
						Extra: map[string]any{
							artifact.ExtraID: "lib3",
						},
					})
				}
			case "amd64":
				// v5 is invalid, filtered out in tests
				for _, goamd64 := range []string{"v1", "v2", "v3", "v4", "v5"} {
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:    "subdir/mybin",
						Path:    binPath,
						Goarch:  goarch,
						Goos:    goos,
						Goamd64: goamd64,
						Type:    artifact.Binary,
						Extra: map[string]any{
							artifact.ExtraID: "default",
						},
					})
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:    "foo.h",
						Path:    foohPath,
						Goarch:  goarch,
						Goos:    goos,
						Goamd64: goamd64,
						Type:    artifact.Header,
						Extra: map[string]any{
							artifact.ExtraID: "lib1",
						},
					})
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:    "foo.so",
						Path:    foosoPath,
						Goarch:  goarch,
						Goos:    goos,
						Goamd64: goamd64,
						Type:    artifact.CShared,
						Extra: map[string]any{
							artifact.ExtraID: "lib2",
						},
					})
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:    "foo.a",
						Path:    fooaPath,
						Goarch:  goarch,
						Goos:    goos,
						Goamd64: goamd64,
						Type:    artifact.CArchive,
						Extra: map[string]any{
							artifact.ExtraID: "lib3",
						},
					})
				}
			case "mips":
				for _, gomips := range []string{"softfloat", "hardfloat"} {
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:   "subdir/mybin",
						Path:   binPath,
						Goarch: goarch,
						Goos:   goos,
						Gomips: gomips,
						Type:   artifact.Binary,
						Extra: map[string]any{
							artifact.ExtraID: "default",
						},
					})
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:   "foo.h",
						Path:   foohPath,
						Goarch: goarch,
						Goos:   goos,
						Gomips: gomips,
						Type:   artifact.Header,
						Extra: map[string]any{
							artifact.ExtraID: "lib1",
						},
					})
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:   "foo.so",
						Path:   foosoPath,
						Goarch: goarch,
						Goos:   goos,
						Gomips: gomips,
						Type:   artifact.CShared,
						Extra: map[string]any{
							artifact.ExtraID: "lib2",
						},
					})
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:   "foo.a",
						Path:   fooaPath,
						Goarch: goarch,
						Goos:   goos,
						Gomips: gomips,
						Type:   artifact.CArchive,
						Extra: map[string]any{
							artifact.ExtraID: "lib3",
						},
					})
				}
			default:
				ctx.Artifacts.Add(&artifact.Artifact{
					Name:   "subdir/mybin",
					Path:   binPath,
					Goarch: goarch,
					Goos:   goos,
					Type:   artifact.Binary,
					Extra: map[string]any{
						artifact.ExtraID: "default",
					},
				})
				ctx.Artifacts.Add(&artifact.Artifact{
					Name:   "foo.h",
					Path:   foohPath,
					Goarch: goarch,
					Goos:   goos,
					Type:   artifact.Header,
					Extra: map[string]any{
						artifact.ExtraID: "lib1",
					},
				})
				ctx.Artifacts.Add(&artifact.Artifact{
					Name:   "foo.so",
					Path:   foosoPath,
					Goarch: goarch,
					Goos:   goos,
					Type:   artifact.CShared,
					Extra: map[string]any{
						artifact.ExtraID: "lib2",
					},
				})
				ctx.Artifacts.Add(&artifact.Artifact{
					Name:   "foo.a",
					Path:   fooaPath,
					Goarch: goarch,
					Goos:   goos,
					Type:   artifact.CArchive,
					Extra: map[string]any{
						artifact.ExtraID: "lib3",
					},
				})
			}
		}
	}
	require.NoError(t, Pipe{}.Run(ctx))
	packages := ctx.Artifacts.Filter(artifact.ByType(artifact.LinuxPackage)).List()
	require.Len(t, packages, 57)

	for _, pkg := range packages {
		format := pkg.Format()
		require.NotEmpty(t, format)
		require.Equal(t, "."+pkg.Format(), pkg.Ext())
		arch := pkg.Goarch
		if pkg.Goarm != "" {
			arch += "v" + pkg.Goarm
		}
		if pkg.Goamd64 != "v1" {
			arch += pkg.Goamd64
		}
		if pkg.Gomips != "" {
			arch += "_" + pkg.Gomips
		}

		ext := "." + format
		if format != termuxFormat {
			packager, err := nfpm.Get(format)
			require.NoError(t, err)

			if packager, ok := packager.(nfpm.PackagerWithExtension); ok {
				ext = packager.ConventionalExtension()
			}
		}

		switch pkg.Goos {
		case "linux":
			require.Equal(t, "foo_1.0.0_linux_"+arch+"-10-20"+ext, pkg.Name)
		case "android":
			require.Equal(t, "foo_1.0.0_android_"+arch+"-10-20"+ext, pkg.Name)
		case "aix":
			require.Equal(t, "foo_1.0.0_aix_ppc64-10-20"+ext, pkg.Name)
		default:
			require.Equal(t, "foo_1.0.0_ios_arm64-10-20"+ext, pkg.Name)
		}
		require.Equal(t, "someid", pkg.ID())

		stat, err := os.Stat(pkg.Path)
		require.NoError(t, err)
		require.Equal(t, now.UTC(), stat.ModTime().UTC())

		contents := artifact.MustExtra[files.Contents](*pkg, extraFiles)
		for _, src := range contents {
			require.NotNil(t, src.FileInfo, src.Destination)
			require.Equal(t, now.UTC(), src.FileInfo.MTime.UTC(), src.Destination)
		}

		require.ElementsMatch(t, []string{
			"./testdata/testfile.txt",
			"./testdata/testfile.txt",
			"./testdata/testfile.txt",
			"/etc/nope.conf",
			"./testdata/folder",
			"./testdata/testfile-" + pkg.Goarch + pkg.Goamd64 + pkg.Goarm + pkg.Gomips + ".txt",
			binPath,
			foohPath,
			fooaPath,
			foosoPath,
		}, sources(contents))

		bin := "/usr/bin/subdir/"
		header := "/usr/lib/headers"
		carchive := "/usr/lib/c-archives"
		cshared := "/usr/lib/c-shareds"
		if pkg.Goarch == "amd64" {
			if pkg.Format() == "rpm" {
				header = "/usr/lib_rpm64/headers"
				carchive = "/usr/lib_rpm64/c-archives"
				cshared = "/usr/lib_rpm64/c-shareds"
			} else {
				header = "/usr/lib64/headers"
				carchive = "/usr/lib64/c-archives"
				cshared = "/usr/lib64/c-shareds"
			}
		}
		if format == termuxFormat {
			bin = filepath.Join("/data/data/com.termux/files", bin)
			header = filepath.Join("/data/data/com.termux/files", header)
			cshared = filepath.Join("/data/data/com.termux/files", cshared)
			carchive = filepath.Join("/data/data/com.termux/files", carchive)
		}
		bin = filepath.ToSlash(filepath.Join(bin, "mybin"))
		header = filepath.ToSlash(filepath.Join(header, "foo.h"))
		cshared = filepath.ToSlash(filepath.Join(cshared, "foo.so"))
		carchive = filepath.ToSlash(filepath.Join(carchive, "foo.a"))
		require.ElementsMatch(t, []string{
			"/var/log/foobar",
			"/usr/share/testfile.txt",
			"/etc/mydir",
			"/etc/nope.conf",
			"/etc/nope-rpm.conf",
			"/etc/nope2.conf",
			"/etc/nope3_mybin.conf",
			"/etc/folder",
			bin,
			header,
			carchive,
			cshared,
		}, destinations(artifact.MustExtra[files.Contents](*pkg, extraFiles)))
	}
	require.Len(t, ctx.Config.NFPMs[0].Contents, 8, "should not modify the config file list")
}

func TestSkipOne(t *testing.T) {
	t.Helper()
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	binPath := filepath.ToSlash(filepath.Join(dist, "mybin"))
	require.NoError(t, os.WriteFile(binPath, nil, 0o755))
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		NFPMs: []config.NFPM{
			{
				ID: "this configuration will be ignored as it has no formats",
			},
			{
				Formats: []string{"deb", "rpm"},
			},
		},
	}, testctx.WithVersion("1.0.0"))

	for _, goos := range []string{"linux", "darwin"} {
		for _, goarch := range []string{"amd64", "arm64"} {
			ctx.Artifacts.Add(&artifact.Artifact{
				Name:   "subdir/mybin",
				Path:   binPath,
				Goarch: goarch,
				Goos:   goos,
				Type:   artifact.Binary,
			})
		}
	}
	require.NoError(t, Pipe{}.Default(ctx))
	err := Pipe{}.Run(ctx)
	require.True(t, pipe.IsSkip(err), err)

	packages := ctx.Artifacts.Filter(artifact.ByType(artifact.LinuxPackage)).List()
	require.Len(t, packages, 4)
	for _, pkg := range packages {
		require.NotEmpty(t, pkg.Format())
		require.Contains(t, []string{
			"mybin_1.0.0_linux_arm64.deb",
			"mybin_1.0.0_linux_amd64.deb",
			"mybin_1.0.0_linux_amd64.rpm",
			"mybin_1.0.0_linux_arm64.rpm",
		}, pkg.Name, "package name is not expected")
	}
}

func TestRunPipeConventionalNameTemplate(t *testing.T) {
	t.Run("regular", func(t *testing.T) { doTestRunPipeConventionalNameTemplate(t, false) })
	t.Run("snapshot", func(t *testing.T) { doTestRunPipeConventionalNameTemplate(t, true) })
}

func doTestRunPipeConventionalNameTemplate(t *testing.T, snapshot bool) {
	t.Helper()
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	binPath := filepath.ToSlash(filepath.Join(dist, "mybin", "mybin"))
	require.NoError(t, os.WriteFile(binPath, []byte("nope"), 0o755))
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		NFPMs: []config.NFPM{
			{
				ID:          "someid",
				Builds:      []string{"default"},
				Formats:     []string{"deb", "rpm", "apk", "archlinux", "ipk"},
				Section:     "somesection",
				Priority:    "standard",
				Description: "Some description ",
				License:     "MIT",
				Maintainer:  "me@me",
				Vendor:      "asdf",
				Homepage:    "https://goreleaser.com/",
				Bindir:      "/usr/bin",
				NFPMOverridables: config.NFPMOverridables{
					FileNameTemplate: `
						{{- trimsuffix .ConventionalFileName .ConventionalExtension -}}
						{{- if and (eq .Arm "6") (eq .ConventionalExtension ".deb") }}6{{ end -}}
						{{- if not (eq .Amd64 "v1")}}{{ .Amd64 }}{{ end -}}
						{{- .ConventionalExtension -}}
					`,
					PackageName: "foo{{ if .IsSnapshot }}-snapshot{{ end }}",
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

	if snapshot {
		ctx.Snapshot = true
	}
	for _, goos := range []string{"linux", "darwin", "aix"} {
		for _, goarch := range []string{"amd64", "386", "arm64", "arm", "mips", "ppc64", "riscv64"} {
			if goarch == "ppc64" && goos != "aix" {
				continue
			}

			switch goarch {
			case "arm64":
				ctx.Artifacts.Add(&artifact.Artifact{
					Name:    "subdir/mybin",
					Path:    binPath,
					Goarch:  goarch,
					Goos:    goos,
					Goarm64: "v8.0",
					Type:    artifact.Binary,
					Extra: map[string]any{
						artifact.ExtraID: "default",
					},
				})
			case "arm":
				for _, goarm := range []string{"6", "7"} {
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:   "subdir/mybin",
						Path:   binPath,
						Goarch: goarch,
						Goos:   goos,
						Goarm:  goarm,
						Type:   artifact.Binary,
						Extra: map[string]any{
							artifact.ExtraID: "default",
						},
					})
				}
			case "amd64":
				for _, goamd64 := range []string{"v1", "v2", "v3", "v4"} {
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:    "subdir/mybin",
						Path:    binPath,
						Goarch:  goarch,
						Goos:    goos,
						Goamd64: goamd64,
						Type:    artifact.Binary,
						Extra: map[string]any{
							artifact.ExtraID: "default",
						},
					})
				}
			case "mips":
				for _, gomips := range []string{"softfloat", "hardfloat"} {
					ctx.Artifacts.Add(&artifact.Artifact{
						Name:   "subdir/mybin",
						Path:   binPath,
						Goarch: goarch,
						Goos:   goos,
						Gomips: gomips,
						Type:   artifact.Binary,
						Extra: map[string]any{
							artifact.ExtraID: "default",
						},
					})
				}
			case "386":
				ctx.Artifacts.Add(&artifact.Artifact{
					Name:   "subdir/mybin",
					Path:   binPath,
					Goarch: goarch,
					Goos:   goos,
					Go386:  "sse2",
					Type:   artifact.Binary,
					Extra: map[string]any{
						artifact.ExtraID: "default",
					},
				})
			case "riscv64":
				ctx.Artifacts.Add(&artifact.Artifact{
					Name:      "subdir/mybin",
					Path:      binPath,
					Goarch:    goarch,
					Goos:      goos,
					Goriscv64: "rva22u64",
					Type:      artifact.Binary,
					Extra: map[string]any{
						artifact.ExtraID: "default",
					},
				})
			case "ppc64":
				ctx.Artifacts.Add(&artifact.Artifact{
					Name:    "subdir/mybin",
					Path:    binPath,
					Goarch:  goarch,
					Goos:    goos,
					Goppc64: "power9",
					Type:    artifact.Binary,
					Extra: map[string]any{
						artifact.ExtraID: "default",
					},
				})
			default:
				ctx.Artifacts.Add(&artifact.Artifact{
					Name:   "subdir/mybin",
					Path:   binPath,
					Goarch: goarch,
					Goos:   goos,
					Type:   artifact.Binary,
					Extra: map[string]any{
						artifact.ExtraID: "default",
					},
				})
			}
		}
	}
	require.NoError(t, Pipe{}.Run(ctx))
	packages := ctx.Artifacts.Filter(artifact.ByType(artifact.LinuxPackage)).List()
	require.Len(t, packages, 52)
	prefix := "foo"
	if snapshot {
		prefix += "-snapshot"
	}
	for _, pkg := range packages {
		format := pkg.Format()
		require.NotEmpty(t, format)
		require.Contains(t, []string{
			prefix + "-1.0.0-1.aarch64.rpm",
			prefix + "-1.0.0-1.armv6hl.rpm",
			prefix + "-1.0.0-1.armv7hl.rpm",
			prefix + "-1.0.0-1.i386.rpm",
			prefix + "-1.0.0-1.mips.rpm",
			prefix + "-1.0.0-1.mips.rpm",
			prefix + "-1.0.0-1.x86_64.rpm",
			prefix + "-1.0.0-1.x86_64v2.rpm",
			prefix + "-1.0.0-1.x86_64v3.rpm",
			prefix + "-1.0.0-1.x86_64v4.rpm",
			prefix + "-1.0.0-1.ppc.rpm",
			prefix + "_1.0.0_aarch64.apk",
			prefix + "_1.0.0_amd64.deb",
			prefix + "_1.0.0_amd64.ipk",
			prefix + "_1.0.0_amd64v2.deb",
			prefix + "_1.0.0_amd64v2.ipk",
			prefix + "_1.0.0_amd64v3.deb",
			prefix + "_1.0.0_amd64v3.ipk",
			prefix + "_1.0.0_amd64v4.deb",
			prefix + "_1.0.0_amd64v4.ipk",
			prefix + "_1.0.0_arm64.deb",
			prefix + "_1.0.0_arm64.ipk",
			prefix + "_1.0.0_armhf.apk",
			prefix + "_1.0.0_armhf.deb",
			prefix + "_1.0.0_armhf.ipk",
			prefix + "_1.0.0_armhf6.deb",
			prefix + "_1.0.0_armhf6.ipk",
			prefix + "_1.0.0_armv7.apk",
			prefix + "_1.0.0_i386.deb",
			prefix + "_1.0.0_i386.ipk",
			prefix + "_1.0.0_mips.apk",
			prefix + "_1.0.0_mips.deb",
			prefix + "_1.0.0_mips.ipk",
			prefix + "_1.0.0_mips.apk",
			prefix + "_1.0.0_mips.deb",
			prefix + "_1.0.0_mips.ipk",
			prefix + "_1.0.0_x86.apk",
			prefix + "_1.0.0_x86.ipk",
			prefix + "_1.0.0_x86_64.apk",
			prefix + "_1.0.0_x86_64.ipk",
			prefix + "_1.0.0_x86_64v2.apk",
			prefix + "_1.0.0_x86_64v2.ipk",
			prefix + "_1.0.0_x86_64v3.apk",
			prefix + "_1.0.0_x86_64v3.ipk",
			prefix + "_1.0.0_x86_64v4.apk",
			prefix + "_1.0.0_x86_64v4.ipk",
			prefix + "-1.0.0-1-aarch64.pkg.tar.zst",
			prefix + "-1.0.0-1-armv6h.pkg.tar.zst",
			prefix + "-1.0.0-1-armv7h.pkg.tar.zst",
			prefix + "-1.0.0-1-i686.pkg.tar.zst",
			prefix + "-1.0.0-1-x86_64.pkg.tar.zst",
			prefix + "-1.0.0-1-x86_64v2.pkg.tar.zst",
			prefix + "-1.0.0-1-x86_64v3.pkg.tar.zst",
			prefix + "-1.0.0-1-x86_64v4.pkg.tar.zst",
			prefix + "-1.0.0-1-mips.pkg.tar.zst",
			prefix + "-1.0.0-1-mips.pkg.tar.zst",
			prefix + "-1.0.0-1-riscv64.pkg.tar.zst",
			prefix + "_1.0.0_riscv64.apk",
			prefix + "-1.0.0-1.riscv64.rpm",
			prefix + "_1.0.0_riscv64.ipk",
			prefix + "_1.0.0_riscv64.deb",
		}, pkg.Name, "package name is not expected")
		require.Equal(t, "someid", pkg.ID())
		require.ElementsMatch(t, []string{binPath}, sources(artifact.MustExtra[files.Contents](*pkg, extraFiles)))
		require.ElementsMatch(t, []string{"/usr/bin/subdir/mybin"}, destinations(artifact.MustExtra[files.Contents](*pkg, extraFiles)))
	}
}

func TestInvalidTemplate(t *testing.T) {
	makeCtx := func() *context.Context {
		ctx := testctx.WrapWithCfg(t.Context(),
			config.Project{
				ProjectName: "test",
				NFPMs: []config.NFPM{
					{
						Formats: []string{"deb"},
						Builds:  []string{"default"},
					},
				},
			},
			testctx.WithVersion("1.2.3"))

		ctx.Artifacts.Add(&artifact.Artifact{
			Name:   "mybin",
			Goos:   "linux",
			Goarch: "amd64",
			Type:   artifact.Binary,
			Extra: map[string]any{
				artifact.ExtraID: "default",
			},
		})
		return ctx
	}

	t.Run("filename_template", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].Meta = true
		ctx.Config.NFPMs[0].NFPMOverridables = config.NFPMOverridables{
			FileNameTemplate: "{{.Foo}",
		}
		require.NoError(t, Pipe{}.Default(ctx))
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("source", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].NFPMOverridables = config.NFPMOverridables{
			Contents: []config.NFPMContent{
				{
					Source:      "{{ .NOPE_SOURCE }}",
					Destination: "/foo",
				},
			},
		}
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("target", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].NFPMOverridables = config.NFPMOverridables{
			Contents: []config.NFPMContent{
				{
					Source:      "./testdata/testfile.txt",
					Destination: "{{ .NOPE_TARGET }}",
				},
			},
		}
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("description", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].Description = "{{ .NOPE_DESC }}"
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("maintainer", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].Maintainer = "{{ .NOPE_DESC }}"
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("homepage", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].Homepage = "{{ .NOPE_HOMEPAGE }}"
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("deb key file", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].Deb.Signature.KeyFile = "{{ .NOPE_KEY_FILE }}"
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("rpm key file", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].RPM.Signature.KeyFile = "{{ .NOPE_KEY_FILE }}"
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("apk key file", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].APK.Signature.KeyFile = "{{ .NOPE_KEY_FILE }}"
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("apk key name", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].APK.Signature.KeyName = "{{ .NOPE_KEY_FILE }}"
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})

	t.Run("bindir", func(t *testing.T) {
		ctx := makeCtx()
		ctx.Config.NFPMs[0].Bindir = "/usr/{{ .NOPE }}"
		testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
	})
}

func TestRunPipeInvalidContentsSourceTemplate(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		NFPMs: []config.NFPM{
			{
				NFPMOverridables: config.NFPMOverridables{
					PackageName: "foo",
					Contents: []config.NFPMContent{
						{
							Source:      "{{.asdsd}",
							Destination: "testfile",
						},
					},
				},
				Formats: []string{"deb"},
				Builds:  []string{"default"},
			},
		},
	})

	ctx.Artifacts.Add(&artifact.Artifact{
		Name:   "mybin",
		Goos:   "linux",
		Goarch: "amd64",
		Type:   artifact.Binary,
		Extra: map[string]any{
			artifact.ExtraID: "default",
		},
	})
	testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
}

func TestRunPipeInvalidContentMTimeReportsCorrectValue(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		NFPMs: []config.NFPM{
			{
				// top-level MTime is empty, so the bug would show an empty
				// value in the error instead of the actual invalid value.
				NFPMOverridables: config.NFPMOverridables{
					PackageName: "foo",
					Contents: []config.NFPMContent{
						{
							Source:      "./testdata/testfile.txt",
							Destination: "/usr/share/testfile.txt",
							FileInfo: config.FileInfo{
								MTime: "not-a-date",
							},
						},
					},
				},
				Formats: []string{"deb"},
				Builds:  []string{"default"},
			},
		},
	})

	ctx.Artifacts.Add(&artifact.Artifact{
		Name:   "mybin",
		Path:   "/does/not/matter",
		Goos:   "linux",
		Goarch: "amd64",
		Type:   artifact.Binary,
		Extra: map[string]any{
			artifact.ExtraID: "default",
		},
	})
	err := Pipe{}.Run(ctx)
	require.Error(t, err)
	require.ErrorContains(t, err, "failed to parse not-a-date:")
}

func TestNoBuildsFound(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		NFPMs: []config.NFPM{
			{
				Formats: []string{"deb"},
				IDs:     []string{"nope"},
			},
		},
	})

	ctx.Artifacts.Add(&artifact.Artifact{
		Name:   "mybin",
		Goos:   "linux",
		Goarch: "amd64",
		Type:   artifact.Binary,
		Extra: map[string]any{
			artifact.ExtraID: "default",
		},
	})
	require.EqualError(t, Pipe{}.Run(ctx), `no linux binaries found`)
}

func TestCreateFileDoesntExist(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		Dist:        dist,
		ProjectName: "asd",
		NFPMs: []config.NFPM{
			{
				Formats: []string{"deb", "rpm", "ipk"},
				Builds:  []string{"default"},
				NFPMOverridables: config.NFPMOverridables{
					PackageName: "foo",
					Contents: []config.NFPMContent{
						{
							Source:      "testdata/testfile.txt",
							Destination: "/var/lib/test/testfile.txt",
						},
					},
				},
			},
		},
	}, testctx.WithVersion("1.2.3"), testctx.WithCurrentTag("v1.2.3"))

	ctx.Artifacts.Add(&artifact.Artifact{
		Name:   "mybin",
		Path:   filepath.Join(dist, "mybin", "mybin"),
		Goos:   "linux",
		Goarch: "amd64",
		Type:   artifact.Binary,
		Extra: map[string]any{
			artifact.ExtraID: "default",
		},
	})
	require.Contains(t, Pipe{}.Run(ctx).Error(), `dist/mybin/mybin": file does not exist`)
}

func TestInvalidConfig(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		Dist: dist,
		NFPMs: []config.NFPM{
			{
				Formats: []string{"deb"},
				Builds:  []string{"default"},
			},
		},
	}, testctx.WithCurrentTag("v1.2.3"), testctx.WithVersion("1.2.3"))

	ctx.Artifacts.Add(&artifact.Artifact{
		Name:   "mybin",
		Path:   filepath.Join(dist, "mybin", "mybin"),
		Goos:   "linux",
		Goarch: "amd64",
		Type:   artifact.Binary,
		Extra: map[string]any{
			artifact.ExtraID: "default",
		},
	})
	require.Contains(t, Pipe{}.Run(ctx).Error(), `package name must be provided`)
}

func TestDefault(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "foobar",
		NFPMs: []config.NFPM{
			{},
		},
	})

	require.NoError(t, Pipe{}.Default(ctx))
	require.Equal(t, "/usr/bin", ctx.Config.NFPMs[0].Bindir)
	require.Empty(t, ctx.Config.NFPMs[0].Builds)
	require.Equal(t, defaultNameTemplate, ctx.Config.NFPMs[0].FileNameTemplate)
	require.Equal(t, ctx.Config.ProjectName, ctx.Config.NFPMs[0].PackageName)
}

func TestDefaultSet(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		NFPMs: []config.NFPM{
			{
				Builds: []string{"foo"},
				Bindir: "/bin",
				NFPMOverridables: config.NFPMOverridables{
					FileNameTemplate: "foo",
				},
			},
		},
	})

	require.NoError(t, Pipe{}.Default(ctx))
	require.Equal(t, "/bin", ctx.Config.NFPMs[0].Bindir)
	require.Equal(t, "foo", ctx.Config.NFPMs[0].FileNameTemplate)
	require.Equal(t, []string{"foo"}, ctx.Config.NFPMs[0].Builds)
	require.Equal(t, config.NFPMRPMScripts{}, ctx.Config.NFPMs[0].RPM.Scripts)
}

func TestOverrides(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		NFPMs: []config.NFPM{
			{
				Bindir: "/bin",
				NFPMOverridables: config.NFPMOverridables{
					FileNameTemplate: "foo",
				},
				Overrides: map[string]config.NFPMOverridables{
					"deb": {
						FileNameTemplate: "bar",
					},
				},
			},
		},
	})

	require.NoError(t, Pipe{}.Default(ctx))
	merged, err := mergeOverrides(ctx.Config.NFPMs[0], "deb")
	require.NoError(t, err)
	require.Equal(t, "/bin", ctx.Config.NFPMs[0].Bindir)
	require.Equal(t, "foo", ctx.Config.NFPMs[0].FileNameTemplate)
	require.Equal(t, "bar", ctx.Config.NFPMs[0].Overrides["deb"].FileNameTemplate)
	require.Equal(t, "bar", merged.FileNameTemplate)
}

func TestDebSpecificConfig(t *testing.T) {
	setupContext := func(tb testing.TB) *context.Context {
		tb.Helper()
		folder := t.TempDir()
		dist := filepath.Join(folder, "dist")
		require.NoError(t, os.Mkdir(dist, 0o755))
		require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
		binPath := filepath.Join(dist, "mybin", "mybin")
		f, err := os.Create(binPath)
		require.NoError(t, err)
		require.NoError(t, f.Close())
		ctx := testctx.WrapWithCfg(t.Context(), config.Project{
			ProjectName: "mybin",
			Dist:        dist,
			NFPMs: []config.NFPM{
				{
					ID:         "someid",
					Builds:     []string{"default"},
					Formats:    []string{"deb"},
					Maintainer: "foo",
					NFPMOverridables: config.NFPMOverridables{
						PackageName: "foo",
						Contents: []config.NFPMContent{
							{
								Source:      "testdata/testfile.txt",
								Destination: "/usr/share/testfile.txt",
							},
						},
						Deb: config.NFPMDeb{
							Signature: config.NFPMDebSignature{
								KeyFile: "./testdata/privkey.gpg",
							},
						},
					},
				},
			},
		}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

		for _, goos := range []string{"linux", "darwin", "android"} {
			for _, goarch := range []string{"amd64", "386"} {
				ctx.Artifacts.Add(&artifact.Artifact{
					Name:   "mybin",
					Path:   binPath,
					Goarch: goarch,
					Goos:   goos,
					Type:   artifact.Binary,
					Extra: map[string]any{
						artifact.ExtraID: "default",
					},
				})
			}
		}
		return ctx
	}

	t.Run("no passphrase set", func(t *testing.T) {
		require.Contains(
			t,
			Pipe{}.Run(setupContext(t)).Error(),
			`key is encrypted but no passphrase was provided`,
		)
	})

	t.Run("global passphrase set", func(t *testing.T) {
		ctx := setupContext(t)
		ctx.Env = map[string]string{
			"NFPM_PASSPHRASE": "hunter2",
		}
		require.NoError(t, Pipe{}.Run(ctx))
	})

	t.Run("general passphrase set", func(t *testing.T) {
		ctx := setupContext(t)
		ctx.Env = map[string]string{
			"NFPM_SOMEID_PASSPHRASE": "hunter2",
		}
		require.NoError(t, Pipe{}.Run(ctx))
	})

	t.Run("packager specific passphrase set", func(t *testing.T) {
		ctx := setupContext(t)
		ctx.Env = map[string]string{
			"NFPM_SOMEID_DEB_PASSPHRASE": "hunter2",
		}
		require.NoError(t, Pipe{}.Run(ctx))
	})

	t.Run("lintian", func(t *testing.T) {
		ctx := setupContext(t)
		ctx.Parallelism = 100
		ctx.Env = map[string]string{
			"NFPM_SOMEID_DEB_PASSPHRASE": "hunter2",
		}
		ctx.Config.NFPMs[0].Deb.Lintian = []string{
			"statically-linked-binary",
			"changelog-file-missing-in-native-package",
		}
		ctx.Config.NFPMs[0].Formats = []string{"apk", "rpm", "deb", "termux.deb", "ipk"}

		require.NoError(t, Pipe{}.Run(ctx))
		for _, format := range []string{"apk", "rpm", "ipk"} {
			require.NoDirExists(t, filepath.Join(ctx.Config.Dist, format))
		}
		require.DirExists(t, filepath.Join(ctx.Config.Dist, "deb"))
		for _, goarch := range []string{"amd64", "386"} {
			bts, err := os.ReadFile(filepath.Join(ctx.Config.Dist, "deb", "foo_"+goarch, "lintian"))
			require.NoError(t, err)
			require.Equal(t, "foo: statically-linked-binary\nfoo: changelog-file-missing-in-native-package", string(bts))
		}
		require.DirExists(t, filepath.Join(ctx.Config.Dist, "termux.deb"))
		for _, goarch := range []string{"x86_64", "i686"} {
			bts, err := os.ReadFile(filepath.Join(ctx.Config.Dist, "termux.deb", "foo_"+goarch, "lintian"))
			require.NoError(t, err)
			require.Equal(t, "foo: statically-linked-binary\nfoo: changelog-file-missing-in-native-package", string(bts))
		}
	})

	t.Run("lintian no debs", func(t *testing.T) {
		ctx := setupContext(t)
		ctx.Parallelism = 100
		ctx.Env = map[string]string{
			"NFPM_SOMEID_DEB_PASSPHRASE": "hunter2",
		}
		ctx.Config.NFPMs[0].Deb.Lintian = []string{
			"statically-linked-binary",
			"changelog-file-missing-in-native-package",
		}
		ctx.Config.NFPMs[0].Formats = []string{"apk", "rpm", "ipk"}

		require.NoError(t, Pipe{}.Run(ctx))
		for _, format := range []string{"deb", "termux.deb"} {
			require.NoDirExists(t, filepath.Join(ctx.Config.Dist, format))
		}
	})
}

func TestRPMSpecificConfig(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	binPath := filepath.Join(dist, "mybin", "mybin")
	f, err := os.Create(binPath)
	require.NoError(t, err)
	require.NoError(t, f.Close())
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		NFPMs: []config.NFPM{
			{
				ID:      "someid",
				Builds:  []string{"default"},
				Formats: []string{"rpm"},
				NFPMOverridables: config.NFPMOverridables{
					PackageName: "foo",
					Contents: []config.NFPMContent{
						{
							Source:      "testdata/testfile.txt",
							Destination: "/usr/share/testfile.txt",
						},
					},
					RPM: config.NFPMRPM{
						Signature: config.NFPMRPMSignature{
							KeyFile: "./testdata/privkey.gpg",
						},
					},
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

	for _, goos := range []string{"linux", "darwin"} {
		for _, goarch := range []string{"amd64", "386"} {
			ctx.Artifacts.Add(&artifact.Artifact{
				Name:   "mybin",
				Path:   binPath,
				Goarch: goarch,
				Goos:   goos,
				Type:   artifact.Binary,
				Extra: map[string]any{
					artifact.ExtraID: "default",
				},
			})
		}
	}

	t.Run("no passphrase set", func(t *testing.T) {
		require.Contains(
			t,
			Pipe{}.Run(ctx).Error(),
			`key is encrypted but no passphrase was provided`,
		)
	})

	t.Run("general passphrase set", func(t *testing.T) {
		ctx.Env = map[string]string{
			"NFPM_SOMEID_PASSPHRASE": "hunter2",
		}
		require.NoError(t, Pipe{}.Run(ctx))
	})

	t.Run("packager specific passphrase set", func(t *testing.T) {
		ctx.Env = map[string]string{
			"NFPM_SOMEID_RPM_PASSPHRASE": "hunter2",
		}
		require.NoError(t, Pipe{}.Run(ctx))
	})
}

func TestRPMSpecificScriptsConfig(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	binPath := filepath.Join(dist, "mybin", "mybin")
	f, err := os.Create(binPath)
	require.NoError(t, err)
	require.NoError(t, f.Close())
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		NFPMs: []config.NFPM{
			{
				ID:      "someid",
				Builds:  []string{"default"},
				Formats: []string{"rpm"},
				NFPMOverridables: config.NFPMOverridables{
					PackageName: "foo",
					RPM: config.NFPMRPM{
						Scripts: config.NFPMRPMScripts{
							PreTrans:  "/does/not/exist_pretrans.sh",
							PostTrans: "/does/not/exist_posttrans.sh",
						},
					},
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

	for _, goos := range []string{"linux", "darwin"} {
		for _, goarch := range []string{"amd64", "386"} {
			ctx.Artifacts.Add(&artifact.Artifact{
				Name:   "mybin",
				Path:   binPath,
				Goarch: goarch,
				Goos:   goos,
				Type:   artifact.Binary,
				Extra: map[string]any{
					artifact.ExtraID: "default",
				},
			})
		}
	}

	t.Run("PreTrans script file does not exist", func(t *testing.T) {
		require.ErrorIs(t, Pipe{}.Run(ctx), os.ErrNotExist)
	})

	t.Run("PostTrans script file does not exist", func(t *testing.T) {
		ctx.Config.NFPMs[0].RPM.Scripts.PreTrans = "testdata/testfile.txt"
		require.ErrorIs(t, Pipe{}.Run(ctx), os.ErrNotExist)
	})

	t.Run("pretrans and posttrans scriptlets set", func(t *testing.T) {
		ctx.Config.NFPMs[0].RPM.Scripts.PreTrans = "testdata/testfile.txt"
		ctx.Config.NFPMs[0].RPM.Scripts.PostTrans = "testdata/testfile.txt"

		require.NoError(t, Pipe{}.Run(ctx))
	})
}

func TestAPKSpecificConfig(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	binPath := filepath.Join(dist, "mybin", "mybin")
	f, err := os.Create(binPath)
	require.NoError(t, err)
	require.NoError(t, f.Close())
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		NFPMs: []config.NFPM{
			{
				ID:         "someid",
				Maintainer: "me@me",
				Builds:     []string{"default"},
				Formats:    []string{"apk"},
				NFPMOverridables: config.NFPMOverridables{
					PackageName: "foo",
					Contents: []config.NFPMContent{
						{
							Source:      "testdata/testfile.txt",
							Destination: "/usr/share/testfile.txt",
						},
					},
					APK: config.NFPMAPK{
						Signature: config.NFPMAPKSignature{
							KeyFile: "./testdata/rsa.priv",
						},
					},
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

	for _, goos := range []string{"linux", "darwin"} {
		for _, goarch := range []string{"amd64", "386"} {
			ctx.Artifacts.Add(&artifact.Artifact{
				Name:   "mybin",
				Path:   binPath,
				Goarch: goarch,
				Goos:   goos,
				Type:   artifact.Binary,
				Extra: map[string]any{
					artifact.ExtraID: "default",
				},
			})
		}
	}

	t.Run("no passphrase set", func(t *testing.T) {
		require.Contains(
			t,
			Pipe{}.Run(ctx).Error(),
			`key is encrypted but no passphrase was provided`,
		)
	})

	t.Run("general passphrase set", func(t *testing.T) {
		ctx.Env = map[string]string{
			"NFPM_SOMEID_PASSPHRASE": "hunter2",
		}
		require.NoError(t, Pipe{}.Run(ctx))
	})

	t.Run("packager specific passphrase set", func(t *testing.T) {
		ctx.Env = map[string]string{
			"NFPM_SOMEID_APK_PASSPHRASE": "hunter2",
		}
		require.NoError(t, Pipe{}.Run(ctx))
	})
}

func TestAPKSpecificScriptsConfig(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	binPath := filepath.Join(dist, "mybin", "mybin")
	f, err := os.Create(binPath)
	require.NoError(t, err)
	require.NoError(t, f.Close())
	scripts := config.NFPMAPKScripts{
		PreUpgrade:  "/does/not/exist_preupgrade.sh",
		PostUpgrade: "/does/not/exist_postupgrade.sh",
	}
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		NFPMs: []config.NFPM{
			{
				ID:         "someid",
				Maintainer: "me@me",
				Builds:     []string{"default"},
				Formats:    []string{"apk"},
				NFPMOverridables: config.NFPMOverridables{
					PackageName: "foo",
					Contents: []config.NFPMContent{
						{
							Source:      "testdata/testfile.txt",
							Destination: "/usr/share/testfile.txt",
						},
					},
					APK: config.NFPMAPK{
						Scripts: scripts,
					},
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

	for _, goos := range []string{"linux", "darwin"} {
		for _, goarch := range []string{"amd64", "386"} {
			ctx.Artifacts.Add(&artifact.Artifact{
				Name:   "mybin",
				Path:   binPath,
				Goarch: goarch,
				Goos:   goos,
				Type:   artifact.Binary,
				Extra: map[string]any{
					artifact.ExtraID: "default",
				},
			})
		}
	}

	t.Run("PreUpgrade script file does not exist", func(t *testing.T) {
		ctx.Config.NFPMs[0].APK.Scripts = scripts
		ctx.Config.NFPMs[0].APK.Scripts.PostUpgrade = "testdata/testfile.txt"
		require.ErrorIs(t, Pipe{}.Run(ctx), os.ErrNotExist)
	})

	t.Run("PostUpgrade script file does not exist", func(t *testing.T) {
		ctx.Config.NFPMs[0].APK.Scripts = scripts
		ctx.Config.NFPMs[0].APK.Scripts.PreUpgrade = "testdata/testfile.txt"
		require.ErrorIs(t, Pipe{}.Run(ctx), os.ErrNotExist)
	})

	t.Run("preupgrade and postupgrade scriptlets set", func(t *testing.T) {
		ctx.Config.NFPMs[0].APK.Scripts.PreUpgrade = "testdata/testfile.txt"
		ctx.Config.NFPMs[0].APK.Scripts.PostUpgrade = "testdata/testfile.txt"

		require.NoError(t, Pipe{}.Run(ctx))
	})
}

func TestIPKSpecificConfig(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	binPath := filepath.Join(dist, "mybin", "mybin")
	f, err := os.Create(binPath)
	require.NoError(t, err)
	require.NoError(t, f.Close())

	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		NFPMs: []config.NFPM{
			{
				ID:         "someid",
				Maintainer: "me@me",
				Builds:     []string{"default"},
				Formats:    []string{"ipk"},
				NFPMOverridables: config.NFPMOverridables{
					PackageName: "foo",
					Contents: []config.NFPMContent{
						{
							Source:      "testdata/testfile.txt",
							Destination: "/usr/share/testfile.txt",
						},
					},
					IPK: config.NFPMIPK{
						ABIVersion: "1.0",
						Alternatives: []config.NFPMIPKAlternative{
							{
								Priority: 100,
								Target:   "/usr/bin/mybin",
								LinkName: "/usr/bin/myaltbin",
							},
						},
						AutoInstalled: true,
						Essential:     true,
						Predepends:    []string{"libc"},
						Tags:          []string{"foo", "bar"},
						Fields: map[string]string{
							"CustomField": "customValue",
						},
					},
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

	for _, goos := range []string{"linux", "darwin"} {
		for _, goarch := range []string{"amd64", "386"} {
			ctx.Artifacts.Add(&artifact.Artifact{
				Name:   "mybin",
				Path:   binPath,
				Goarch: goarch,
				Goos:   goos,
				Type:   artifact.Binary,
				Extra: map[string]any{
					artifact.ExtraID: "default",
				},
			})
		}
	}

	t.Run("everything is fine", func(t *testing.T) {
		require.NoError(t, Pipe{}.Run(ctx))
		ipks := ctx.Artifacts.
			Filter(artifact.ByExt("ipk")).
			List()
		require.Len(t, ipks, 2)
	})
}

func TestSeveralNFPMsWithTheSameID(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		NFPMs: []config.NFPM{
			{
				ID: "a",
			},
			{
				ID: "a",
			},
		},
	})

	require.EqualError(t, Pipe{}.Default(ctx), "found 2 nfpms with the ID 'a', please fix your config")
}

func TestMeta(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	binPath := filepath.Join(dist, "mybin", "mybin")
	f, err := os.Create(binPath)
	require.NoError(t, err)
	require.NoError(t, f.Close())
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		NFPMs: []config.NFPM{
			{
				ID:          "someid",
				Bindir:      "/usr/bin",
				Builds:      []string{"default"},
				Formats:     []string{"deb", "rpm"},
				Section:     "somesection",
				Priority:    "standard",
				Description: "Some description",
				License:     "MIT",
				Maintainer:  "me@me",
				Vendor:      "asdf",
				Homepage:    "https://goreleaser.github.io",
				Meta:        true,
				NFPMOverridables: config.NFPMOverridables{
					FileNameTemplate: defaultNameTemplate + "-{{ .Release }}-{{ .Epoch }}",
					PackageName:      "foo",
					Dependencies:     []string{"make"},
					Recommends:       []string{"svn"},
					Suggests:         []string{"bzr"},
					Replaces:         []string{"fish"},
					Conflicts:        []string{"git"},
					Release:          "10",
					Epoch:            "20",
					Contents: []config.NFPMContent{
						{
							Source:      "testdata/testfile.txt",
							Destination: "/usr/share/testfile.txt",
						},
						{
							Source:      "./testdata/testfile.txt",
							Destination: "/etc/nope.conf",
							Type:        "config",
						},
						{
							Source:      "./testdata/testfile.txt",
							Destination: "/etc/nope-rpm.conf",
							Type:        "config",
							Packager:    "rpm",
						},
						{
							Destination: "/var/log/foobar",
							Type:        "dir",
						},
					},
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

	for _, goos := range []string{"linux", "darwin"} {
		for _, goarch := range []string{"amd64", "386"} {
			ctx.Artifacts.Add(&artifact.Artifact{
				Name:   "mybin",
				Path:   binPath,
				Goarch: goarch,
				Goos:   goos,
				Type:   artifact.Binary,
				Extra: map[string]any{
					artifact.ExtraID: "default",
				},
			})
		}
	}
	require.NoError(t, Pipe{}.Run(ctx))
	packages := ctx.Artifacts.Filter(artifact.ByType(artifact.LinuxPackage)).List()
	require.Len(t, packages, 2)
	for _, pkg := range packages {
		format := pkg.Format()
		require.NotEmpty(t, format)
		require.Equal(t, pkg.Name, "foo_1.0.0_linux_all-10-20."+format)
		require.Equal(t, "someid", pkg.ID())
		require.Equal(t, "all", pkg.Goarch)
		require.Equal(t, "linux", pkg.Goos)
		require.ElementsMatch(t, []string{
			"/var/log/foobar",
			"/usr/share/testfile.txt",
			"/etc/nope.conf",
			"/etc/nope-rpm.conf",
		}, destinations(artifact.MustExtra[files.Contents](*pkg, extraFiles)))
	}

	require.Len(t, ctx.Config.NFPMs[0].Contents, 4, "should not modify the config file list")
}

func TestMetaNoBinaries(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "testpkg",
		Dist:        dist,
		NFPMs: []config.NFPM{
			{
				ID:          "someid",
				Bindir:      "/usr/bin",
				Formats:     []string{"deb", "rpm"},
				Description: "Some description",
				License:     "MIT",
				Maintainer:  "me@me",
				Vendor:      "asdf",
				Homepage:    "https://goreleaser.github.io",
				Meta:        true,
				NFPMOverridables: config.NFPMOverridables{
					PackageName: "foo",
					Contents: []config.NFPMContent{
						{
							Source:      "testdata/testfile.txt",
							Destination: "/usr/share/testfile.txt",
						},
						{
							Destination: "/var/log/foobar",
							Type:        "dir",
						},
					},
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

	require.NoError(t, Pipe{}.Run(ctx))
	packages := ctx.Artifacts.Filter(artifact.ByType(artifact.LinuxPackage)).List()
	require.Len(t, packages, 2)
	for _, pkg := range packages {
		format := pkg.Format()
		require.NotEmpty(t, format)
		require.Equal(t, "someid", pkg.ID())
		require.Equal(t, "all", pkg.Goarch)
		require.Equal(t, "linux", pkg.Goos)
		require.ElementsMatch(t, []string{
			"/var/log/foobar",
			"/usr/share/testfile.txt",
		}, destinations(artifact.MustExtra[files.Contents](*pkg, extraFiles)))
	}
}

func TestSkipSign(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	binPath := filepath.Join(dist, "mybin", "mybin")
	_, err := os.Create(binPath)
	require.NoError(t, err)
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		NFPMs: []config.NFPM{
			{
				ID:      "someid",
				Builds:  []string{"default"},
				Formats: []string{"deb", "rpm", "apk"},
				NFPMOverridables: config.NFPMOverridables{
					PackageName:      "foo",
					FileNameTemplate: defaultNameTemplate,
					Contents: []config.NFPMContent{
						{
							Source:      "testdata/testfile.txt",
							Destination: "/usr/share/testfile.txt",
						},
					},
					Deb: config.NFPMDeb{
						Signature: config.NFPMDebSignature{
							KeyFile: "/does/not/exist.gpg",
						},
					},
					RPM: config.NFPMRPM{
						Signature: config.NFPMRPMSignature{
							KeyFile: "/does/not/exist.gpg",
						},
					},
					APK: config.NFPMAPK{
						Signature: config.NFPMAPKSignature{
							KeyFile: "/does/not/exist.gpg",
						},
					},
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

	for _, goos := range []string{"linux", "darwin"} {
		for _, goarch := range []string{"amd64", "386"} {
			ctx.Artifacts.Add(&artifact.Artifact{
				Name:   "mybin",
				Path:   binPath,
				Goarch: goarch,
				Goos:   goos,
				Type:   artifact.Binary,
				Extra: map[string]any{
					artifact.ExtraID: "default",
				},
			})
		}
	}

	t.Run("skip sign not set", func(t *testing.T) {
		// TODO: once https://github.com/goreleaser/nfpm/pull/630 is released,
		// use require.ErrorIs() here.
		require.Error(t, Pipe{}.Run(ctx))
	})

	t.Run("skip sign set", func(t *testing.T) {
		skips.Set(ctx, skips.Sign)
		require.NoError(t, Pipe{}.Run(ctx))
	})
}

func TestBinDirTemplating(t *testing.T) {
	folder := t.TempDir()
	dist := filepath.Join(folder, "dist")
	require.NoError(t, os.Mkdir(dist, 0o755))
	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
	binPath := filepath.Join(dist, "mybin", "mybin")
	f, err := os.Create(binPath)
	require.NoError(t, err)
	require.NoError(t, f.Close())
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		ProjectName: "mybin",
		Dist:        dist,
		Env: []string{
			"PRO=pro",
			"DESC=templates",
			"MAINTAINER=me@me",
		},
		NFPMs: []config.NFPM{
			{
				ID: "someid",

				Bindir:      "/usr/lib/{{ .Env.PRO }}/nagios/plugins",
				Builds:      []string{"default"},
				Formats:     []string{"rpm"},
				Section:     "somesection",
				Priority:    "standard",
				Description: "Some description with {{ .Env.DESC }}",
				License:     "MIT",
				Maintainer:  "{{ .Env.MAINTAINER }}",
				Vendor:      "asdf",
				Homepage:    "https://goreleaser.com/{{ .Env.PRO }}",
				NFPMOverridables: config.NFPMOverridables{
					PackageName: "foo",
				},
			},
		},
	}, testctx.WithVersion("1.0.0"), testctx.WithCurrentTag("v1.0.0"))

	for _, goos := range []string{"linux"} {
		for _, goarch := range []string{"amd64", "386"} {
			ctx.Artifacts.Add(&artifact.Artifact{
				Name:   "subdir/mybin",
				Path:   binPath,
				Goarch: goarch,
				Goos:   goos,
				Type:   artifact.Binary,
				Extra: map[string]any{
					artifact.ExtraID: "default",
				},
			})
		}
	}
	require.NoError(t, Pipe{}.Run(ctx))
	packages := ctx.Artifacts.Filter(artifact.ByType(artifact.LinuxPackage)).List()

	for _, pkg := range packages {
		format := pkg.Format()
		require.NotEmpty(t, format)
		// the final binary should contain the evaluated bindir (after template eval)
		require.ElementsMatch(t, []string{
			"/usr/lib/pro/nagios/plugins/subdir/mybin",
		}, destinations(artifact.MustExtra[files.Contents](*pkg, extraFiles)))
	}
}

func TestSkip(t *testing.T) {
	t.Run("skip", func(t *testing.T) {
		require.True(t, Pipe{}.Skip(testctx.Wrap(t.Context())))
	})
	t.Run("skip flag", func(t *testing.T) {
		ctx := testctx.WrapWithCfg(t.Context(), config.Project{
			NFPMs: []config.NFPM{
				{},
			},
		}, testctx.Skip(skips.NFPM))

		require.True(t, Pipe{}.Skip(ctx))
	})

	t.Run("dont skip", func(t *testing.T) {
		ctx := testctx.WrapWithCfg(t.Context(), config.Project{
			NFPMs: []config.NFPM{
				{},
			},
		})

		require.False(t, Pipe{}.Skip(ctx))
	})
}

func TestTemplateExt(t *testing.T) {
	ctx := testctx.WrapWithCfg(t.Context(), config.Project{
		Dist: t.TempDir(),
		NFPMs: []config.NFPM{
			{
				NFPMOverridables: config.NFPMOverridables{
					FileNameTemplate: "a_{{ .ConventionalExtension }}_b",
					PackageName:      "foo",
				},
				Meta:       true,
				Maintainer: "foo@bar",
				Formats:    []string{"deb", "rpm", "termux.deb", "apk", "archlinux"},
			},
		},
	})

	require.NoError(t, Pipe{}.Run(ctx))

	packages := ctx.Artifacts.Filter(artifact.ByType(artifact.LinuxPackage)).List()
	require.Len(t, packages, 5)
	names := make([]string, 0, 5)
	for _, p := range packages {
		names = append(names, p.Name)
	}

	require.ElementsMatch(t, []string{
		"a_.apk_b.apk",
		"a_.deb_b.deb",
		"a_.rpm_b.rpm",
		"a_.termux.deb_b.termux.deb",
		"a_.pkg.tar.zst_b.pkg.tar.zst",
	}, names)
}

func sources(contents files.Contents) []string {
	result := make([]string, 0, len(contents))
	for _, f := range contents {
		if f.Source == "" {
			continue
		}
		result = append(result, f.Source)
	}
	return result
}
