package api

import (
	"context"
	"encoding/json"
	"net/http"
	"net/url"
	"strings"
)

// userView is the trimmed user shape returned to managers.
type userView struct {
	ID               string `json:"id"`
	Email            string `json:"email"`
	Name             string `json:"name"`
	Role             string `json:"role"`
	Verified         bool   `json:"verified"`
	Created          string `json:"created"`
	Organization     string `json:"organization"`     // org record id ("" = none)
	OrganizationName string `json:"organizationName"` // resolved name ("" = none)
}

// userFields is the field set fetched for a userView.
const userFields = "id,email,name,role,verified,created,organization"

// getUserRecord fetches a single user via the service account. Returns nil (not
// an error) when the user does not exist.
func (s *Server) getUserRecord(ctx context.Context, id string) (*userView, error) {
	path := "/api/collections/" + s.usersCollection() + "/records/" + url.PathEscape(id) + "?fields=" + userFields
	data, status, err := s.pb.Raw(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, err
	}
	if status != http.StatusOK {
		return nil, nil
	}
	var v userView
	if err := json.Unmarshal(data, &v); err != nil {
		return nil, err
	}
	if v.Role == "" {
		v.Role = roleUser
	}
	return &v, nil
}

// GET /api/users — list users (manager only). Superadmins see everyone; admins
// see only their own organization's members.
func (s *Server) handleListUsers(w http.ResponseWriter, r *http.Request) {
	who := caller(r)
	path := "/api/collections/" + s.usersCollection() + "/records?perPage=500&sort=email&fields=" + userFields
	if !who.isSuperadmin() {
		// Admin: scope to their own organization. An org-less admin manages nobody.
		if who.OrgID == "" {
			writeJSON(w, http.StatusOK, map[string]any{"users": []userView{}})
			return
		}
		path += "&filter=" + url.QueryEscape("organization = \""+who.OrgID+"\"")
	}
	data, status, err := s.pb.Raw(r.Context(), http.MethodGet, path, nil)
	if err != nil {
		writeUpstreamDown(w, err)
		return
	}
	if status != http.StatusOK {
		relay(w, status, data)
		return
	}
	var list struct {
		Items []userView `json:"items"`
	}
	_ = json.Unmarshal(data, &list)
	names := s.orgNameMap(r.Context())
	for i := range list.Items {
		if list.Items[i].Role == "" {
			list.Items[i].Role = roleUser
		}
		list.Items[i].OrganizationName = names[list.Items[i].Organization]
	}
	writeJSON(w, http.StatusOK, map[string]any{"users": list.Items})
}

