package distrodef

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

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

const testDefLocation = "test_defs"

func TestLoadSimple(t *testing.T) {
	def, err := LoadImageDef([]string{testDefLocation}, "debiantest", "13", "anaconda-iso")
	require.NoError(t, err)
	assert.NotEmpty(t, def.Packages)
}

func TestLoadFuzzy(t *testing.T) {
	def, err := LoadImageDef([]string{testDefLocation}, "fedoratest", "99", "anaconda-iso")
	require.NoError(t, err)
	assert.NotEmpty(t, def.Packages)
}

func TestLoadUnhappy(t *testing.T) {
	_, err := LoadImageDef([]string{testDefLocation}, "lizard", "42", "anaconda-iso")
	assert.ErrorContains(t, err, "could not find def file for distro lizard-42")
	_, err = LoadImageDef([]string{testDefLocation}, "debiantest", "0", "anaconda-iso")
	assert.ErrorContains(t, err, "could not find def file for distro debiantest-0")

	_, err = LoadImageDef([]string{testDefLocation}, "debiantest", "13", "anaconda-disk")
	assert.ErrorContains(t, err, "could not find def for distro debiantest and image type anaconda-disk")

	_, err = LoadImageDef([]string{testDefLocation}, "debiantest", "xxx", "anaconda-disk")
	assert.ErrorContains(t, err, `cannot parse wanted version string: `)
}

const fakeDefFileContent = "anaconda-iso:\n packages:  \n    - foo\n"

func makeFakeDistrodefRoot(t *testing.T, defFiles []string) (searchPaths []string) {
	tmp := t.TempDir()

	for _, defFile := range defFiles {
		p := filepath.Join(tmp, defFile)
		err := os.MkdirAll(filepath.Dir(p), 0755)
		require.NoError(t, err)
		err = os.WriteFile(p, []byte(fakeDefFileContent), 0644)
		require.NoError(t, err)

		if !slices.Contains(searchPaths, filepath.Dir(p)) {
			searchPaths = append(searchPaths, filepath.Dir(p))
		}
	}

	return searchPaths
}

func TestFindDistroDefMultiDirs(t *testing.T) {
	defDirs := makeFakeDistrodefRoot(t, []string{
		"a/debian-12.yaml",
		"b/debian-13.yaml",
		"c/debian-13.yaml",
	})
	assert.Equal(t, 3, len(defDirs))

	def, err := findDistroDef(defDirs, "debian", "13")
	assert.NoError(t, err)
	assert.True(t, strings.HasSuffix(def, "b/debian-13.yaml"))
}

func TestFindDistroDefMultiDirsIgnoreENOENT(t *testing.T) {
	defDirs := makeFakeDistrodefRoot(t, []string{
		"a/debian-13.yaml",
	})
	defDirs = append([]string{"/no/such/path"}, defDirs...)

	def, err := findDistroDef(defDirs, "debian", "13")
	assert.NoError(t, err)
	assert.True(t, strings.HasSuffix(def, "a/debian-13.yaml"))
}

func TestFindDistroDefMultiFuzzy(t *testing.T) {
	defDirs := makeFakeDistrodefRoot(t, []string{
		"a/debian-12.yaml",
		"b/debian-13.yaml",
		"b/b/debian-14.yaml",
		"c/debian-13.yaml",
	})
	// no debian-99, pick the closest
	def, err := findDistroDef(defDirs, "debian", "99")
	assert.NoError(t, err)
	assert.True(t, strings.HasSuffix(def, "b/b/debian-14.yaml"))
}

func TestFindDistroDefMultiFuzzyMinorReleases(t *testing.T) {
	defDirs := makeFakeDistrodefRoot(t, []string{
		"a/debian-12.5.yaml",
		"b/debian-12.yaml",
		"c/debian-13.1.yaml",
		"d/debian-13.1.1.yaml",
		"b/b/debian-13.10.yaml",
	})
	def, err := findDistroDef(defDirs, "debian", "13.11")
	assert.NoError(t, err)
	assert.True(t, strings.HasSuffix(def, "b/b/debian-13.10.yaml"), def)
}

func TestFindDistroDefMultiFuzzyMinorReleasesIsZero(t *testing.T) {
	defDirs := makeFakeDistrodefRoot(t, []string{
		"a/debian-12.yaml",
		"a/debian-13.yaml",
	})
	def, err := findDistroDef(defDirs, "debian", "13.0")
	assert.NoError(t, err)
	assert.True(t, strings.HasSuffix(def, "a/debian-13.yaml"), def)
}

func TestFindDistroDefMultiFuzzyError(t *testing.T) {
	defDirs := makeFakeDistrodefRoot(t, []string{
		"a/debian-13.yaml",
	})
	// the best version we have is newer than what is requested, this
	// is an error
	_, err := findDistroDef(defDirs, "fedora", "30")
	assert.ErrorContains(t, err, "could not find def file for distro fedora-30")
}

func TestFindDistroDefBadNumberIgnoresBadFiles(t *testing.T) {
	defDirs := makeFakeDistrodefRoot(t, []string{
		"a/fedora-NaN.yaml",
	})
	_, err := findDistroDef(defDirs, "fedora", "40")
	assert.ErrorContains(t, err, "could not find def file for distro fedora-40")
}

func TestFindDistroDefCornerCases(t *testing.T) {
	defDirs := makeFakeDistrodefRoot(t, []string{
		"a/fedora-.yaml",
		"b/fedora-1.yaml",
		"c/fedora.yaml",
	})
	def, err := findDistroDef(defDirs, "fedora", "2")
	assert.NoError(t, err)
	assert.True(t, strings.HasSuffix(def, "b/fedora-1.yaml"))
}
