package users

import (
	"encoding/json"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"nadir/internal/oscmd"

	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humago"
	"github.com/danielgtaylor/huma/v2/humatest"
)

func TestUsersHandlers(t *testing.T) {
	tempPasswd := filepath.Join(t.TempDir(), "passwd")
	initialContent := "root:x:0:0:root:/root:/bin/bash\nalice:x:1000:1000:Alice Smith:/home/alice:/bin/bash\n"
	if err := os.WriteFile(tempPasswd, []byte(initialContent), 0644); err != nil {
		t.Fatal(err)
	}

	oldPasswd := passwdPath
	passwdPath = tempPasswd
	defer func() { passwdPath = oldPasswd }()

	mux := http.NewServeMux()
	api := humatest.Wrap(t, humago.New(mux, huma.DefaultConfig("Test", "1.0.0")))
	registerUsers(api, nil)

	// 1. Test GET /api/users
	resp := api.Get("/api/users")
	if resp.Code != http.StatusOK {
		t.Errorf("list users: got %d, want %d", resp.Code, http.StatusOK)
	}
	var listRes ListUsersOutput
	if err := json.Unmarshal(resp.Body.Bytes(), &listRes.Body); err != nil {
		t.Fatal(err)
	}
	if len(listRes.Body.Users) != 2 {
		t.Errorf("got %d users, want 2", len(listRes.Body.Users))
	}

	// 2. Test GET /api/users/{username}
	resp = api.Get("/api/users/alice")
	if resp.Code != http.StatusOK {
		t.Errorf("get user: got %d, want %d", resp.Code, http.StatusOK)
	}
	var getRes GetUserOutput
	if err := json.Unmarshal(resp.Body.Bytes(), &getRes.Body); err != nil {
		t.Fatal(err)
	}
	if getRes.Body.Username != "alice" || getRes.Body.UID != 1000 {
		t.Errorf("get user: got %+v", getRes.Body)
	}

	resp = api.Get("/api/users/bob")
	if resp.Code != http.StatusNotFound {
		t.Errorf("get non-existent user: got %d, want %d", resp.Code, http.StatusNotFound)
	}

	// 3. Test POST /api/users
	oscmd.SetMock("useradd", func(args []string) oscmd.MockCommand {
		wantArgs := []string{"-m", "-c", "Bob Jones", "-s", "/bin/sh", "--", "bob"}
		if !reflect.DeepEqual(args, wantArgs) {
			t.Errorf("useradd args: got %v, want %v", args, wantArgs)
		}
		bobContent := initialContent + "bob:x:1001:1001:Bob Jones:/home/bob:/bin/sh\n"
		os.WriteFile(tempPasswd, []byte(bobContent), 0644)
		return oscmd.MockCommand{ExitCode: 0}
	})
	defer oscmd.ClearMocks()

	resp = api.Post("/api/users", struct {
		Username   string `json:"username"`
		Comment    string `json:"comment"`
		Shell      string `json:"shell"`
		CreateHome bool   `json:"create_home"`
	}{
		Username:   "bob",
		Comment:    "Bob Jones",
		Shell:      "/bin/sh",
		CreateHome: true,
	})
	if resp.Code != http.StatusOK {
		t.Errorf("create user: got %d, want %d", resp.Code, http.StatusOK)
	}

	// 4. Test DELETE /api/users/{username}
	oscmd.SetMock("userdel", func(args []string) oscmd.MockCommand {
		wantArgs := []string{"-r", "--", "bob"}
		if !reflect.DeepEqual(args, wantArgs) {
			t.Errorf("userdel args: got %v, want %v", args, wantArgs)
		}
		os.WriteFile(tempPasswd, []byte(initialContent), 0644)
		return oscmd.MockCommand{ExitCode: 0}
	})

	resp = api.Delete("/api/users/bob?remove_home=true")
	if resp.Code != http.StatusOK {
		t.Errorf("delete user: got %d, want %d", resp.Code, http.StatusOK)
	}

	// 5. Test POST /api/users/{username}/password
	oscmd.SetMock("chpasswd", func(args []string) oscmd.MockCommand {
		return oscmd.MockCommand{ExitCode: 0}
	})

	resp = api.Post("/api/users/alice/password", struct {
		Password string `json:"password"`
	}{
		Password: "newsecretpwd",
	})
	if resp.Code != http.StatusOK {
		t.Errorf("set password: got %d, want %d", resp.Code, http.StatusOK)
	}

	// 6. Test PUT /api/users/{username}/groups
	oscmd.SetMock("usermod", func(args []string) oscmd.MockCommand {
		wantArgs := []string{"-G", "wheel,dev", "--", "alice"}
		if !reflect.DeepEqual(args, wantArgs) {
			t.Errorf("usermod args: got %v, want %v", args, wantArgs)
		}
		return oscmd.MockCommand{ExitCode: 0}
	})

	oscmd.SetMock("id", func(args []string) oscmd.MockCommand {
		wantArgs := []string{"-nG", "alice"}
		if !reflect.DeepEqual(args, wantArgs) {
			t.Errorf("id args: got %v, want %v", args, wantArgs)
		}
		return oscmd.MockCommand{Stdout: "alice wheel dev\n", ExitCode: 0}
	})

	resp = api.Put("/api/users/alice/groups", struct {
		Groups []string `json:"groups"`
	}{
		Groups: []string{"wheel", "dev"},
	})
	if resp.Code != http.StatusOK {
		t.Errorf("set groups: got %d, want %d", resp.Code, http.StatusOK)
	}
}
