package handlers

import (
	"errors"
	"net/http"
	"strconv"
	"strings"

	"github.com/mixeme/selfpost/internal/store"
	"github.com/mixeme/selfpost/internal/web/validate"
	"golang.org/x/crypto/bcrypt"
)

type userFormView struct {
	FormErr      string
	FormUsername string
	FormRole     string
	FormDomains  map[int64]bool
	FormPassword string
}

// HandleUsers lists panel users (global only).
func (h *Handlers) HandleUsers(w http.ResponseWriter, r *http.Request) {
	if _, ok := h.requireGlobal(w, r); !ok {
		return
	}
	rows, err := h.store.ListUserRows()
	if err != nil {
		logf("panel: list users: %v", err)
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}
	data := h.pageBase(r)
	data["Title"] = "SelfPost — users"
	data["Active"] = "users"
	data["Users"] = rows
	data["Flash"] = usersFlash(r)
	h.view.Render(w, http.StatusOK, "users", data)
}

func usersFlash(r *http.Request) string {
	switch r.URL.Query().Get("done") {
	case "created":
		return "User created."
	case "updated":
		return "User updated."
	case "deleted":
		return "User deleted."
	default:
		return ""
	}
}

// HandleUserNew creates a panel user (global only).
func (h *Handlers) HandleUserNew(w http.ResponseWriter, r *http.Request) {
	if _, ok := h.requireGlobal(w, r); !ok {
		return
	}
	switch r.Method {
	case http.MethodGet:
		h.renderUserForm(w, r, http.StatusOK, 0, userFormView{FormRole: string(store.RoleDomainAdmin)})
	case http.MethodPost:
		h.submitUserCreate(w, r)
	default:
		w.Header().Set("Allow", "GET, POST")
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
	}
}

// HandleUserEdit edits or deletes a panel user (global only).
func (h *Handlers) HandleUserEdit(w http.ResponseWriter, r *http.Request) {
	if _, ok := h.requireGlobal(w, r); !ok {
		return
	}
	uid, ok := parseUserID(w, r)
	if !ok {
		return
	}
	u, err := h.store.GetUser(uid)
	if err != nil {
		if errors.Is(err, store.ErrUserNotFound) {
			http.NotFound(w, r)
			return
		}
		logf("panel: get user %d: %v", uid, err)
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}
	switch r.Method {
	case http.MethodGet:
		selected := make(map[int64]bool, len(u.DomainIDs))
		for _, id := range u.DomainIDs {
			selected[id] = true
		}
		h.renderUserForm(w, r, http.StatusOK, u.ID, userFormView{
			FormUsername: u.Username,
			FormRole:     string(u.Role),
			FormDomains:  selected,
		})
	case http.MethodPost:
		if err := r.ParseForm(); err != nil {
			h.renderUserForm(w, r, http.StatusBadRequest, u.ID, userFormView{FormErr: "Invalid form submission.", FormUsername: u.Username, FormRole: string(u.Role)})
			return
		}
		h.submitUserUpdate(w, r, u)
	default:
		w.Header().Set("Allow", "GET, POST")
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
	}
}

func (h *Handlers) renderUserForm(w http.ResponseWriter, r *http.Request, status int, userID int64, view userFormView) {
	domains, err := h.store.ListDomains()
	if err != nil {
		logf("panel: user form: list domains: %v", err)
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}
	data := h.pageBase(r)
	if userID != 0 {
		data["Title"] = "SelfPost — edit user"
	} else {
		data["Title"] = "SelfPost — create user"
	}
	data["Active"] = "users"
	data["UserID"] = userID
	data["Domains"] = domains
	data["Error"] = view.FormErr
	data["FormUsername"] = view.FormUsername
	data["FormRole"] = view.FormRole
	data["GlobalRole"] = store.RoleGlobal
	data["FormDomains"] = view.FormDomains
	data["FormPassword"] = view.FormPassword
	data["IsEdit"] = userID != 0
	data["LastGlobalLocked"] = lastGlobalLocked(h, userID, view.FormRole)
	h.view.Render(w, status, "user_form", data)
}

func lastGlobalLocked(h *Handlers, userID int64, formRole string) bool {
	if userID == 0 || formRole != string(store.RoleGlobal) {
		return false
	}
	n, err := h.store.CountGlobalUsers()
	return err == nil && n <= 1
}

