package sfcrmappservice

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

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

const LayoutAuthoringCapabilityID = "io.nexuscrm.sfcrm.layout-authoring.v1"
const LayoutAuthoringPath = "/services/data/v63.0/metadata/layout-authoring/"

type RuntimeReadLayoutAuthoringWorkspaceFunc func(context.Context, string, semantic.WorkspaceRequest) (semantic.WorkspaceResponse, error)
type RuntimeReadLayoutAuthoringStoredLayoutFunc func(context.Context, string, semantic.StoredLayoutRequest) (semantic.StoredLayoutResponse, error)
type RuntimeMutateLayoutAuthoringFunc func(context.Context, string, semantic.Request) (semantic.Response, error)

type runtimeLayoutAuthoringClient struct {
	workspace RuntimeReadLayoutAuthoringWorkspaceFunc
	stored    RuntimeReadLayoutAuthoringStoredLayoutFunc
	mutate    RuntimeMutateLayoutAuthoringFunc
}

func newRuntimeLayoutAuthoringClient(
	workspace RuntimeReadLayoutAuthoringWorkspaceFunc,
	stored RuntimeReadLayoutAuthoringStoredLayoutFunc,
	mutate RuntimeMutateLayoutAuthoringFunc,
) (*runtimeLayoutAuthoringClient, error) {
	if workspace == nil || stored == nil || mutate == nil {
		return nil, errors.New("sfcrm AppService: complete synchronous Runtime Layout authoring ports are required")
	}
	return &runtimeLayoutAuthoringClient{workspace: workspace, stored: stored, mutate: mutate}, nil
}

type layoutWorkspaceDTO struct {
	ObjectID       string                      `json:"objectId"`
	LayoutRevision string                      `json:"layoutRevision"`
	Layouts        []semantic.LayoutSummary    `json:"layouts"`
	Assignments    []semantic.LayoutAssignment `json:"assignments"`
}

type layoutStoredDTO struct {
	LayoutID             string                  `json:"layoutId"`
	ObjectID             string                  `json:"objectId"`
	LayoutRevision       string                  `json:"layoutRevision"`
	Document             semantic.LayoutDocument `json:"document"`
	OrderedListFields    []string                `json:"orderedListFields"`
	RequiredCreateFields []string                `json:"requiredCreateFields"`
}

type layoutMutationDTO struct {
	Changed        bool    `json:"changed"`
	LayoutRevision string  `json:"layoutRevision"`
	LayoutID       *string `json:"layoutId,omitempty"`
	AssignmentID   *string `json:"assignmentId,omitempty"`
}

func (service *Service) serveLayoutAuthoring(writer http.ResponseWriter, request *http.Request) bool {
	if service.layoutAuthoring == nil || !strings.HasPrefix(request.URL.Path, LayoutAuthoringPath) {
		return false
	}
	action := strings.TrimPrefix(request.URL.Path, LayoutAuthoringPath)
	if action != "workspace" && action != "stored" && action != "mutate" {
		return false
	}
	if request.Method != http.MethodPost {
		writer.Header().Set("Allow", http.MethodPost)
		writeCRMError(writer, http.StatusMethodNotAllowed, crmwire.SFCodeInvalidRequest, "Method not allowed")
		return true
	}
	service.requests.Add(1)
	if service.draining.Load() {
		service.drainRejected.Add(1)
		writeCRMError(writer, http.StatusServiceUnavailable, crmwire.SFCodeUnknownException, "Service is draining")
		return true
	}
	bearer, ok := endUserBearer(request.Header.Values("Authorization"))
	if !ok {
		service.localRejected.Add(1)
		writeCRMError(writer, http.StatusUnauthorized, crmwire.SFCodeInvalidSessionID, "Session expired or invalid")
		return true
	}
	service.inFlight.Add(1)
	defer service.inFlight.Add(-1)
	ctx, cancel := context.WithTimeout(request.Context(), service.requestTimeout)
	defer cancel()
	request.Body = http.MaxBytesReader(writer, request.Body, service.maxRequestBytes)
	decoder := json.NewDecoder(request.Body)
	decoder.DisallowUnknownFields()
	decode := func(body any) bool {
		if decoder.Decode(body) != nil || requireJSONEOF(decoder) != nil {
			service.localRejected.Add(1)
			writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, "Invalid layout request")
			return false
		}
		return true
	}
	fail := func(err error) { service.failures.Add(1); writeRuntimeFailure(writer, err) }

	switch action {
	case "workspace":
		var body struct {
			ObjectID string `json:"objectId"`
		}
		if !decode(&body) {
			return true
		}
		input := semantic.WorkspaceRequest{ObjectID: body.ObjectID}
		if err := semantic.ValidateWorkspaceRequest(ctx, input); err != nil {
			fail(err)
			return true
		}
		result, err := service.layoutAuthoring.workspace(ctx, bearer, input)
		if err == nil {
			err = semantic.ValidateWorkspaceResponse(input, result)
		}
		if err != nil {
			fail(err)
			return true
		}
		writeJSON(writer, http.StatusOK, layoutWorkspaceDTO{
			ObjectID: result.ObjectID, LayoutRevision: result.LayoutRevision,
			Layouts:     append([]semantic.LayoutSummary{}, result.Layouts...),
			Assignments: append([]semantic.LayoutAssignment{}, result.Assignments...),
		})
	case "stored":
		var body struct {
			ObjectID string `json:"objectId"`
			LayoutID string `json:"layoutId"`
		}
		if !decode(&body) {
			return true
		}
		input := semantic.StoredLayoutRequest{ObjectID: body.ObjectID, LayoutID: body.LayoutID}
		if err := semantic.ValidateStoredLayoutRequest(ctx, input); err != nil {
			fail(err)
			return true
		}
		result, err := service.layoutAuthoring.stored(ctx, bearer, input)
		if err == nil {
			err = semantic.ValidateStoredLayoutResponse(input, result)
		}
		if err != nil {
			fail(err)
			return true
		}
		writeJSON(writer, http.StatusOK, layoutStoredDTO{
			LayoutID: result.LayoutID, ObjectID: result.ObjectID, LayoutRevision: result.LayoutRevision,
			Document:             result.Document,
			OrderedListFields:    append([]string{}, result.OrderedListFields...),
			RequiredCreateFields: append([]string{}, result.RequiredCreateFields...),
		})
	case "mutate":
		var body struct {
			ObjectID               string             `json:"objectId"`
			ExpectedLayoutRevision string             `json:"expectedLayoutRevision"`
			Operation              semantic.Operation `json:"operation"`
		}
		if !decode(&body) {
			return true
		}
		input := semantic.Request{ObjectID: body.ObjectID, ExpectedLayoutRevision: body.ExpectedLayoutRevision, Operation: body.Operation}
		if err := semantic.ValidateRequest(ctx, input); err != nil {
			fail(err)
			return true
		}

		input.HistoryRelatedLists = historyLayoutProjections(body.ObjectID)

		result, err := service.layoutAuthoring.mutate(ctx, bearer, input)
		if err == nil {
			err = semantic.ValidateResponse(input, result)
		}
		if err != nil {
			fail(err)
			return true
		}
		writeJSON(writer, http.StatusOK, layoutMutationDTO{result.Changed, result.LayoutRevision, result.LayoutID, result.AssignmentID})
	}
	service.successes.Add(1)
	return true
}
