package runtimeapi

import (
	"context"
	"errors"
	"math"
	"sort"

	"github.com/nexuscrm/backend/internal/layoutauthoring"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv2"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
)

type LayoutAuthoringV2Engine interface {
	ReadWorkspace(context.Context, string) (metadata.LayoutAuthoringAggregateSnapshot, error)
	ReadStoredLayout(context.Context, string, string) (*models.PageLayout, metadata.LayoutAuthoringAggregateSnapshot, error)
	Mutate(context.Context, models.UserSession, permissionstatev1.Version, semantic.Request) (semantic.Response, error)
}

type LayoutAuthoringV2Server struct {
	runtimev1.UnimplementedLayoutAuthoringV2ServiceServer
	engine      LayoutAuthoringV2Engine
	permissions EffectivePermissionEngine
}

func NewLayoutAuthoringV2Server(engine LayoutAuthoringV2Engine, permissions EffectivePermissionEngine) (*LayoutAuthoringV2Server, error) {
	if engine == nil || permissions == nil {
		return nil, errors.New("Layout authoring v2 requires mutation and permission engines")
	}
	return &LayoutAuthoringV2Server{engine: engine, permissions: permissions}, nil
}

func (server *LayoutAuthoringV2Server) ReadWorkspace(ctx context.Context, wireRequest *runtimev1.LayoutAuthoringV2ServiceReadWorkspaceRequest) (*runtimev1.LayoutAuthoringV2ServiceReadWorkspaceResponse, error) {
	request, err := mapping.FromWireLayoutAuthoringV2ReadWorkspaceRequest(ctx, wireRequest)
	if err != nil {
		return nil, layoutAuthoringV2Status(err)
	}
	receipts, _, err := server.authorizeRead(ctx)
	if err != nil {
		return nil, layoutAuthoringV2Status(err)
	}
	snapshot, err := server.engine.ReadWorkspace(ctx, request.ObjectID)
	if err != nil {
		return nil, layoutAuthoringV2Status(classifyLayoutAuthoringV2Error(ctx, err))
	}
	response, err := mapping.ToWireLayoutAuthoringV2ReadWorkspaceResponse(request, semantic.WorkspaceResponse{
		ObjectID: request.ObjectID, LayoutRevision: snapshot.Versions.WorkspaceVersion,
		Layouts: projectLayoutSummaries(snapshot.Layouts), Assignments: projectLayoutAssignments(snapshot.Assignments), Receipts: receipts,
	})
	if err != nil {
		return nil, layoutAuthoringV2Status(err)
	}
	return response, nil
}

func (server *LayoutAuthoringV2Server) ReadStoredLayout(ctx context.Context, wireRequest *runtimev1.LayoutAuthoringV2ServiceReadStoredLayoutRequest) (*runtimev1.LayoutAuthoringV2ServiceReadStoredLayoutResponse, error) {
	request, err := mapping.FromWireLayoutAuthoringV2ReadStoredLayoutRequest(ctx, wireRequest)
	if err != nil {
		return nil, layoutAuthoringV2Status(err)
	}
	receipts, _, err := server.authorizeRead(ctx)
	if err != nil {
		return nil, layoutAuthoringV2Status(err)
	}
	layout, snapshot, err := server.engine.ReadStoredLayout(ctx, request.ObjectID, request.LayoutID)
	if err != nil {
		return nil, layoutAuthoringV2Status(classifyLayoutAuthoringV2Error(ctx, err))
	}
	document, err := layoutauthoring.DocumentFromPageLayout(*layout)
	if err != nil {
		return nil, layoutAuthoringV2Status(&semantic.Failure{Category: semantic.FailureInternal})
	}
	response, err := mapping.ToWireLayoutAuthoringV2ReadStoredLayoutResponse(request, semantic.StoredLayoutResponse{
		LayoutID: layout.ID, ObjectID: layout.ObjectAPIName, LayoutRevision: snapshot.Versions.WorkspaceVersion,
		Document: document, OrderedListFields: append([]string(nil), snapshot.OrderedListFields...),
		RequiredCreateFields: append([]string(nil), snapshot.RequiredCreateFields...), Receipts: receipts,
	})
	if err != nil {
		return nil, layoutAuthoringV2Status(err)
	}
	return response, nil
}