func (h *Handlers) submitUserCreate(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		h.renderUserForm(w, r, http.StatusBadRequest, 0, userFormView{FormErr: "Invalid form submission."})
		return
	}
	username := strings.TrimSpace(r.PostFormValue("username"))
	password := r.PostFormValue("password")
	role := store.Role(r.PostFormValue("role"))
	domainIDs := parseDomainIDs(r)

	if err := validate.Username(username); err != nil {
		h.renderUserForm(w, r, http.StatusBadRequest, 0, userFormView{FormErr: err.Error(), FormUsername: username, FormRole: string(role), FormDomains: domainIDSetFromForm(r)})
		return
	}
	if err := validate.AdminPassword(password); err != nil {
		h.renderUserForm(w, r, http.StatusBadRequest, 0, userFormView{FormErr: err.Error(), FormUsername: username, FormRole: string(role), FormDomains: domainIDSetFromForm(r)})
		return
	}
	if role != store.RoleGlobal && role != store.RoleDomainAdmin {
		h.renderUserForm(w, r, http.StatusBadRequest, 0, userFormView{FormErr: "Choose a valid role.", FormUsername: username, FormRole: string(role), FormDomains: domainIDSetFromForm(r)})
		return
	}
	if role == store.RoleDomainAdmin && len(domainIDs) == 0 {
		h.renderUserForm(w, r, http.StatusBadRequest, 0, userFormView{FormErr: "Select at least one domain for a domain administrator.", FormUsername: username, FormRole: string(role), FormDomains: domainIDSetFromForm(r)})
		return
	}

	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		logf("panel: create user hash: %v", err)
		h.renderUserForm(w, r, http.StatusInternalServerError, 0, userFormView{FormErr: "Internal error. Please try again."})
		return
	}
	if _, err := h.store.CreateUser(username, string(hash), role, domainIDs); err != nil {
		if errors.Is(err, store.ErrUserExists) {
			h.renderUserForm(w, r, http.StatusConflict, 0, userFormView{FormErr: "That username is already in use.", FormUsername: username, FormRole: string(role), FormDomains: domainIDSetFromForm(r)})
			return
		}
		logf("panel: create user: %v", err)
		h.renderUserForm(w, r, http.StatusInternalServerError, 0, userFormView{FormErr: "Could not create user. Please check the logs."})
		return
	}
	http.Redirect(w, r, "/users?done=created", http.StatusSeeOther)
}

func (h *Handlers) submitUserUpdate(w http.ResponseWriter, r *http.Request, u store.User) {
	if err := r.ParseForm(); err != nil {
		h.renderUserForm(w, r, http.StatusBadRequest, u.ID, userFormView{FormErr: "Invalid form submission.", FormUsername: u.Username, FormRole: string(u.Role)})
		return
	}
	p, ok := h.principal(r)
	if !ok {
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}

	username := strings.TrimSpace(r.PostFormValue("username"))
	password := r.PostFormValue("password")
	role := store.Role(r.PostFormValue("role"))
	domainIDs := parseDomainIDs(r)
	selected := domainIDSetFromForm(r)

	if username == "" {
		username = u.Username
	}
	if err := validate.Username(username); err != nil {
		h.renderUserForm(w, r, http.StatusBadRequest, u.ID, userFormView{FormErr: err.Error(), FormUsername: username, FormRole: string(role), FormDomains: selected})
		return
	}
	if role != store.RoleGlobal && role != store.RoleDomainAdmin {
		h.renderUserForm(w, r, http.StatusBadRequest, u.ID, userFormView{FormErr: "Choose a valid role.", FormUsername: username, FormRole: string(role), FormDomains: selected})
		return
	}
	if role == store.RoleDomainAdmin && len(domainIDs) == 0 {
		h.renderUserForm(w, r, http.StatusBadRequest, u.ID, userFormView{FormErr: "Select at least one domain for a domain administrator.", FormUsername: username, FormRole: string(role), FormDomains: selected})
		return
	}

	if u.Role == store.RoleGlobal && role == store.RoleDomainAdmin {
		n, err := h.store.CountGlobalUsers()
		if err != nil || n <= 1 {
			h.renderUserForm(w, r, http.StatusBadRequest, u.ID, userFormView{FormErr: "Cannot demote the last global administrator.", FormUsername: username, FormRole: string(u.Role), FormDomains: selected})
			return
		}
	}

	if u.ID == p.ID && u.Role == store.RoleGlobal && role == store.RoleDomainAdmin {
		n, err := h.store.CountGlobalUsers()
		if err != nil || n <= 1 {
			h.renderUserForm(w, r, http.StatusBadRequest, u.ID, userFormView{FormErr: "You cannot demote yourself without another global administrator.", FormUsername: username, FormRole: string(u.Role), FormDomains: selected})
			return
		}
	}

	hash := u.PasswordHash
	if password != "" {
		if err := validate.AdminPassword(password); err != nil {
			h.renderUserForm(w, r, http.StatusBadRequest, u.ID, userFormView{FormErr: err.Error(), FormUsername: username, FormRole: string(role), FormDomains: selected})
			return
		}
		newHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
		if err != nil {
			logf("panel: update user hash: %v", err)
			h.renderUserForm(w, r, http.StatusInternalServerError, u.ID, userFormView{FormErr: "Internal error. Please try again.", FormUsername: username, FormRole: string(role), FormDomains: selected})
			return
		}
		hash = string(newHash)
	}

	if err := h.store.UpdateUser(u.ID, username, hash, u.DMARCReportEmail); err != nil {
		if errors.Is(err, store.ErrUserExists) {
			h.renderUserForm(w, r, http.StatusConflict, u.ID, userFormView{FormErr: "That username is already in use.", FormUsername: username, FormRole: string(role), FormDomains: selected})
			return
		}
		logf("panel: update user: %v", err)
		h.renderUserForm(w, r, http.StatusInternalServerError, u.ID, userFormView{FormErr: "Could not save user. Please check the logs.", FormUsername: username, FormRole: string(role), FormDomains: selected})
		return
	}

	if role != u.Role {
		if err := h.store.SetUserRole(u.ID, role); err != nil {
			logf("panel: set user role: %v", err)
			h.renderUserForm(w, r, http.StatusInternalServerError, u.ID, userFormView{FormErr: "Could not update role.", FormUsername: username, FormRole: string(role), FormDomains: selected})
			return
		}
		if role == store.RoleGlobal {
			if err := h.store.ClearUserDomains(u.ID); err != nil {
				logf("panel: clear user domains: %v", err)
			}
		}
	}

	if role == store.RoleDomainAdmin {
		if err := h.store.SetUserDomains(u.ID, domainIDs); err != nil {
			logf("panel: set user domains: %v", err)
			h.renderUserForm(w, r, http.StatusInternalServerError, u.ID, userFormView{FormErr: "Could not save domain assignments.", FormUsername: username, FormRole: string(role), FormDomains: selected})
			return
		}
	}

	http.Redirect(w, r, "/users?done=updated", http.StatusSeeOther)
}

