package sfcrmappservice

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

	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	"github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/querycodec"
	crmwire "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/wire"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	listviewsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/listviewv1"
	sessionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/sessionv1"
)

type RuntimeReadPrivateListViewsFunc func(context.Context, string, listviewsemantic.ReadRequest) (listviewsemantic.ReadResponse, error)
type RuntimeMutatePrivateListViewFunc func(context.Context, string, listviewsemantic.MutateRequest) (listviewsemantic.MutateResponse, error)

type RuntimePrivateListViewClient struct {
	read   RuntimeReadPrivateListViewsFunc
	mutate RuntimeMutatePrivateListViewFunc
}

func NewRuntimePrivateListViewClient(read RuntimeReadPrivateListViewsFunc, mutate RuntimeMutatePrivateListViewFunc) (*RuntimePrivateListViewClient, error) {
	if read == nil || mutate == nil {
		return nil, errors.New("sfcrm AppService: Runtime private list-view read and mutation are required")
	}
	return &RuntimePrivateListViewClient{read: read, mutate: mutate}, nil
}

func (c *RuntimePrivateListViewClient) Read(ctx context.Context, bearer string, request listviewsemantic.ReadRequest) (listviewsemantic.ReadResponse, error) {
	if err := listviewsemantic.ValidateReadRequest(ctx, request); err != nil {
		return listviewsemantic.ReadResponse{}, err
	}
	response, err := c.read(ctx, bearer, request)
	if err != nil {
		return listviewsemantic.ReadResponse{}, err
	}
	if err := listviewsemantic.ValidateReadResponse(request, response); err != nil {
		return listviewsemantic.ReadResponse{}, err
	}
	return response, nil
}

func (c *RuntimePrivateListViewClient) Mutate(ctx context.Context, bearer string, request listviewsemantic.MutateRequest) (listviewsemantic.MutateResponse, error) {
	if err := listviewsemantic.ValidateMutateRequest(ctx, request); err != nil {
		return listviewsemantic.MutateResponse{}, err
	}
	response, err := c.mutate(ctx, bearer, request)
	if err != nil {
		return listviewsemantic.MutateResponse{}, err
	}
	if err := listviewsemantic.ValidateMutateResponse(request, response); err != nil {
		return listviewsemantic.MutateResponse{}, err
	}
	return response, nil
}

type privateListViewRoute struct {
	objectID string
	viewID   string
	describe bool
}

func matchPrivateListViewRoute(path string) (privateListViewRoute, bool) {
	parts := strings.Split(strings.Trim(path, "/"), "/")
	if len(parts) < 6 || len(parts) > 8 || parts[0] != "services" || parts[1] != "data" ||
		parts[3] != "sobjects" || parts[5] != "listviews" || !supportedAPIVersion(parts[2]) {
		return privateListViewRoute{}, false
	}
	objectID, err := url.PathUnescape(parts[4])
	if err != nil || !validSearchIdentifier(objectID) {
		return privateListViewRoute{}, false
	}
	route := privateListViewRoute{objectID: objectID}
	if len(parts) == 6 {
		return route, true
	}
	viewID, err := url.PathUnescape(parts[6])
	if err != nil || strings.TrimSpace(viewID) == "" {
		return privateListViewRoute{}, false
	}
	route.viewID = viewID
	if len(parts) == 8 {
		if parts[7] != "describe" {
			return privateListViewRoute{}, false
		}
		route.describe = true
	}
	return route, true
}

type privateListViewPayload struct {
	Label         *string          `json:"Label"`
	ObjectAPIName *string          `json:"ObjectApiName"`
	FilterExpr    *string          `json:"FilterExpr"`
	FilterLogic   *string          `json:"FilterLogic"`
	Fields        *json.RawMessage `json:"Fields"`
	SortField     *string          `json:"SortField"`
	SortDirection *string          `json:"SortDirection"`
	DeveloperName *string          `json:"DeveloperName"`
	Visibility    *string          `json:"Visibility"`
}

