package main

import (
	"strings"
	"testing"
)

func TestParseConfigFindsShares(t *testing.T) {
	doc, err := ParseConfig(`
# comment
[global]
    workgroup = WORKGROUP

[media]
    path = /srv/media
    valid users = alice, bob
    guest ok = no
`)
	if err != nil {
		t.Fatalf("ParseConfig() error = %v", err)
	}

	shares := doc.ShareSections()
	if len(shares) != 1 {
		t.Fatalf("expected 1 share, got %d", len(shares))
	}

	cfg := ShareFromSection(shares[0])
	if cfg.Name != "media" {
		t.Fatalf("expected share name media, got %q", cfg.Name)
	}
	if cfg.Path != "/srv/media" {
		t.Fatalf("expected path /srv/media, got %q", cfg.Path)
	}
	if strings.Join(cfg.ValidUsers, ",") != "alice,bob" {
		t.Fatalf("expected users alice,bob, got %v", cfg.ValidUsers)
	}
}

func TestBuildShareSectionUpdatesValues(t *testing.T) {
	section := &Section{
		Name: "public",
		Lines: []Line{
			{Key: "path", Value: "/old", IsKV: true},
			{Key: "comment", Value: "old", IsKV: true},
		},
	}

	updated := BuildShareSection(section, ShareConfig{
		Name:       "public",
		Path:       "/srv/public",
		Comment:    "",
		Browseable: "yes",
		ReadOnly:   "no",
		GuestOK:    "yes",
		ValidUsers: []string{"alice"},
	})

	cfg := ShareFromSection(updated)
	if cfg.Path != "/srv/public" {
		t.Fatalf("expected updated path, got %q", cfg.Path)
	}
	if cfg.Comment != "" {
		t.Fatalf("expected comment removed, got %q", cfg.Comment)
	}
	if strings.Join(cfg.ValidUsers, ",") != "alice" {
		t.Fatalf("expected alice, got %v", cfg.ValidUsers)
	}
}

func TestSerializeProducesSectionHeaders(t *testing.T) {
	doc := &Document{
		Preamble: []string{"# smb.conf"},
		Sections: []*Section{
			BuildShareSection(nil, ShareConfig{
				Name:       "docs",
				Path:       "/srv/docs",
				Browseable: "yes",
				ReadOnly:   "no",
				GuestOK:    "no",
			}),
		},
	}

	out := doc.Serialize()
	for _, want := range []string{
		"# smb.conf",
		"[docs]",
		"path = /srv/docs",
		"browseable = yes",
	} {
		if !strings.Contains(out, want) {
			t.Fatalf("serialized output missing %q:\n%s", want, out)
		}
	}
}

func TestDetectSambaInstallPlanPrefersApt(t *testing.T) {
	lookPath := func(file string) (string, error) {
		switch file {
		case "apt-get", "sudo":
			return "/usr/bin/" + file, nil
		default:
			return "", execErr(file)
		}
	}

	plan, ok := DetectSambaInstallPlan(lookPath, false)
	if !ok {
		t.Fatalf("expected install plan")
	}
	if plan.ManagerName != "apt" {
		t.Fatalf("expected apt manager, got %q", plan.ManagerName)
	}
	if plan.DisplayCommand() != "sudo sh -c apt-get update && apt-get install -y samba" {
		t.Fatalf("unexpected command: %q", plan.DisplayCommand())
	}
}

func TestDetectSambaInstallPlanWithoutSudo(t *testing.T) {
	lookPath := func(file string) (string, error) {
		switch file {
		case "pacman":
			return "/usr/bin/pacman", nil
		default:
			return "", execErr(file)
		}
	}

	plan, ok := DetectSambaInstallPlan(lookPath, false)
	if !ok {
		t.Fatalf("expected install plan")
	}
	if plan.DisplayCommand() != "pacman -Sy --noconfirm samba" {
		t.Fatalf("unexpected command: %q", plan.DisplayCommand())
	}
}

func TestShareUserReferences(t *testing.T) {
	doc := &Document{
		Sections: []*Section{
			BuildShareSection(nil, ShareConfig{
				Name:       "media",
				Path:       "/srv/media",
				ValidUsers: []string{"alice", "bob"},
			}),
			BuildShareSection(nil, ShareConfig{
				Name:       "photos",
				Path:       "/srv/photos",
				ValidUsers: []string{"alice"},
			}),
		},
	}

	refs := shareUserReferences(doc)
	if len(refs) != 2 {
		t.Fatalf("expected 2 refs, got %d", len(refs))
	}
	if refs[0].User != "alice" || strings.Join(refs[0].Shares, ",") != "media,photos" {
		t.Fatalf("unexpected first ref: %+v", refs[0])
	}
	if refs[1].User != "bob" || strings.Join(refs[1].Shares, ",") != "media" {
		t.Fatalf("unexpected second ref: %+v", refs[1])
	}
}

func TestUnusedAccountCandidates(t *testing.T) {
	entries := []PasswdEntry{
		{Name: "alice", UID: 1001, Shell: "/usr/sbin/nologin"},
		{Name: "bob", UID: 1002, Shell: "/bin/bash"},
		{Name: "daemon", UID: 1, Shell: "/usr/sbin/nologin"},
		{Name: "carol", UID: 1003, Shell: "/bin/false"},
	}

	active := map[string]struct{}{
		"alice": {},
	}

	candidates := unusedAccountCandidates(entries, active)
	if len(candidates) != 1 {
		t.Fatalf("expected 1 candidate, got %d", len(candidates))
	}
	if candidates[0].Name != "carol" {
		t.Fatalf("unexpected candidate: %+v", candidates[0])
	}
}

func TestBuildFstabLine(t *testing.T) {
	line := buildFstabLine(CIFSMountConfig{
		Server:     "fileserver",
		Share:      "media",
		MountPoint: "/mnt/media",
		Username:   "alice",
		Password:   "p@ ss,word",
		UID:        "1000",
		GID:        "1000",
		FileMode:   "0664",
		DirMode:    "0775",
		AutoMount:  true,
		ReadOnly:   false,
	})

	want := "//fileserver/media /mnt/media cifs username=alice,password=p@\\040ss\\,word,iocharset=utf8,uid=1000,gid=1000,file_mode=0664,dir_mode=0775,rw 0 0"
	if line != want {
		t.Fatalf("unexpected fstab line:\nwant: %s\ngot:  %s", want, line)
	}
}

type execErr string

func (e execErr) Error() string {
	return "not found: " + string(e)
}
