package ns

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

func writeNSMd(t *testing.T, path, content string) {
	t.Helper()
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		t.Fatalf("mkdir: %v", err)
	}
	if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
		t.Fatalf("write %s: %v", path, err)
	}
}

const validNSMd = `---
kind: Namespace
name: prod
parents: ["_defaults"]
inherits_env: true
inherits_secrets: true
quota:
  cpu: 4
acl:
  admin: ops
---
# Prod namespace

This body is ignored.
`

func TestParseNSMdValid(t *testing.T) {
	tmp := t.TempDir()
	path := filepath.Join(tmp, "ns.md")
	writeNSMd(t, path, validNSMd)
	cfg, err := ParseNSMd(path)
	if err != nil {
		t.Fatalf("ParseNSMd: %v", err)
	}
	if cfg.Name != "prod" {
		t.Errorf("name = %q, want prod", cfg.Name)
	}
	if !eqSlice(cfg.Parents, []string{"_defaults"}) {
		t.Errorf("parents = %v, want [_defaults]", cfg.Parents)
	}
	if !cfg.InheritsEnv || !cfg.InheritsSecrets {
		t.Errorf("inherits_env=%v inherits_secrets=%v, want both true", cfg.InheritsEnv, cfg.InheritsSecrets)
	}
}

func TestParseNSMdMissingFrontmatter(t *testing.T) {
	tmp := t.TempDir()
	path := filepath.Join(tmp, "ns.md")
	writeNSMd(t, path, "# just a body, no frontmatter\n")
	_, err := ParseNSMd(path)
	if err == nil {
		t.Fatal("expected missing frontmatter error, got nil")
	}
	if !strings.Contains(err.Error(), "missing frontmatter") {
		t.Errorf("error = %q, want contains 'missing frontmatter'", err.Error())
	}
}

func TestParseNSMdEmptyFrontmatter(t *testing.T) {
	tmp := t.TempDir()
	path := filepath.Join(tmp, "ns.md")
	writeNSMd(t, path, "---\n---\nbody\n")
	_, err := ParseNSMd(path)
	if err == nil {
		t.Fatal("expected error for empty frontmatter, got nil")
	}
}

func TestParseNSMdWrongKind(t *testing.T) {
	tmp := t.TempDir()
	path := filepath.Join(tmp, "ns.md")
	writeNSMd(t, path, "---\nkind: Job\nname: x\n---\n")
	_, err := ParseNSMd(path)
	if err == nil {
		t.Fatal("expected wrong-kind error, got nil")
	}
	if !strings.Contains(err.Error(), "not \"Namespace\"") {
		t.Errorf("error = %q, want contains 'is not \"Namespace\"'", err.Error())
	}
}

func TestParseNSMdMissingKind(t *testing.T) {
	tmp := t.TempDir()
	path := filepath.Join(tmp, "ns.md")
	writeNSMd(t, path, "---\nname: x\n---\n")
	_, err := ParseNSMd(path)
	if err == nil {
		t.Fatal("expected missing kind error, got nil")
	}
	if !strings.Contains(err.Error(), "missing kind") {
		t.Errorf("error = %q, want contains 'missing kind'", err.Error())
	}
}

func TestParseNSMdMissingName(t *testing.T) {
	tmp := t.TempDir()
	path := filepath.Join(tmp, "ns.md")
	writeNSMd(t, path, "---\nkind: Namespace\n---\n")
	_, err := ParseNSMd(path)
	if err == nil {
		t.Fatal("expected missing name error, got nil")
	}
	if !strings.Contains(err.Error(), "missing name") {
		t.Errorf("error = %q, want contains 'missing name'", err.Error())
	}
}

func TestParseNSMdParentsUnquoted(t *testing.T) {
	tmp := t.TempDir()
	path := filepath.Join(tmp, "ns.md")
	writeNSMd(t, path, "---\nkind: Namespace\nname: x\nparents: [a, b]\n---\n")
	cfg, err := ParseNSMd(path)
	if err != nil {
		t.Fatalf("ParseNSMd: %v", err)
	}
	if !eqSlice(cfg.Parents, []string{"a", "b"}) {
		t.Errorf("parents = %v, want [a b]", cfg.Parents)
	}
}

