package paths

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

func TestResolveUsesXDGOverrides(t *testing.T) {
	dir := t.TempDir()
	t.Setenv("XDG_CONFIG_HOME", filepath.Join(dir, "config"))
	t.Setenv("XDG_STATE_HOME", filepath.Join(dir, "state"))
	t.Setenv("XDG_CACHE_HOME", filepath.Join(dir, "cache"))
	t.Setenv("XDG_RUNTIME_DIR", filepath.Join(dir, "run"))

	layout, err := Resolve()
	if err != nil {
		t.Fatalf("Resolve: %v", err)
	}
	if layout.ConfigDir != filepath.Join(dir, "config", "banger") {
		t.Errorf("ConfigDir = %q", layout.ConfigDir)
	}
	if layout.StateDir != filepath.Join(dir, "state", "banger") {
		t.Errorf("StateDir = %q", layout.StateDir)
	}
	if layout.CacheDir != filepath.Join(dir, "cache", "banger") {
		t.Errorf("CacheDir = %q", layout.CacheDir)
	}
	if layout.RuntimeDir != filepath.Join(dir, "run", "banger") {
		t.Errorf("RuntimeDir = %q", layout.RuntimeDir)
	}
	if !strings.HasSuffix(layout.SocketPath, "bangerd.sock") {
		t.Errorf("SocketPath = %q", layout.SocketPath)
	}
	if !strings.HasSuffix(layout.DBPath, "state.db") {
		t.Errorf("DBPath = %q", layout.DBPath)
	}
}

func TestResolveUserForHomeUsesProvidedHome(t *testing.T) {
	home := filepath.Join(t.TempDir(), "owner")
	layout, err := ResolveUserForHome(home)
	if err != nil {
		t.Fatalf("ResolveUserForHome: %v", err)
	}
	if layout.ConfigDir != filepath.Join(home, ".config", "banger") {
		t.Fatalf("ConfigDir = %q", layout.ConfigDir)
	}
	if layout.StateDir != filepath.Join(home, ".local", "state", "banger") {
		t.Fatalf("StateDir = %q", layout.StateDir)
	}
	if layout.KnownHostsPath != filepath.Join(home, ".local", "state", "banger", "ssh", "known_hosts") {
		t.Fatalf("KnownHostsPath = %q", layout.KnownHostsPath)
	}
}

func TestResolveSystemUsesFixedPaths(t *testing.T) {
	layout := ResolveSystem()
	if layout.SocketPath != "/run/banger/bangerd.sock" {
		t.Fatalf("SocketPath = %q", layout.SocketPath)
	}
	if layout.StateDir != "/var/lib/banger" {
		t.Fatalf("StateDir = %q", layout.StateDir)
	}
	if layout.KnownHostsPath != "/var/lib/banger/ssh/known_hosts" {
		t.Fatalf("KnownHostsPath = %q", layout.KnownHostsPath)
	}
}

func TestResolveFallsBackWhenRuntimeUnset(t *testing.T) {
	t.Setenv("XDG_RUNTIME_DIR", "")
	layout, err := Resolve()
	if err != nil {
		t.Fatalf("Resolve: %v", err)
	}
	if !strings.Contains(layout.RuntimeDir, "banger-runtime-") {
		t.Errorf("expected fallback runtime dir, got %q", layout.RuntimeDir)
	}
}

func TestEnsureCreatesAllDirs(t *testing.T) {
	base := t.TempDir()
	layout := Layout{
		ConfigDir:   filepath.Join(base, "config"),
		StateDir:    filepath.Join(base, "state"),
		CacheDir:    filepath.Join(base, "cache"),
		RuntimeDir:  filepath.Join(base, "runtime"),
		VMsDir:      filepath.Join(base, "state/vms"),
		ImagesDir:   filepath.Join(base, "state/images"),
		KernelsDir:  filepath.Join(base, "state/kernels"),
		OCICacheDir: filepath.Join(base, "cache/oci"),
	}
	if err := Ensure(layout); err != nil {
		t.Fatalf("Ensure: %v", err)
	}
	for _, dir := range []string{
		layout.ConfigDir,
		layout.StateDir,
		layout.CacheDir,
		layout.RuntimeDir,
		layout.VMsDir,
		layout.ImagesDir,
		layout.KernelsDir,
		layout.OCICacheDir,
	} {
		info, err := os.Stat(dir)
		if err != nil {
			t.Errorf("stat %q: %v", dir, err)
			continue
		}
		if !info.IsDir() {
			t.Errorf("%q is not a directory", dir)
		}
	}

	// RuntimeDir holds sockets; must be 0700.
	info, err := os.Stat(layout.RuntimeDir)
	if err != nil {
		t.Fatalf("stat runtime: %v", err)
	}
	if perm := info.Mode().Perm(); perm != 0o700 {
		t.Errorf("RuntimeDir mode = %#o, want 0700", perm)
	}

	// Idempotent.
	if err := Ensure(layout); err != nil {
		t.Fatalf("Ensure (second run): %v", err)
	}
}

func TestEnsureTightensStaleRuntimeDirMode(t *testing.T) {
	base := t.TempDir()
	runtime := filepath.Join(base, "runtime")
	if err := os.MkdirAll(runtime, 0o755); err != nil {
		t.Fatalf("MkdirAll: %v", err)
	}
	if err := Ensure(Layout{RuntimeDir: runtime}); err != nil {
		t.Fatalf("Ensure: %v", err)
	}
	info, err := os.Stat(runtime)
	if err != nil {
		t.Fatalf("stat: %v", err)
	}
	if perm := info.Mode().Perm(); perm != 0o700 {
		t.Errorf("mode = %#o, want 0700 after Ensure", perm)
	}
}

func TestBangerdPathEnvOverride(t *testing.T) {
	t.Setenv("BANGER_DAEMON_BIN", "/tmp/custom-bangerd")
	got, err := BangerdPath()
	if err != nil {
		t.Fatalf("BangerdPath: %v", err)
	}
	if got != "/tmp/custom-bangerd" {
		t.Errorf("got %q, want /tmp/custom-bangerd", got)
	}
}

func TestBangerdPathFindsSiblingBinary(t *testing.T) {
	t.Setenv("BANGER_DAEMON_BIN", "")

	root := t.TempDir()
	sibling := filepath.Join(root, "bangerd")
	if err := os.WriteFile(sibling, []byte("#!/bin/sh\n"), 0o755); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}
	original := executablePath
	executablePath = func() (string, error) { return filepath.Join(root, "banger"), nil }
	t.Cleanup(func() { executablePath = original })

	got, err := BangerdPath()
	if err != nil {
		t.Fatalf("BangerdPath: %v", err)
	}
	if got != sibling {
		t.Errorf("got %q, want %q", got, sibling)
	}
}

func TestBangerdPathNotFound(t *testing.T) {
	t.Setenv("BANGER_DAEMON_BIN", "")

	root := t.TempDir()
	original := executablePath
	executablePath = func() (string, error) { return filepath.Join(root, "banger"), nil }
	t.Cleanup(func() { executablePath = original })

	if _, err := BangerdPath(); err == nil {
		t.Fatal("expected error when no sibling bangerd exists")
	}
}