// HandleUserDeleteConfirm shows the cascade warning before a panel user is
// removed — the same pattern as HandleDeleteConfirm for domains, so a single
// mis-click on Delete cannot remove a user (P3, code-review.md).
func (h *Handlers) HandleUserDeleteConfirm(w http.ResponseWriter, r *http.Request) {
	if _, ok := h.requireGlobal(w, r); !ok {
		return
	}
	uid, ok := parseUserID(w, r)
	if !ok {
		return
	}
	u, err := h.store.GetUser(uid)
	if err != nil {
		if errors.Is(err, store.ErrUserNotFound) {
			http.NotFound(w, r)
			return
		}
		logf("panel: get user %d: %v", uid, err)
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}
	data := h.pageBase(r)
	data["Title"] = "SelfPost — delete " + u.Username
	data["Active"] = "users"
	data["TargetID"] = u.ID
	data["TargetUsername"] = u.Username
	h.view.Render(w, http.StatusOK, "user_delete", data)
}

// HandleUserDelete performs the deletion confirmed on HandleUserDeleteConfirm
// and returns to the user list.
func (h *Handlers) HandleUserDelete(w http.ResponseWriter, r *http.Request) {
	if _, ok := h.requireGlobal(w, r); !ok {
		return
	}
	uid, ok := parseUserID(w, r)
	if !ok {
		return
	}
	u, err := h.store.GetUser(uid)
	if err != nil {
		if errors.Is(err, store.ErrUserNotFound) {
			http.NotFound(w, r)
			return
		}
		logf("panel: get user %d: %v", uid, err)
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}
	h.submitUserDelete(w, r, u)
}

func (h *Handlers) submitUserDelete(w http.ResponseWriter, r *http.Request, u store.User) {
	p, ok := h.principal(r)
	if !ok {
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}
	if u.ID == p.ID {
		h.renderUserForm(w, r, http.StatusBadRequest, u.ID, userFormView{FormErr: "You cannot delete your own account while signed in.", FormUsername: u.Username, FormRole: string(u.Role)})
		return
	}
	if err := h.store.DeleteUser(u.ID); err != nil {
		if errors.Is(err, store.ErrLastGlobal) {
			h.renderUserForm(w, r, http.StatusBadRequest, u.ID, userFormView{FormErr: "Cannot delete the last global administrator.", FormUsername: u.Username, FormRole: string(u.Role)})
			return
		}
		logf("panel: delete user %d: %v", u.ID, err)
		h.renderUserForm(w, r, http.StatusInternalServerError, u.ID, userFormView{FormErr: "Could not delete user.", FormUsername: u.Username, FormRole: string(u.Role)})
		return
	}
	http.Redirect(w, r, "/users?done=deleted", http.StatusSeeOther)
}

func parseUserID(w http.ResponseWriter, r *http.Request) (int64, bool) {
	id, err := strconv.ParseInt(r.PathValue("uid"), 10, 64)
	if err != nil || id <= 0 {
		http.NotFound(w, r)
		return 0, false
	}
	return id, true
}

func parseDomainIDs(r *http.Request) []int64 {
	var ids []int64
	for _, v := range r.PostForm["domain_ids"] {
		id, err := strconv.ParseInt(v, 10, 64)
		if err == nil && id > 0 {
			ids = append(ids, id)
		}
	}
	return ids
}

func domainIDSetFromForm(r *http.Request) map[int64]bool {
	m := make(map[int64]bool)
	for _, id := range parseDomainIDs(r) {
		m[id] = true
	}
	return m
}
