package api

import (
	"encoding/json"
	"errors"
	"net/http"
	"regexp"
	"strings"

	"github.com/jackc/pgerrcode"
	"github.com/jackc/pgx/v5/pgconn"

	"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
	"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)

// emailRe is a simple format check. We don't try to validate
// deliverability — the SMTP send itself is the real check.
var emailRe = regexp.MustCompile(`^[^@\s]+@[^@\s]+\.[^@\s]+$`)

type updateProfileReq struct {
	// Pointer fields so the caller can omit-vs-clear distinctly.
	// nil = leave unchanged; non-nil empty string = clear; non-nil
	// non-empty = update to that value. Note: distinguishing
	// omit-vs-clear at JSON-decode time requires custom unmarshaling
	// or a sentinel; for v1 we use the simpler "empty string clears"
	// semantics: any field present in the JSON body is applied.
	DisplayName *string `json:"display_name"`
	Email       *string `json:"email"`
}

type meProfileResp struct {
	ID          string  `json:"id"`
	Username    string  `json:"username"`
	DisplayName *string `json:"display_name"`
	Email       *string `json:"email"`
	IsAdmin     bool    `json:"is_admin"`
	// DebugModeEnabled is the account's diagnostics opt-in. The client
	// reads it here to decide whether to run the on-device diagnostics
	// reporter (M9). Admin-set; the client also has a local OFF switch.
	DebugModeEnabled bool `json:"debug_mode_enabled"`
}

func (h *handlers) handleUpdateMyProfile(w http.ResponseWriter, r *http.Request) {
	user, ok := requireUser(w, r)
	if !ok {
		return
	}
	var req updateProfileReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErr(w, apierror.BadRequest("invalid_body", ""))
		return
	}

	// Normalize: trim spaces; treat empty string as "clear" (NULL).
	var displayName, email *string
	if req.DisplayName != nil {
		s := strings.TrimSpace(*req.DisplayName)
		if s == "" {
			displayName = nil
		} else {
			displayName = &s
		}
	}
	if req.Email != nil {
		s := strings.ToLower(strings.TrimSpace(*req.Email))
		if s == "" {
			email = nil
		} else {
			if !emailRe.MatchString(s) {
				writeErr(w, apierror.BadRequest("email_invalid", ""))
				return
			}
			email = &s
		}
	}

	// If neither field was provided, no-op (just re-fetch and return).
	q := dbq.New(h.pool)
	if req.DisplayName == nil && req.Email == nil {
		current, err := q.GetUserByID(r.Context(), user.ID)
		if err != nil {
			h.logger.Error("update profile: lookup failed", "err", err)
			writeErr(w, apierror.Internal(err))
			return
		}
		writeJSON(w, http.StatusOK, profileViewFromUser(current))
		return
	}

	// To preserve existing values for fields not in the request, we need
	// to fetch the current row and merge.
	current, err := q.GetUserByID(r.Context(), user.ID)
	if err != nil {
		h.logger.Error("update profile: lookup failed", "err", err)
		writeErr(w, apierror.Internal(err))
		return
	}
	if req.DisplayName == nil {
		displayName = current.DisplayName
	}
	if req.Email == nil {
		email = current.Email
	}

	updated, err := q.UpdateUserProfile(r.Context(), dbq.UpdateUserProfileParams{
		ID:          user.ID,
		DisplayName: displayName,
		Email:       email,
	})
	if err != nil {
		var pgErr *pgconn.PgError
		if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation {
			writeErr(w, apierror.Conflict("email_taken", ""))
			return
		}
		h.logger.Error("update profile: update failed", "err", err)
		writeErr(w, apierror.Internal(err))
		return
	}

	writeJSON(w, http.StatusOK, profileViewFromUser(updated))
}

func profileViewFromUser(u dbq.User) meProfileResp {
	return meProfileResp{
		ID:               uuidToString(u.ID),
		Username:         u.Username,
		DisplayName:      u.DisplayName,
		Email:            u.Email,
		IsAdmin:          u.IsAdmin,
		DebugModeEnabled: u.DebugModeEnabled,
	}
}