func (s *Service) servePrivateListViews(writer http.ResponseWriter, request *http.Request) bool {
	route, ok := matchPrivateListViewRoute(request.URL.EscapedPath())
	if !ok {
		return false
	}
	allowed := []string{http.MethodGet}
	if route.viewID == "" {
		allowed = []string{http.MethodGet, http.MethodPost}
	} else if !route.describe {
		allowed = []string{http.MethodPatch, http.MethodDelete}
	}
	methodAllowed := false
	for _, method := range allowed {
		methodAllowed = methodAllowed || request.Method == method
	}
	if !methodAllowed {
		writer.Header().Set("Allow", strings.Join(allowed, ", "))
		writeCRMError(writer, http.StatusMethodNotAllowed, crmwire.SFCodeInvalidRequest, "Method not allowed")
		return true
	}

	s.requests.Add(1)
	if s.draining.Load() {
		s.drainRejected.Add(1)
		s.failures.Add(1)
		writeCRMError(writer, http.StatusServiceUnavailable, crmwire.SFCodeUnknownException, "Service is draining")
		return true
	}
	s.inFlight.Add(1)
	defer s.inFlight.Add(-1)
	ctx, cancel := context.WithTimeout(request.Context(), s.requestTimeout)
	defer cancel()
	bearer, ok := endUserBearer(request.Header.Values("Authorization"))
	if !ok {
		s.localRejected.Add(1)
		s.failures.Add(1)
		writeCRMError(writer, http.StatusUnauthorized, crmwire.SFCodeInvalidSessionID, "Session expired or invalid")
		return true
	}

	var err error
	switch {
	case request.Method == http.MethodGet && route.viewID == "":
		err = s.listPrivateListViews(ctx, writer, bearer, route.objectID)
	case request.Method == http.MethodGet && route.describe:
		err = s.describePrivateListView(ctx, writer, bearer, route)
	case request.Method == http.MethodPost && route.viewID == "":
		err = s.mutatePrivateListView(ctx, writer, request, bearer, route, listviewsemantic.MutationCreate)
	case request.Method == http.MethodPatch && route.viewID != "":
		err = s.mutatePrivateListView(ctx, writer, request, bearer, route, listviewsemantic.MutationUpdate)
	case request.Method == http.MethodDelete && route.viewID != "":
		err = s.mutatePrivateListView(ctx, writer, request, bearer, route, listviewsemantic.MutationDelete)
	}
	if err != nil {
		s.failures.Add(1)
		writeRuntimeFailure(writer, err)
	} else {
		s.successes.Add(1)
	}
	return true
}

func (s *Service) listPrivateListViews(ctx context.Context, writer http.ResponseWriter, bearer, objectID string) error {
	response, err := s.listViews.Read(ctx, bearer, listviewsemantic.ReadRequest{Selector: listviewsemantic.ReadList, ObjectID: objectID})
	if err != nil {
		return err
	}
	userID, err := s.listViewPrincipalID(ctx, bearer)
	if err != nil {
		return err
	}
	views := standardCRMListViews(objectID, userID)
	for _, view := range response.Views {
		views = append(views, projectCRMListView(view, true))
	}
	writeJSON(writer, http.StatusOK, map[string]any{"listviews": views})
	return nil
}

func (s *Service) describePrivateListView(ctx context.Context, writer http.ResponseWriter, bearer string, route privateListViewRoute) error {
	if standard, ok := standardCRMListView(route.objectID, route.viewID, ""); ok {
		// Standard views are an L1 projection, but their object must still be
		// admitted by the same revision-bound L0 read used for private views.
		if _, err := s.listViews.Read(ctx, bearer, listviewsemantic.ReadRequest{
			Selector: listviewsemantic.ReadList, ObjectID: route.objectID,
		}); err != nil {
			return err
		}
		if route.viewID == route.objectID+"-my-view" {
			userID, err := s.listViewPrincipalID(ctx, bearer)
			if err != nil {
				return err
			}
			standard, _ = standardCRMListView(route.objectID, route.viewID, userID)
		}
		writeJSON(writer, http.StatusOK, describeCRMListView(standard))
		return nil
	}
	response, err := s.listViews.Read(ctx, bearer, listviewsemantic.ReadRequest{
		Selector: listviewsemantic.ReadDescribe, ObjectID: route.objectID, ViewID: route.viewID,
	})
	if err != nil {
		return err
	}
	writeJSON(writer, http.StatusOK, describeCRMListView(projectCRMListView(*response.View, true)))
	return nil
}

func (s *Service) mutatePrivateListView(ctx context.Context, writer http.ResponseWriter, request *http.Request, bearer string, route privateListViewRoute, operation listviewsemantic.MutationOperation) error {
	neutral := listviewsemantic.MutateRequest{Operation: operation, ObjectID: route.objectID, ViewID: route.viewID}
	if operation != listviewsemantic.MutationDelete {
		request.Body = http.MaxBytesReader(writer, request.Body, s.maxRequestBytes)
		var payload privateListViewPayload
		if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
			return &listviewsemantic.Failure{Category: commonsemantic.FailureInvalidRequest}
		}
		if payload.ObjectAPIName != nil && *payload.ObjectAPIName != route.objectID {
			return &listviewsemantic.Failure{Category: commonsemantic.FailureInvalidRequest}
		}
		if payload.Visibility != nil && !strings.EqualFold(strings.TrimSpace(*payload.Visibility), "Private") {
			return &listviewsemantic.Failure{Category: commonsemantic.FailureInvalidRequest}
		}
		patch, err := privateListViewPatch(payload)
		if err != nil {
			return err
		}
		neutral.Patch = patch
	}
	response, err := s.listViews.Mutate(ctx, bearer, neutral)
	if err != nil {
		return err
	}
	if operation == listviewsemantic.MutationCreate {
		writeJSON(writer, http.StatusCreated, map[string]any{"id": response.View.ViewID, "success": true, "errors": []any{}})
	} else {
		writer.WriteHeader(http.StatusNoContent)
	}
	return nil
}