func TestParseNSMdParentsEmpty(t *testing.T) {
	tmp := t.TempDir()
	path := filepath.Join(tmp, "ns.md")
	writeNSMd(t, path, "---\nkind: Namespace\nname: x\nparents: []\n---\n")
	cfg, err := ParseNSMd(path)
	if err != nil {
		t.Fatalf("ParseNSMd: %v", err)
	}
	if len(cfg.Parents) != 0 {
		t.Errorf("parents = %v, want empty", cfg.Parents)
	}
}

func TestParseNSMdInheritsFalse(t *testing.T) {
	tmp := t.TempDir()
	path := filepath.Join(tmp, "ns.md")
	writeNSMd(t, path, "---\nkind: Namespace\nname: x\ninherits_env: false\ninherits_secrets: no\n---\n")
	cfg, err := ParseNSMd(path)
	if err != nil {
		t.Fatalf("ParseNSMd: %v", err)
	}
	if cfg.InheritsEnv {
		t.Errorf("inherits_env should be false")
	}
	if cfg.InheritsSecrets {
		t.Errorf("inherits_secrets should be false")
	}
}

func TestParseNSMdMissingFile(t *testing.T) {
	_, err := ParseNSMd(filepath.Join(t.TempDir(), "nope.md"))
	if err == nil {
		t.Fatal("expected error for missing file")
	}
}

func TestParseNSMdDirHappy(t *testing.T) {
	root := t.TempDir()
	writeNSMd(t, filepath.Join(root, "_defaults", "ns.md"), "---\nkind: Namespace\nname: _defaults\n---\n")
	writeNSMd(t, filepath.Join(root, "prod", "ns.md"), validNSMd)

	cfgs, err := ParseNSMdDir(root)
	if err != nil {
		t.Fatalf("ParseNSMdDir: %v", err)
	}
	if _, ok := cfgs["_defaults"]; !ok {
		t.Errorf("missing _defaults in %v", cfgs)
	}
	if _, ok := cfgs["prod"]; !ok {
		t.Errorf("missing prod in %v", cfgs)
	}
}

func TestParseNSMdDirMissingDefaults(t *testing.T) {
	root := t.TempDir()
	writeNSMd(t, filepath.Join(root, "prod", "ns.md"), validNSMd)
	_, err := ParseNSMdDir(root)
	if err == nil {
		t.Fatal("expected missing _defaults error, got nil")
	}
	if !strings.Contains(err.Error(), "_defaults") {
		t.Errorf("error = %q, want contains _defaults", err.Error())
	}
}

func TestParseNSMdDirSkipsCluster(t *testing.T) {
	root := t.TempDir()
	writeNSMd(t, filepath.Join(root, "_defaults", "ns.md"), "---\nkind: Namespace\nname: _defaults\n---\n")
	// cluster/ contains a ns.md-shaped file but must be skipped.
	writeNSMd(t, filepath.Join(root, "cluster", "ns.md"), "---\nkind: Namespace\nname: cluster\n---\n")

	cfgs, err := ParseNSMdDir(root)
	if err != nil {
		t.Fatalf("ParseNSMdDir: %v", err)
	}
	if _, ok := cfgs["cluster"]; ok {
		t.Errorf("cluster should be skipped, present in %v", cfgs)
	}
}

func TestParseNSMdDirNoFiles(t *testing.T) {
	root := t.TempDir()
	_, err := ParseNSMdDir(root)
	if err == nil {
		t.Fatal("expected missing _defaults error on empty dir, got nil")
	}
}

func TestParseNSMdDirNotADir(t *testing.T) {
	tmp := t.TempDir()
	// Create a file with the same name as the expected root dir.
	root := filepath.Join(tmp, "notadir")
	writeNSMd(t, root, "x")
	_, err := ParseNSMdDir(root)
	if err == nil {
		t.Fatal("expected error for non-dir root")
	}
}
