package config

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

	"nadir/internal/module"
	"nadir/internal/rbac"

	"github.com/danielgtaylor/huma/v2"
)

// fakeModule implements module.Module with a fixed permission set, so config
// tests don't depend on the concrete modules' exec behavior.
type fakeModule struct {
	id    string
	perms []rbac.Permission
}

func (f fakeModule) ID() string                     { return f.id }
func (f fakeModule) Permissions() []rbac.Permission { return f.perms }
func (f fakeModule) Register(huma.API)              {}

func mods() []module.Module {
	return []module.Module{
		fakeModule{id: "system", perms: []rbac.Permission{rbac.Read, rbac.Write, rbac.Root}},
		fakeModule{id: "services", perms: []rbac.Permission{rbac.Read, rbac.Write}},
	}
}

func TestSecureCookieDefaultsTrue(t *testing.T) {
	// Fail closed: a config that omits secure_tls must still mark the session
	// cookie Secure, so a forgotten key can't leak it over plaintext.
	if !(&File{}).SecureCookie() {
		t.Error("omitted secure_tls should default to true")
	}
	no := false
	if (&File{Server: Server{SecureTLS: &no}}).SecureCookie() {
		t.Error("secure_tls: false should clear the Secure flag")
	}
}

func TestLoad(t *testing.T) {
	path := filepath.Join(t.TempDir(), "config.yaml")
	os.WriteFile(path, []byte(`
server:
  secure_tls: false
roles:
  admin:
    "*": ["*"]
assignments:
  urania: [admin]
`), 0600)

	f, err := Load(path)
	if err != nil {
		t.Fatal(err)
	}
	if f.SecureCookie() {
		t.Error("secure_tls: false not parsed")
	}
	if len(f.Roles["admin"]) == 0 || len(f.Assignments["urania"]) == 0 {
		t.Error("roles/assignments not parsed")
	}
}

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

func TestApplyValid(t *testing.T) {
	f := &File{
		Roles: map[string]map[string][]string{
			"admin":   {"*": {"*"}},
			"auditor": {"*": {"read"}},
			"sysop":   {"system": {"read", "root"}},
		},
		Assignments: map[string][]string{"urania": {"admin"}, "bob": {"sysop"}},
	}
	roles := rbac.New()
	if err := Apply(f, roles, mods()); err != nil {
		t.Fatal(err)
	}
	if !roles.Can("urania", "services", rbac.Write) {
		t.Error("admin wildcard not applied")
	}
	if !roles.Can("bob", "system", rbac.Root) || roles.Can("bob", "system", rbac.Write) {
		t.Error("sysop grants not applied correctly")
	}
}

func TestApplyErrors(t *testing.T) {
	tests := []struct {
		name string
		f    *File
	}{
		{"unknown module", &File{Roles: map[string]map[string][]string{
			"r": {"firewall": {"read"}}}}},
		{"unknown perm on known module", &File{Roles: map[string]map[string][]string{
			"r": {"system": {"banana"}}}}},
		{"wildcard module with perm no module exports", &File{Roles: map[string]map[string][]string{
			"r": {"*": {"banana"}}}}},
		{"assignment to unknown role", &File{
			Roles:       map[string]map[string][]string{"r": {"system": {"read"}}},
			Assignments: map[string][]string{"u": {"ghost"}}}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := Apply(tt.f, rbac.New(), mods()); err == nil {
				t.Errorf("expected error for %s", tt.name)
			}
		})
	}
}

func TestApplyWildcardPermAlwaysOK(t *testing.T) {
	f := &File{Roles: map[string]map[string][]string{"r": {"system": {"*"}}}}
	if err := Apply(f, rbac.New(), mods()); err != nil {
		t.Fatalf("wildcard permission should validate: %v", err)
	}
}

func TestDefaultPathAndExpandPath(t *testing.T) {
	defaultPath, err := DefaultPath()
	if err != nil {
		t.Fatalf("DefaultPath failed: %v", err)
	}
	expectedSuffix := filepath.Join("nadir", "config.yaml")
	if !filepath.IsAbs(defaultPath) || !strings.HasSuffix(defaultPath, expectedSuffix) {
		t.Errorf("expected default path to end with %q and be absolute, got %q", expectedSuffix, defaultPath)
	}

	expanded, err := ExpandPath("~/foo/config.yaml")
	if err != nil {
		t.Fatalf("ExpandPath failed: %v", err)
	}
	if !filepath.IsAbs(expanded) || !strings.HasSuffix(expanded, filepath.Join("foo", "config.yaml")) {
		t.Errorf("ExpandPath did not resolve ~/ correctly, got %q", expanded)
	}

	plain := "/etc/nadir/config.yaml"
	expandedPlain, err := ExpandPath(plain)
	if err != nil {
		t.Fatalf("ExpandPath failed for plain path: %v", err)
	}
	if expandedPlain != plain {
		t.Errorf("expected no-op for %q, got %q", plain, expandedPlain)
	}
}
