package api

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"

	"drivervault/apiserver/internal/config"
	"drivervault/apiserver/internal/pb"
)

// These tests cover who a newly created account belongs to, through the real
// Handler + middleware chain: an admin's members are pinned to the admin's own
// organization no matter what the client sends, while a superadmin picks the
// organization freely — including omitting it to create an org-less account.
// A stand-in PocketBase (userFakePB) serves the calls those paths hit.

const (
	userTestCallerID = "u1"
	userTestBearer   = "user-bearer-token"
	userTestNewID    = "newuser1"
	userTestOrgA     = "orgA" // the caller's own organization
	userTestOrgB     = "orgB" // some other organization
)

// userFakePB answers the identity and user-create calls, recording the create
// payload so tests can assert on the organization that was actually written.
type userFakePB struct {
	mu sync.Mutex

	// Identity returned by auth-refresh.
	callerRole string
	callerOrg  string

	// Recorded effects.
	createdUsers []map[string]any
}

func (f *userFakePB) handler() http.Handler {
	mux := http.NewServeMux()

	// Service-account auth (pb.Client.Authenticate).
	mux.HandleFunc("POST /api/collections/_superusers/auth-with-password", func(w http.ResponseWriter, r *http.Request) {
		writeJSON(w, 200, map[string]any{"token": "svc-token"})
	})

	// Identify the bearer (withAuth → identify → AuthRefresh).
	mux.HandleFunc("POST /api/collections/users/auth-refresh", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Authorization") == "" {
			writeJSON(w, 401, map[string]any{})
			return
		}
		f.mu.Lock()
		defer f.mu.Unlock()
		writeJSON(w, 200, map[string]any{"record": map[string]any{
			"id": userTestCallerID, "email": "boss@test.local", "name": "Boss",
			"role": f.callerRole, "organization": f.callerOrg,
		}})
	})

	// User create.
	mux.HandleFunc("POST /api/collections/users/records", func(w http.ResponseWriter, r *http.Request) {
		var body map[string]any
		_ = json.NewDecoder(r.Body).Decode(&body)
		f.mu.Lock()
		f.createdUsers = append(f.createdUsers, body)
		f.mu.Unlock()
		org, _ := body["organization"].(string)
		writeJSON(w, 200, map[string]any{
			"id": userTestNewID, "email": body["email"], "name": body["name"],
			"role": body["role"], "verified": true, "organization": org,
			"created": "2026-08-17 10:00:00Z",
		})
	})

	// Organization name lookup for the created record.
	mux.HandleFunc("GET /api/collections/organizations/records/{id}", func(w http.ResponseWriter, r *http.Request) {
		writeJSON(w, 200, map[string]any{"id": r.PathValue("id"), "name": "Org " + r.PathValue("id")})
	})

	return mux
}

// newUserTestServer wires the fake PocketBase to a real Handler and returns the
// app's base URL.
func newUserTestServer(t *testing.T, f *userFakePB) string {
	t.Helper()
	pbSrv := httptest.NewServer(f.handler())
	t.Cleanup(pbSrv.Close)

	s := New(config.Config{UsersCollection: "users"}, pb.New(pbSrv.URL, "admin@test.local", "pw"))
	appSrv := httptest.NewServer(s.Handler())
	t.Cleanup(appSrv.Close)
	return appSrv.URL
}

// An admin's new members join the admin's own organization, even when the
// request asks for a different one.
func TestCreateUserAdminForcesOwnOrganization(t *testing.T) {
	f := &userFakePB{callerRole: roleAdmin, callerOrg: userTestOrgA}
	base := newUserTestServer(t, f)

	status, body := orgReq(t, base, http.MethodPost, "/api/users", map[string]any{
		"email": "member@test.local", "password": "hunter2hunter2",
		"name": "Member", "role": "user", "organization": userTestOrgB,
	})
	if status != http.StatusCreated {
		t.Fatalf("status = %d, want 201 (body %v)", status, body)
	}
	if len(f.createdUsers) != 1 {
		t.Fatalf("created users = %d, want 1 (%v)", len(f.createdUsers), f.createdUsers)
	}
	if got := f.createdUsers[0]["organization"]; got != userTestOrgA {
		t.Errorf("created organization = %v, want the admin's own org %q", got, userTestOrgA)
	}
}

// An admin with no organization has nowhere to put a member, so the create is
// refused rather than producing a stray org-less account.
func TestCreateUserAdminWithoutOrganizationRejected(t *testing.T) {
	f := &userFakePB{callerRole: roleAdmin, callerOrg: ""}
	base := newUserTestServer(t, f)

	status, body := orgReq(t, base, http.MethodPost, "/api/users", map[string]any{
		"email": "member@test.local", "password": "hunter2hunter2",
	})
	if status != http.StatusForbidden {
		t.Fatalf("status = %d, want 403 (body %v)", status, body)
	}
	if len(f.createdUsers) != 0 {
		t.Errorf("created users = %v, want none", f.createdUsers)
	}
}

// A superadmin picks the organization, and it is passed through untouched.
func TestCreateUserSuperadminHonoursChosenOrganization(t *testing.T) {
	f := &userFakePB{callerRole: roleSuperadmin, callerOrg: ""}
	base := newUserTestServer(t, f)

	status, body := orgReq(t, base, http.MethodPost, "/api/users", map[string]any{
		"email": "member@test.local", "password": "hunter2hunter2",
		"name": "Member", "role": "admin", "organization": userTestOrgB,
	})
	if status != http.StatusCreated {
		t.Fatalf("status = %d, want 201 (body %v)", status, body)
	}
	if len(f.createdUsers) != 1 {
		t.Fatalf("created users = %d, want 1 (%v)", len(f.createdUsers), f.createdUsers)
	}
	if got := f.createdUsers[0]["organization"]; got != userTestOrgB {
		t.Errorf("created organization = %v, want %q", got, userTestOrgB)
	}
	user, _ := body["user"].(map[string]any)
	if user["organization"] != userTestOrgB {
		t.Errorf("returned organization = %v, want %q", user["organization"], userTestOrgB)
	}
	if user["organizationName"] != "Org "+userTestOrgB {
		t.Errorf("returned organizationName = %v, want the resolved name", user["organizationName"])
	}
}

// Leaving the picker empty is a deliberate choice: the account is created with
// no organization at all, rather than the field being rejected or defaulted.
func TestCreateUserSuperadminCanOmitOrganization(t *testing.T) {
	for _, tc := range []struct {
		name string
		body map[string]any
	}{
		{"field absent", map[string]any{"email": "solo@test.local", "password": "hunter2hunter2"}},
		{"field empty", map[string]any{"email": "solo@test.local", "password": "hunter2hunter2", "organization": ""}},
	} {
		t.Run(tc.name, func(t *testing.T) {
			f := &userFakePB{callerRole: roleSuperadmin, callerOrg: ""}
			base := newUserTestServer(t, f)

			status, body := orgReq(t, base, http.MethodPost, "/api/users", tc.body)
			if status != http.StatusCreated {
				t.Fatalf("status = %d, want 201 (body %v)", status, body)
			}
			if len(f.createdUsers) != 1 {
				t.Fatalf("created users = %d, want 1 (%v)", len(f.createdUsers), f.createdUsers)
			}
			if got, ok := f.createdUsers[0]["organization"]; ok {
				t.Errorf("created organization = %v, want the field to be omitted entirely", got)
			}
			user, _ := body["user"].(map[string]any)
			if user["organization"] != "" {
				t.Errorf("returned organization = %v, want empty", user["organization"])
			}
		})
	}
}
