package module

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

// TestRenderWritesTemplate verifies the Go text/template pipeline:
// - template file read from manifest.Dir/<cf.Template>
// - values flow via .Values.key
// - output written under dataPath/<cf.Path>, with sub-dirs created
// - missing keys render as empty (missingkey=zero)
func TestRenderWritesTemplate(t *testing.T) {
	dir := t.TempDir()
	// Simulate a module directory with a template subdir.
	if err := os.MkdirAll(filepath.Join(dir, "templates"), 0o755); err != nil {
		t.Fatal(err)
	}
	tmplPath := filepath.Join(dir, "templates", "demo.conf.tmpl")
	tmplBody := "name={{ .Values.name }}\npassword={{ .Values.password }}\nmissing={{ .Values.nope }}END\n"
	if err := os.WriteFile(tmplPath, []byte(tmplBody), 0o644); err != nil {
		t.Fatal(err)
	}

	m := &Manifest{
		ID:   "demo",
		Name: "Demo",
		Dir:  dir,
		ConfigFiles: []ConfigFile{
			{Path: "conf/demo.conf", Format: "kv", Template: "templates/demo.conf.tmpl"},
		},
	}

	dataPath := filepath.Join(t.TempDir(), "instance-data")
	values := map[string]string{
		"name":     "hello",
		"password": "s3cret",
	}
	if err := Render(m, dataPath, values); err != nil {
		t.Fatalf("Render: %v", err)
	}

	out, err := os.ReadFile(filepath.Join(dataPath, "conf", "demo.conf"))
	if err != nil {
		t.Fatalf("read rendered: %v", err)
	}
	got := string(out)
	if !strings.Contains(got, "name=hello") {
		t.Errorf("missing 'name=hello' in %q", got)
	}
	if !strings.Contains(got, "password=s3cret") {
		t.Errorf("missing 'password=s3cret' in %q", got)
	}
	if !strings.Contains(got, "missing=END") {
		t.Errorf("missing-key didn't render as empty in %q", got)
	}
}

// TestRenderSkipsConfigFilesWithoutTemplate verifies a ConfigFile entry
// without a Template is left alone (operator edits it directly, for example
// via SFTP/file manager).
func TestRenderSkipsConfigFilesWithoutTemplate(t *testing.T) {
	m := &Manifest{
		ID:   "demo",
		Name: "Demo",
		Dir:  t.TempDir(),
		ConfigFiles: []ConfigFile{
			{Path: "manual.cfg", Format: "kv"}, // no Template
		},
	}
	dataPath := filepath.Join(t.TempDir(), "instance-data")
	if err := Render(m, dataPath, nil); err != nil {
		t.Errorf("Render with no-template ConfigFile should not error: %v", err)
	}
	if _, err := os.Stat(filepath.Join(dataPath, "manual.cfg")); err == nil {
		t.Error("should not have written manual.cfg")
	}
}

func TestGenerateSecretsUnique(t *testing.T) {
	m := &Manifest{
		Secrets: []Secret{
			{Name: "telnet_password", Generated: true},
			{Name: "admin_token", Generated: true},
			{Name: "unused_static", Generated: false},
		},
	}
	secrets, err := GenerateSecrets(m)
	if err != nil {
		t.Fatalf("GenerateSecrets: %v", err)
	}
	if len(secrets) != 2 {
		t.Errorf("got %d secrets, want 2 (only Generated=true)", len(secrets))
	}
	if secrets["telnet_password"] == "" || secrets["admin_token"] == "" {
		t.Errorf("empty secret values: %+v", secrets)
	}
	if secrets["telnet_password"] == secrets["admin_token"] {
		t.Errorf("two secrets collided: %q", secrets["telnet_password"])
	}
	if _, ok := secrets["unused_static"]; ok {
		t.Errorf("non-generated secret should not be in output")
	}
}

// TestRenderAgainst7DTDTemplate is an integration smoke test that renders
// the shipped 7 Days to Die template with a plausible value set and asserts
// the resulting XML contains the expected property values.
func TestRenderAgainst7DTDTemplate(t *testing.T) {
	m, err := LoadFile(filepath.Join("..", "..", "modules", "7dtd", "module.yaml"))
	if err != nil {
		t.Skipf("7dtd manifest not loadable: %v", err)
	}
	// The template path under the manifest may not exist yet during early
	// development. Skip cleanly if so.
	var tmplRef, outRel string
	for _, cf := range m.ConfigFiles {
		if cf.Template != "" {
			tmplRef = filepath.Join(m.Dir, cf.Template)
			outRel = cf.Path
			break
		}
	}
	if tmplRef == "" {
		t.Skip("7dtd manifest has no config_files with templates")
	}
	if _, err := os.Stat(tmplRef); err != nil {
		t.Skipf("7dtd template not present: %v", err)
	}

	dataPath := t.TempDir()
	values := map[string]string{
		"server_name":     "Refuge Test",
		"telnet_password": "test-pw-123",
		"max_players":     "16",
		"world_seed":      "Navezgane",
	}
	if err := Render(m, dataPath, values); err != nil {
		t.Fatalf("Render 7dtd: %v", err)
	}
	out, err := os.ReadFile(filepath.Join(dataPath, outRel))
	if err != nil {
		t.Fatalf("read: %v", err)
	}
	got := string(out)
	for _, needle := range []string{"Refuge Test", "test-pw-123", `value="16"`} {
		if !strings.Contains(got, needle) {
			t.Errorf("rendered serverconfig.xml missing %q; got:\n%s", needle, got)
		}
	}
}