// POST /api/users — create a user (manager only). Body: {email, password, name?,
// role?, organization?}. Admins may only create within their own org and may not
// mint superadmins; superadmins may target any org (or none) and any role.
func (s *Server) handleCreateUser(w http.ResponseWriter, r *http.Request) {
	who := caller(r)
	var body struct {
		Email        string `json:"email"`
		Password     string `json:"password"`
		Name         string `json:"name"`
		Role         string `json:"role"`
		Organization string `json:"organization"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json")
		return
	}
	body.Email = strings.TrimSpace(strings.ToLower(body.Email))
	if body.Email == "" || !strings.Contains(body.Email, "@") {
		writeError(w, http.StatusBadRequest, "a valid email is required")
		return
	}
	if len(body.Password) < 8 {
		writeError(w, http.StatusBadRequest, "password must be at least 8 characters")
		return
	}

	role, ok := normalizeRole(body.Role)
	if !ok {
		writeError(w, http.StatusBadRequest, "role must be 'user', 'admin', or 'superadmin'")
		return
	}
	org := strings.TrimSpace(body.Organization)

	if !who.isSuperadmin() {
		// Admin: no superadmins, and members are forced into the admin's own org.
		if role == roleSuperadmin {
			writeError(w, http.StatusForbidden, "only a superadmin can create superadmins")
			return
		}
		if who.OrgID == "" {
			writeError(w, http.StatusForbidden, "your account is not attached to an organization")
			return
		}
		org = who.OrgID
	}

	create := map[string]any{
		"email":           body.Email,
		"password":        body.Password,
		"passwordConfirm": body.Password,
		"name":            strings.TrimSpace(body.Name),
		"role":            role,
		"verified":        true,
		"emailVisibility": false,
	}
	// Only send organization when set; a superadmin may deliberately omit it to
	// create an org-less account.
	if org != "" {
		create["organization"] = org
	}

	data, status, err := s.pb.Raw(r.Context(), http.MethodPost, "/api/collections/"+s.usersCollection()+"/records", create)
	if err != nil {
		writeUpstreamDown(w, err)
		return
	}
	if status != http.StatusOK {
		// Relay PocketBase's validation error (e.g. duplicate email, bad org id).
		relay(w, status, data)
		return
	}
	var rec userView
	_ = json.Unmarshal(data, &rec)
	if rec.Role == "" {
		rec.Role = role
	}
	rec.OrganizationName = s.orgName(r.Context(), rec.Organization)
	writeJSON(w, http.StatusCreated, map[string]any{"user": rec})
}

// PATCH /api/users/{id} — edit a user (manager only). Any subset of
// {email, name, role, password, verified, organization} may be supplied. Admins
// are scoped to their own org and cannot touch superadmins or grant the
// superadmin role; nobody can change their own role (avoids self-lockout).
func (s *Server) handleUpdateUser(w http.ResponseWriter, r *http.Request) {
	who := caller(r)
	id := r.PathValue("id")
	if id == "" {
		writeError(w, http.StatusBadRequest, "missing user id")
		return
	}
	var body struct {
		Email        string  `json:"email"`
		Name         *string `json:"name"`
		Role         string  `json:"role"`
		Password     string  `json:"password"`
		Verified     *bool   `json:"verified"`
		Organization *string `json:"organization"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json")
		return
	}

	// Resolve the target so we can enforce org/role scoping.
	target, err := s.getUserRecord(r.Context(), id)
	if err != nil {
		writeUpstreamDown(w, err)
		return
	}
	if target == nil {
		writeError(w, http.StatusNotFound, "user not found")
		return
	}

	if !who.isSuperadmin() {
		// Admin scoping: target must be inside the admin's org and not a superadmin.
		if who.OrgID == "" || target.Organization != who.OrgID {
			writeError(w, http.StatusForbidden, "user is outside your organization")
			return
		}
		if target.Role == roleSuperadmin {
			writeError(w, http.StatusForbidden, "you cannot edit a superadmin")
			return
		}
	}

	patch := map[string]any{}

	if email := strings.TrimSpace(strings.ToLower(body.Email)); email != "" {
		if !strings.Contains(email, "@") {
			writeError(w, http.StatusBadRequest, "a valid email is required")
			return
		}
		patch["email"] = email
	}
	if body.Name != nil {
		patch["name"] = strings.TrimSpace(*body.Name)
	}
	if body.Role != "" {
		role, ok := normalizeRole(body.Role)
		if !ok {
			writeError(w, http.StatusBadRequest, "role must be 'user', 'admin', or 'superadmin'")
			return
		}
		if !who.isSuperadmin() && role == roleSuperadmin {
			writeError(w, http.StatusForbidden, "only a superadmin can grant the superadmin role")
			return
		}
		if who.ID == id && role != who.Role {
			writeError(w, http.StatusBadRequest, "you cannot change your own role")
			return
		}
		patch["role"] = role
	}
	if body.Password != "" {
		if len(body.Password) < 8 {
			writeError(w, http.StatusBadRequest, "password must be at least 8 characters")
			return
		}
		patch["password"] = body.Password
		patch["passwordConfirm"] = body.Password
	}
	if body.Verified != nil {
		patch["verified"] = *body.Verified
	}
	// Organization moves are superadmin-only; admins cannot reassign membership.
	if body.Organization != nil {
		if !who.isSuperadmin() {
			if *body.Organization != who.OrgID {
				writeError(w, http.StatusForbidden, "you cannot move users to another organization")
				return
			}
			// no-op for admins staying in their own org
		} else {
			patch["organization"] = *body.Organization // "" clears membership
		}
	}
	if len(patch) == 0 {
		writeError(w, http.StatusBadRequest, "no changes provided")
		return
	}

	data, status, err := s.pb.Raw(r.Context(), http.MethodPatch,
		"/api/collections/"+s.usersCollection()+"/records/"+url.PathEscape(id), patch)
	if err != nil {
		writeUpstreamDown(w, err)
		return
	}
	if status != http.StatusOK {
		relay(w, status, data)
		return
	}
	var rec userView
	_ = json.Unmarshal(data, &rec)
	if rec.Role == "" {
		rec.Role = roleUser
	}
	rec.OrganizationName = s.orgName(r.Context(), rec.Organization)
	writeJSON(w, http.StatusOK, map[string]any{"user": rec})
}

// DELETE /api/users/{id} — delete a user (manager only). Admins may delete only
// non-superadmin members of their own org; nobody can delete their own account.
func (s *Server) handleDeleteUser(w http.ResponseWriter, r *http.Request) {
	who := caller(r)
	id := r.PathValue("id")
	if id == "" {
		writeError(w, http.StatusBadRequest, "missing user id")
		return
	}
	if who.ID == id {
		writeError(w, http.StatusBadRequest, "you cannot delete your own account")
		return
	}

	if !who.isSuperadmin() {
		target, err := s.getUserRecord(r.Context(), id)
		if err != nil {
			writeUpstreamDown(w, err)
			return
		}
		if target == nil {
			writeError(w, http.StatusNotFound, "user not found")
			return
		}
		if who.OrgID == "" || target.Organization != who.OrgID {
			writeError(w, http.StatusForbidden, "user is outside your organization")
			return
		}
		if target.Role == roleSuperadmin {
			writeError(w, http.StatusForbidden, "you cannot delete a superadmin")
			return
		}
	}

	data, status, err := s.pb.Raw(r.Context(), http.MethodDelete,
		"/api/collections/"+s.usersCollection()+"/records/"+url.PathEscape(id), nil)
	if err != nil {
		writeUpstreamDown(w, err)
		return
	}
	if status != http.StatusOK && status != http.StatusNoContent {
		relay(w, status, data)
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}

// normalizeRole validates a client-supplied role. Returns the canonical value
// and whether it was recognised.
func normalizeRole(role string) (string, bool) {
	switch strings.TrimSpace(strings.ToLower(role)) {
	case "", roleUser:
		return roleUser, true
	case roleAdmin:
		return roleAdmin, true
	case roleSuperadmin:
		return roleSuperadmin, true
	default:
		return "", false
	}
}