func (server *LayoutAuthoringV2Server) Mutate(ctx context.Context, wireRequest *runtimev1.LayoutAuthoringV2ServiceMutateRequest) (*runtimev1.LayoutAuthoringV2ServiceMutateResponse, error) {
	request, err := mapping.FromWireLayoutAuthoringV2MutateRequest(ctx, wireRequest)
	if err != nil {
		return nil, layoutAuthoringV2Status(err)
	}
	receipts, trusted, err := layoutAuthoringV2Authority(ctx)
	if err != nil {
		return nil, layoutAuthoringV2Status(err)
	}
	result, err := server.engine.Mutate(ctx, trusted.UserSession(), trusted.PermissionFloor(), request)
	if err != nil {
		return nil, layoutAuthoringV2Status(classifyLayoutAuthoringV2Error(ctx, err))
	}
	result.Receipts = receipts
	response, err := mapping.ToWireLayoutAuthoringV2MutateResponse(request, result)
	if err != nil {
		return nil, layoutAuthoringV2Status(err)
	}
	return response, nil
}

func layoutAuthoringV2Authority(ctx context.Context) (semantic.Receipts, TrustedCall, error) {
	capability, current := runtimegrpc.Capability(ctx)
	trusted, ok := trustedCallFromContext(ctx)
	if !current || capability != semantic.CapabilityName || !ok || !containsCapability(trusted.capabilities, semantic.CapabilityName) {
		return semantic.Receipts{}, TrustedCall{}, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	return semantic.Receipts{ProtocolVersion: "1.0", SecurityEpoch: trusted.securityEpoch, CapabilityVersion: semantic.CapabilityName}, trusted, nil
}

func (server *LayoutAuthoringV2Server) authorizeRead(ctx context.Context) (semantic.Receipts, TrustedCall, error) {
	receipts, trusted, err := layoutAuthoringV2Authority(ctx)
	if err != nil {
		return semantic.Receipts{}, TrustedCall{}, err
	}
	user := trusted.UserSession()
	result, err := server.permissions.EffectivePermissions(ctx, &user, "", trusted.PermissionFloor())
	if err != nil {
		return semantic.Receipts{}, TrustedCall{}, classifyEffectivePermissionError(ctx, err)
	}
	if result == nil {
		return semantic.Receipts{}, TrustedCall{}, &semantic.Failure{Category: semantic.FailureInternal}
	}
	if !result.SystemPermissions[string(constants.PermCustomizeApplication)] {
		return semantic.Receipts{}, TrustedCall{}, &semantic.Failure{Category: semantic.FailureForbidden}
	}
	return receipts, trusted, nil
}

func projectLayoutSummaries(layouts []*models.PageLayout) []semantic.LayoutSummary {
	result := make([]semantic.LayoutSummary, 0, len(layouts))
	for _, layout := range layouts {
		if layout == nil {
			continue
		}
		result = append(result, semantic.LayoutSummary{LayoutID: layout.ID, ObjectID: layout.ObjectAPIName, Name: layout.LayoutName, Type: layout.Type, IsDefault: layout.IsDefault, CreatedAt: metadataAdminReadTimestamp(layout.CreatedDate), LastModifiedAt: metadataAdminReadTimestamp(layout.LastModifiedDate)})
	}
	sort.Slice(result, func(i, j int) bool { return result[i].LayoutID < result[j].LayoutID })
	return result
}

func projectLayoutAssignments(assignments []*models.LayoutAssignment) []semantic.LayoutAssignment {
	result := make([]semantic.LayoutAssignment, 0, len(assignments))
	for _, assignment := range assignments {
		if assignment == nil || assignment.Specificity < 0 || uint64(assignment.Specificity) > math.MaxUint32 {
			continue
		}
		result = append(result, semantic.LayoutAssignment{AssignmentID: assignment.ID, ObjectID: assignment.ObjectAPIName, LayoutID: assignment.LayoutID, Dimensions: metadataAdminReadAssignmentDimensions(assignment), Specificity: uint32(assignment.Specificity), CreatedAt: metadataAdminReadTimestamp(assignment.CreatedDate), LastModifiedAt: metadataAdminReadTimestamp(assignment.LastModifiedDate), CreatedByID: cloneMetadataAdminString(assignment.CreatedByID)})
	}
	sort.Slice(result, func(i, j int) bool { return result[i].AssignmentID < result[j].AssignmentID })
	return result
}

func classifyLayoutAuthoringV2Error(ctx context.Context, err error) error {
	return classifyPlatformEngineError(ctx, err, "OPERATION")
}
func layoutAuthoringV2Status(err error) error {
	return mapping.ToPlatformStatusError(err, semantic.CapabilityName)
}
