package api

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

// orgView is the trimmed organization shape returned to clients.
type orgView struct {
	ID      string `json:"id"`
	Name    string `json:"name"`
	Created string `json:"created"`
}

// orgNameMap returns an id→name map of all organizations via the service
// account. On any error it returns an empty (non-nil) map so callers can index
// it safely.
func (s *Server) orgNameMap(ctx context.Context) map[string]string {
	out := map[string]string{}
	if !s.pb.Configured() {
		return out
	}
	data, status, err := s.pb.Raw(ctx, http.MethodGet,
		"/api/collections/"+colOrgs+"/records?perPage=500&fields=id,name", nil)
	if err != nil || status != http.StatusOK {
		return out
	}
	var list struct {
		Items []orgView `json:"items"`
	}
	_ = json.Unmarshal(data, &list)
	for _, o := range list.Items {
		out[o.ID] = o.Name
	}
	return out
}

// orgName resolves a single organization's name (best effort; "" on miss).
func (s *Server) orgName(ctx context.Context, id string) string {
	if id == "" || !s.pb.Configured() {
		return ""
	}
	data, status, err := s.pb.Raw(ctx, http.MethodGet,
		"/api/collections/"+colOrgs+"/records/"+url.PathEscape(id)+"?fields=id,name", nil)
	if err != nil || status != http.StatusOK {
		return ""
	}
	var o orgView
	_ = json.Unmarshal(data, &o)
	return o.Name
}