func privateListViewPatch(payload privateListViewPayload) (*listviewsemantic.Patch, error) {
	patch := &listviewsemantic.Patch{
		Label: payload.Label, FilterExpression: payload.FilterExpr, FilterLogic: payload.FilterLogic,
		SortFieldID: payload.SortField, StableName: payload.DeveloperName,
	}
	if payload.Fields != nil {
		fields, err := decodeCRMListViewFields(*payload.Fields)
		if err != nil {
			return nil, err
		}
		patch.Fields = &listviewsemantic.FieldSelection{FieldIDs: fields}
	}
	if payload.SortDirection != nil {
		direction := privateListViewSortDirection(*payload.SortDirection)
		patch.SortDirection = &direction
	}
	return patch, nil
}

func privateListViewSortDirection(value string) listviewsemantic.SortDirection {
	switch strings.ToUpper(strings.TrimSpace(value)) {
	case "ASC", "ASCENDING":
		return listviewsemantic.SortAscending
	case "DESC", "DESCENDING":
		return listviewsemantic.SortDescending
	default:
		return listviewsemantic.SortDirection(strings.ToUpper(strings.TrimSpace(value)))
	}
}

func decodeCRMListViewFields(raw json.RawMessage) ([]string, error) {
	var values []any
	if err := json.Unmarshal(raw, &values); err != nil {
		return nil, &listviewsemantic.Failure{Category: commonsemantic.FailureInvalidRequest}
	}
	fields := make([]string, 0, len(values))
	for _, value := range values {
		name, ok := value.(string)
		if !ok {
			entry, entryOK := value.(map[string]any)
			name, ok = entry["name"].(string)
			if !entryOK || !ok {
				return nil, &listviewsemantic.Failure{Category: commonsemantic.FailureInvalidRequest}
			}
		}
		fields = append(fields, name)
	}
	return fields, nil
}

func projectCRMListView(view listviewsemantic.View, writable bool) map[string]any {
	direction := strings.ToLower(string(view.SortDirection))
	if direction == "ascending" {
		direction = "asc"
	} else if direction == "descending" {
		direction = "desc"
	}
	return map[string]any{
		"id": view.ViewID, "objectApiName": view.ObjectID, "label": view.Label,
		"filterExpr": view.FilterExpression, "filterLogic": view.FilterLogic,
		"fields": view.FieldIDs, "sortField": view.SortFieldID, "sortDirection": direction,
		"developerName": view.StableName, "visibility": "Private", "writable": writable,
	}
}

func standardCRMListViews(objectID, userID string) []map[string]any {
	labels := []struct{ suffix, label, filter string }{
		{"all-view", "All " + objectID, ""}, {"my-view", "My " + objectID, "OwnerId = " + (&querycodec.StringLiteral{Value: userID}).String()},
		{"recent-view", "Recently Viewed", ""},
	}
	views := make([]map[string]any, 0, len(labels))
	for _, item := range labels {
		views = append(views, map[string]any{
			"id": objectID + "-" + item.suffix, "objectApiName": objectID, "label": item.label,
			"filterExpr": item.filter, "fields": []string{}, "writable": false,
		})
	}
	return views
}

func standardCRMListView(objectID, viewID, userID string) (map[string]any, bool) {
	for _, view := range standardCRMListViews(objectID, userID) {
		if view["id"] == viewID {
			return view, true
		}
	}
	return nil, false
}

func describeCRMListView(view map[string]any) map[string]any {
	objectID, _ := view["objectApiName"].(string)
	fields, _ := view["fields"].([]string)
	if len(fields) == 0 {
		fields = []string{"Id"}
	}
	query := "SELECT " + strings.Join(fields, ", ") + " FROM " + objectID
	if filter, _ := view["filterExpr"].(string); strings.TrimSpace(filter) != "" {
		query += " WHERE " + filter
	}
	if sortField, _ := view["sortField"].(string); strings.TrimSpace(sortField) != "" {
		query += " ORDER BY " + sortField
		if direction, _ := view["sortDirection"].(string); strings.TrimSpace(direction) != "" {
			query += " " + strings.ToUpper(direction)
		}
	}
	columns := make([]map[string]any, 0, len(fields))
	for _, field := range fields {
		columns = append(columns, map[string]any{"fieldNameOrPath": field, "label": field, "type": "string"})
	}
	return map[string]any{
		"id": view["id"], "sobjectType": objectID, "query": query, "columns": columns,
	}
}

// listViewPrincipalID reads the Runtime-owned session without rotating credentials.
// Standard CRM views are request-local projections; never persist or cache this ID
// in the shared view definition, and never infer identity from an unverified JWT.
func (s *Service) listViewPrincipalID(ctx context.Context, bearer string) (string, error) {
	if s.sessions == nil || s.sessions.read == nil {
		return "", &sessionsemantic.Failure{Category: sessionsemantic.FailureUnavailable}
	}
	response, _, err := s.sessions.read(ctx, runtimegrpc.SessionPresentedCredentials{AccessToken: bearer}, sessionsemantic.ReadRequest{})
	if err != nil {
		return "", err
	}
	userID := response.Session.Principal.PrincipalID
	if strings.TrimSpace(userID) == "" {
		return "", &sessionsemantic.Failure{Category: sessionsemantic.FailureUnauthenticated}
	}
	return userID, nil
}
