package runtimeapi

import (
	"context"
	"errors"

	"github.com/nexuscrm/backend/internal/runtimeassembly"
	"github.com/nexuscrm/persistence"
	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationnavigationv1"
	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
)

type ApplicationNavigationEngine interface {
	Read(context.Context, runtimeassembly.NavigationCaller, semantic.ReadRequest) (semantic.Document, error)
	Resolve(context.Context, runtimeassembly.NavigationCaller, semantic.ResolveRequest) ([]semantic.Document, error)
	Save(context.Context, runtimeassembly.NavigationCaller, semantic.SaveRequest) (persistence.NavigationMutationResult, error)
}
type ApplicationNavigationServer struct {
	runtimev1.UnimplementedApplicationNavigationServiceServer
	engine ApplicationNavigationEngine
}

func NewApplicationNavigationServer(engine ApplicationNavigationEngine) (*ApplicationNavigationServer, error) {
	if engine == nil {
		return nil, errors.New("navigation Runtime engine is required")
	}
	return &ApplicationNavigationServer{engine: engine}, nil
}
func applicationNavigationAuthority(ctx context.Context) (runtimeassembly.NavigationCaller, common.Receipts, error) {
	capability, present := runtimegrpc.Capability(ctx)
	trusted, ok := trustedCallFromContext(ctx)
	owner, _ := ctx.Value(applicationNavigationOwnerKey{}).(string)
	if !present || capability != semantic.CapabilityName || !ok || owner == "" || !containsCapability(trusted.capabilities, semantic.CapabilityName) {
		return runtimeassembly.NavigationCaller{}, common.Receipts{}, &common.Failure{Category: common.FailureUnavailable}
	}
	caller := runtimeassembly.NavigationCaller{OwnerID: owner, Actor: trusted.UserSession(), PermissionFloor: trusted.PermissionFloor()}
	// Current navigation never claims to have published or served a Catalog revision.
	return caller, common.Receipts{ProtocolVersion: "1.0", SecurityEpoch: trusted.securityEpoch, CapabilityVersion: semantic.CapabilityName}, nil
}
func (s *ApplicationNavigationServer) Read(ctx context.Context, wire *runtimev1.ApplicationNavigationServiceReadRequest) (*runtimev1.ApplicationNavigationServiceReadResponse, error) {
	request, err := mapping.FromWireNavigationReadRequest(ctx, wire)
	if err != nil {
		return nil, applicationNavigationStatus(ctx, err)
	}
	caller, receipts, err := applicationNavigationAuthority(ctx)
	if err != nil {
		return nil, applicationNavigationStatus(ctx, err)
	}
	document, err := s.engine.Read(ctx, caller, request)
	if err != nil {
		return nil, applicationNavigationStatus(ctx, err)
	}
	response, err := mapping.ToWireNavigationReadResponse(request, semantic.ReadResponse{Document: document, Receipts: receipts})
	return response, applicationNavigationStatus(ctx, err)
}
func (s *ApplicationNavigationServer) Resolve(ctx context.Context, wire *runtimev1.ApplicationNavigationServiceResolveRequest) (*runtimev1.ApplicationNavigationServiceResolveResponse, error) {
	request, err := mapping.FromWireNavigationResolveRequest(ctx, wire)
	if err != nil {
		return nil, applicationNavigationStatus(ctx, err)
	}
	caller, receipts, err := applicationNavigationAuthority(ctx)
	if err != nil {
		return nil, applicationNavigationStatus(ctx, err)
	}
	documents, err := s.engine.Resolve(ctx, caller, request)
	if err != nil {
		return nil, applicationNavigationStatus(ctx, err)
	}
	response, err := mapping.ToWireNavigationResolveResponse(request, semantic.ResolveResponse{Documents: documents, Receipts: receipts})
	return response, applicationNavigationStatus(ctx, err)
}
func (s *ApplicationNavigationServer) Save(ctx context.Context, wire *runtimev1.ApplicationNavigationServiceSaveRequest) (*runtimev1.ApplicationNavigationServiceSaveResponse, error) {
	request, err := mapping.FromWireNavigationSaveRequest(ctx, wire)
	if err != nil {
		return nil, applicationNavigationStatus(ctx, err)
	}
	caller, receipts, err := applicationNavigationAuthority(ctx)
	if err != nil {
		return nil, applicationNavigationStatus(ctx, err)
	}
	result, err := s.engine.Save(ctx, caller, request)
	if err != nil {
		return nil, applicationNavigationStatus(ctx, err)
	}
	response, err := mapping.ToWireNavigationSaveResponse(request, semantic.SaveResponse{Document: result.Document, Changed: result.Changed, Receipts: receipts})
	return response, applicationNavigationStatus(ctx, err)
}
func applicationNavigationStatus(ctx context.Context, err error) error {
	if err == nil {
		return nil
	}
	switch {
	case errors.Is(err, persistence.ErrNavigationConflict), errors.Is(err, persistence.ErrNavigationRequestConflict):
		err = &common.Failure{Category: common.FailureConflict}
	case errors.Is(err, persistence.ErrNavigationMissing):
		err = &common.Failure{Category: common.FailureForbidden}
	case errors.Is(err, persistence.ErrNavigationUninitialized):
		err = &common.Failure{Category: common.FailureUnavailable}
	default:
		err = classifyEffectivePermissionError(ctx, err)
	}
	return mapping.ToPlatformStatusError(err, semantic.CapabilityName)
}