// GET /api/orgs — list organizations (manager only). Superadmins see all;
// admins see only their own organization.
func (s *Server) handleListOrgs(w http.ResponseWriter, r *http.Request) {
	who := caller(r)
	path := "/api/collections/" + colOrgs + "/records?perPage=500&sort=name&fields=id,name,created"
	if !who.isSuperadmin() {
		if who.OrgID == "" {
			writeJSON(w, http.StatusOK, map[string]any{"organizations": []orgView{}})
			return
		}
		path += "&filter=" + url.QueryEscape("id = \""+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 []orgView `json:"items"`
	}
	_ = json.Unmarshal(data, &list)
	writeJSON(w, http.StatusOK, map[string]any{"organizations": list.Items})
}

// POST /api/orgs — create an organization. Body: {name}. A superadmin may create
// any number of organizations and is not attached to them (they manage every
// tenant centrally). Any other user may create one only if they don't already
// belong to an organization, and they become its admin and first member — the
// self-service path for standing up a new tenant.
//
// This handler carries no requireRole gate (any authenticated caller may reach
// it), so it checks the service account itself.
func (s *Server) handleCreateOrg(w http.ResponseWriter, r *http.Request) {
	if !s.pb.Configured() {
		writeError(w, http.StatusServiceUnavailable, "organizations are not configured on the server")
		return
	}
	who := caller(r)
	if !who.isSuperadmin() && who.OrgID != "" {
		writeError(w, http.StatusForbidden, "you already belong to an organization")
		return
	}
	name, ok := decodeOrgName(w, r)
	if !ok {
		return
	}
	data, status, err := s.pb.Raw(r.Context(), http.MethodPost,
		"/api/collections/"+colOrgs+"/records", map[string]any{"name": name})
	if err != nil {
		writeUpstreamDown(w, err)
		return
	}
	if status != http.StatusOK {
		// Relay PocketBase's error (e.g. duplicate name violates the unique index).
		relay(w, status, data)
		return
	}
	var org orgView
	_ = json.Unmarshal(data, &org)

	// A non-superadmin creator becomes the admin of, and first member of, the org
	// they just made. If that promotion fails, roll the org back so we never leave
	// a stranded organization that nobody can administer.
	if !who.isSuperadmin() {
		data, status, err = s.pb.Raw(r.Context(), http.MethodPatch,
			"/api/collections/"+s.usersCollection()+"/records/"+url.PathEscape(who.ID),
			map[string]any{"organization": org.ID, "role": roleAdmin})
		if err != nil || status != http.StatusOK {
			_, _, _ = s.pb.Raw(r.Context(), http.MethodDelete,
				"/api/collections/"+colOrgs+"/records/"+url.PathEscape(org.ID), nil)
			if err != nil {
				writeUpstreamDown(w, err)
				return
			}
			relay(w, status, data)
			return
		}
	}
	writeJSON(w, http.StatusCreated, map[string]any{"organization": org})
}

// PATCH /api/orgs/{id} — rename an organization (manager only). A superadmin may
// rename any organization; an admin may rename only their own. Body: {name}.
func (s *Server) handleUpdateOrg(w http.ResponseWriter, r *http.Request) {
	who := caller(r)
	id := r.PathValue("id")
	if id == "" {
		writeError(w, http.StatusBadRequest, "missing organization id")
		return
	}
	// An admin is scoped to their own organization; a superadmin spans all.
	if !who.isSuperadmin() && (who.OrgID == "" || id != who.OrgID) {
		writeError(w, http.StatusForbidden, "you can only rename your own organization")
		return
	}
	name, ok := decodeOrgName(w, r)
	if !ok {
		return
	}
	data, status, err := s.pb.Raw(r.Context(), http.MethodPatch,
		"/api/collections/"+colOrgs+"/records/"+url.PathEscape(id), map[string]any{"name": name})
	if err != nil {
		writeUpstreamDown(w, err)
		return
	}
	if status != http.StatusOK {
		relay(w, status, data)
		return
	}
	var org orgView
	_ = json.Unmarshal(data, &org)
	writeJSON(w, http.StatusOK, map[string]any{"organization": org})
}

// DELETE /api/orgs/{id} — delete an organization (manager only). A superadmin may
// delete any organization, but only once it has no members. An admin may delete
// only their own organization, and only when they are its sole member: the admin
// is detached and demoted back to a plain user, then the org is removed. Either
// path refuses to silently orphan other members.
func (s *Server) handleDeleteOrg(w http.ResponseWriter, r *http.Request) {
	who := caller(r)
	id := r.PathValue("id")
	if id == "" {
		writeError(w, http.StatusBadRequest, "missing organization id")
		return
	}

	// Count the members that block deletion. A superadmin is blocked by anyone at
	// all; an admin deleting their own org is blocked only by *other* members,
	// since they detach themselves below.
	filter := "organization = \"" + id + "\""
	blocked := "organization still has members; reassign or remove them first"
	if !who.isSuperadmin() {
		// An admin is scoped to their own organization.
		if who.OrgID == "" || id != who.OrgID {
			writeError(w, http.StatusForbidden, "you can only delete your own organization")
			return
		}
		filter += " && id != \"" + who.ID + "\""
		blocked = "organization still has other members; reassign or remove them first"
	}
	countPath := "/api/collections/" + s.usersCollection() + "/records?perPage=1&fields=id&filter=" +
		url.QueryEscape(filter)
	data, status, err := s.pb.Raw(r.Context(), http.MethodGet, countPath, nil)
	if err != nil {
		writeUpstreamDown(w, err)
		return
	}
	if status == http.StatusOK {
		var page struct {
			TotalItems int `json:"totalItems"`
		}
		_ = json.Unmarshal(data, &page)
		if page.TotalItems > 0 {
			writeError(w, http.StatusConflict, blocked)
			return
		}
	}

	// Detach and demote the admin so the org is empty before it is removed. Done
	// before the delete because users.organization does not cascade: a failed
	// delete leaves an empty org a superadmin can clean up, which beats leaving a
	// member pointing at an organization that no longer exists.
	if !who.isSuperadmin() {
		data, status, err = s.pb.Raw(r.Context(), http.MethodPatch,
			"/api/collections/"+s.usersCollection()+"/records/"+url.PathEscape(who.ID),
			map[string]any{"organization": "", "role": roleUser})
		if err != nil {
			writeUpstreamDown(w, err)
			return
		}
		if status != http.StatusOK {
			relay(w, status, data)
			return
		}
	}

	data, status, err = s.pb.Raw(r.Context(), http.MethodDelete,
		"/api/collections/"+colOrgs+"/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})
}

// decodeOrgName parses and validates a {name} body, writing an error response
// and returning ok=false on failure.
func decodeOrgName(w http.ResponseWriter, r *http.Request) (string, bool) {
	var body struct {
		Name string `json:"name"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json")
		return "", false
	}
	name := strings.TrimSpace(body.Name)
	if name == "" {
		writeError(w, http.StatusBadRequest, "organization name is required")
		return "", false
	}
	if len(name) > 120 {
		writeError(w, http.StatusBadRequest, "organization name is too long (max 120)")
		return "", false
	}
	return name, true
}
