package provision

import (
	"errors"
	"log/slog"
	"net/http"
	"strings"

	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
	"github.com/ultisuite/ulti-backend/internal/mail/hosted"
	"github.com/ultisuite/ulti-backend/internal/migration"
	"github.com/ultisuite/ulti-backend/internal/nextcloud"
	"github.com/ultisuite/ulti-backend/internal/users"
)

type Handler struct {
	secret         string
	platformDomain string
	hosted         *hosted.Service
	nc             *nextcloud.Client
	db             *pgxpool.Pool
	logger         *slog.Logger
}

func NewHandler(secret, platformDomain string, hostedSvc *hosted.Service, nc *nextcloud.Client, db *pgxpool.Pool) *Handler {
	return &Handler{
		secret:         strings.TrimSpace(secret),
		platformDomain: strings.ToLower(strings.TrimSpace(platformDomain)),
		hosted:         hostedSvc,
		nc:             nc,
		db:             db,
		logger:         slog.Default().With("component", "provision"),
	}
}

type provisionUserRequest struct {
	Email      string `json:"email"`
	Username   string `json:"username"`
	Name       string `json:"name"`
	Password   string `json:"password"`
	ExternalID string `json:"external_id"`
}

func (h *Handler) ProvisionUser(w http.ResponseWriter, r *http.Request) {
	if h.secret == "" {
		apiresponse.WriteError(w, r, http.StatusServiceUnavailable, "provision_disabled", "provisioning webhook not configured", nil)
		return
	}
	if !authorizeProvision(r, h.secret) {
		apiresponse.WriteError(w, r, http.StatusUnauthorized, "unauthorized", "invalid provision secret", nil)
		return
	}

	req, err := decodeProvisionBody(r)
	if err != nil {
		apiresponse.WriteError(w, r, http.StatusBadRequest, "invalid_json", "invalid request body", nil)
		return
	}

	email := strings.ToLower(strings.TrimSpace(req.Email))
	if email == "" {
		apiresponse.WriteError(w, r, http.StatusBadRequest, "validation_error", "email required", nil)
		return
	}

	if h.platformDomain != "" && !strings.Contains(email, "@") {
		email = email + "@" + h.platformDomain
	}

	ctx := r.Context()
	externalID := strings.TrimSpace(req.ExternalID)
	userID, err := users.ResolveProvisionUser(ctx, h.db, externalID, email, req.Name)
	if err != nil {
		h.logger.Error("resolve user", "error", err, "email", email)
		apiresponse.WriteError(w, r, http.StatusInternalServerError, "internal_error", "failed to provision user", nil)
		return
	}

	skipMailbox, err := migration.HasPendingMigrationInvite(ctx, h.db, email)
	if err != nil {
		h.logger.Error("check migration invite", "error", err, "email", email)
		apiresponse.WriteError(w, r, http.StatusInternalServerError, "internal_error", "failed to check migration invite", nil)
		return
	}

	var result hosted.ProvisionMailboxResult
	if !skipMailbox {
		result, err = h.hosted.EnsureMailboxProvisioned(ctx, hosted.ProvisionMailboxInput{
			UserID:      userID,
			Email:       email,
			DisplayName: req.Name,
			Password:    req.Password,
		})
		if err != nil {
			if errors.Is(err, hosted.ErrAddressTaken) {
				apiresponse.WriteError(w, r, http.StatusConflict, "address_taken", err.Error(), nil)
				return
			}
			h.logger.Error("provision mailbox", "error", err, "email", email)
			apiresponse.WriteError(w, r, http.StatusConflict, "provision_failed", err.Error(), nil)
			return
		}
	}

	_ = migration.LinkHostedMailboxByEmail(ctx, h.db, userID, email)

	if h.nc != nil && userID != "" && externalID != "" {
		if _, err := h.nc.EnsurePrincipal(ctx, email, externalID, req.Name); err != nil {
			h.logger.Warn("nextcloud provision", "error", err)
		}
	}

	resp := map[string]any{
		"user_id": userID,
		"email":   email,
	}
	if !skipMailbox {
		resp["mailbox_id"] = result.Mailbox.ID
		resp["mail_account_id"] = result.MailAccountID
	}
	if skipMailbox {
		resp["mailbox_deferred"] = true
	}
	apiresponse.WriteJSON(w, http.StatusOK, resp)
}

// CheckAddress validates local part availability (Authentik expression policy or public API).
func (h *Handler) CheckAddress(w http.ResponseWriter, r *http.Request) {
	if h.hosted == nil {
		apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"available": true})
		return
	}
	local := strings.TrimSpace(r.URL.Query().Get("local"))
	domain := strings.TrimSpace(r.URL.Query().Get("domain"))
	if domain == "" {
		domain = h.platformDomain
	}
	if local == "" || domain == "" {
		apiresponse.WriteError(w, r, http.StatusBadRequest, "validation_error", "local and domain required", nil)
		return
	}
	available, err := h.hosted.IsAddressAvailable(r.Context(), domain, local)
	if err != nil {
		apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"available": false, "reason": err.Error()})
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"available": available})
}
