// This native navigation editor API has no Salesforce equivalent. UI Apps
// and supported Tooling projections retain their Salesforce wire contracts.
package sfcrmappservice

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

	crmwire "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/wire"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationnavigationv1"
)

const ApplicationNavigationCapabilityID = "io.nexuscrm.sfcrm.application-navigation.v1"

const ApplicationNavigationPrefix = "/services/data/v63.0/app-navigation/"

type RuntimeReadApplicationNavigationFunc func(context.Context, string, semantic.ReadRequest) (semantic.ReadResponse, error)
type RuntimeResolveApplicationNavigationFunc func(context.Context, string, semantic.ResolveRequest) (semantic.ResolveResponse, error)
type RuntimeSaveApplicationNavigationFunc func(context.Context, string, semantic.SaveRequest) (semantic.SaveResponse, error)
type RuntimeApplicationNavigationClient struct {
	read    RuntimeReadApplicationNavigationFunc
	resolve RuntimeResolveApplicationNavigationFunc
	save    RuntimeSaveApplicationNavigationFunc
}

func NewRuntimeApplicationNavigationClient(read RuntimeReadApplicationNavigationFunc, resolve RuntimeResolveApplicationNavigationFunc, save RuntimeSaveApplicationNavigationFunc) (*RuntimeApplicationNavigationClient, error) {
	if read == nil || resolve == nil || save == nil {
		return nil, errors.New("application navigation requires current Runtime read, resolve and save")
	}
	return &RuntimeApplicationNavigationClient{read: read, resolve: resolve, save: save}, nil
}
func (s *Service) serveApplicationNavigation(w http.ResponseWriter, r *http.Request) bool {
	if !strings.HasPrefix(r.URL.EscapedPath(), ApplicationNavigationPrefix) {
		return false
	}
	s.requests.Add(1)
	fail := func(status int, message string) {
		s.failures.Add(1)
		writeCRMError(w, status, crmwire.SFCodeInvalidRequest, message)
	}
	rawID := strings.TrimPrefix(r.URL.EscapedPath(), ApplicationNavigationPrefix)
	id, err := url.PathUnescape(rawID)
	if err != nil || strings.Contains(rawID, "/") || !semantic.ValidIdentifier(id) {
		fail(http.StatusBadRequest, "Invalid application identity")
		return true
	}
	if r.Method != http.MethodGet && r.Method != http.MethodPut {
		w.Header().Set("Allow", "GET, PUT")
		fail(http.StatusMethodNotAllowed, "Method not allowed")
		return true
	}
	if s.draining.Load() {
		s.drainRejected.Add(1)
		fail(http.StatusServiceUnavailable, "Service is draining")
		return true
	}
	bearer, ok := endUserBearer(r.Header.Values("Authorization"))
	if !ok {
		s.localRejected.Add(1)
		s.failures.Add(1)
		writeCRMError(w, http.StatusUnauthorized, crmwire.SFCodeInvalidSessionID, "Session expired or invalid")
		return true
	}
	s.inFlight.Add(1)
	defer s.inFlight.Add(-1)
	ctx, cancel := context.WithTimeout(r.Context(), s.requestTimeout)
	defer cancel()
	var response any
	if r.Method == http.MethodGet {
		request := semantic.ReadRequest{ApplicationID: id}
		var result semantic.ReadResponse
		result, err = s.applicationNavigation.read(ctx, bearer, request)
		if err == nil {
			err = semantic.ValidateReadResponse(request, result)
		}
		response = result
	} else {
		var body struct {
			ExpectedRevision uint64          `json:"expected_revision"`
			IdempotencyKey   string          `json:"idempotency_key"`
			Items            []semantic.Item `json:"items"`
		}
		decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, semantic.MaxSemanticRequestBytes))
		decoder.DisallowUnknownFields()
		if decoder.Decode(&body) != nil || requireJSONEOF(decoder) != nil {
			fail(http.StatusBadRequest, "Invalid navigation document")
			return true
		}
		request := semantic.SaveRequest{ApplicationID: id, ExpectedRevision: body.ExpectedRevision, IdempotencyKey: body.IdempotencyKey, Items: body.Items}
		if err := semantic.ValidateSaveRequest(ctx, request); err != nil {
			s.failures.Add(1)
			writeRuntimeFailure(w, err)
			return true
		}
		if err := validateApplicationNavigationTargets(body.Items); err != nil {
			s.failures.Add(1)
			writeRuntimeFailure(w, err)
			return true
		}
		var result semantic.SaveResponse
		result, err = s.applicationNavigation.save(ctx, bearer, request)
		if err == nil {
			err = semantic.ValidateSaveResponse(request, result)
		}
		response = result
	}
	if err != nil {
		s.failures.Add(1)
		writeRuntimeFailure(w, err)
		return true
	}
	w.Header().Set("Cache-Control", "no-store")
	s.successes.Add(1)
	writeJSON(w, http.StatusOK, response)
	return true
}
