package mapping

import (
	"context"

	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/resourceaccessv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/protobuf/proto"
)

func ToWireResourceAccessRequest(ctx context.Context, request semantic.Request) (*runtimev1.AccessResourceRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, err
	}
	operation, ok := toWireResourceAccessOperation(request.Operation)
	if !ok {
		return nil, invalidPlatformWire()
	}
	result := &runtimev1.AccessResourceRequest{CommandId: request.CommandID, Operation: operation, ResourceType: request.ResourceType, ResourceId: request.ResourceID, GrantId: request.GrantID, AccessLevel: request.AccessLevel, PrincipalType: request.PrincipalType, SourceOwnerId: request.SourceOwnerID, TargetOwnerId: request.TargetOwnerID, MinCatalogRevision: request.MinCatalogRevision}
	if request.Grant != nil {
		result.Grant = toWireResourceAccessGrantInput(*request.Grant)
	}
	for _, grant := range request.Grants {
		result.Grants = append(result.Grants, toWireResourceAccessGrantInput(grant))
	}
	return result, nil
}
func FromWireResourceAccessRequest(ctx context.Context, request *runtimev1.AccessResourceRequest) (semantic.Request, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.Request{}, err
	}
	if request == nil {
		return semantic.Request{}, invalidPlatformWire()
	}
	operation, ok := fromWireResourceAccessOperation(request.GetOperation())
	if !ok {
		return semantic.Request{}, invalidPlatformWire()
	}
	if err := commonsemantic.ValidateRequestContext(ctx); err != nil {
		return semantic.Request{}, err
	}
	if err := preflightWireResourceAccessRequest(request); err != nil {
		return semantic.Request{}, err
	}
	result := semantic.Request{CommandID: request.GetCommandId(), Operation: operation, ResourceType: request.GetResourceType(), ResourceID: request.GetResourceId(), GrantID: request.GetGrantId(), AccessLevel: request.GetAccessLevel(), PrincipalType: request.GetPrincipalType(), SourceOwnerID: request.GetSourceOwnerId(), TargetOwnerID: request.GetTargetOwnerId(), MinCatalogRevision: request.MinCatalogRevision}
	if request.GetGrant() != nil {
		grant := fromWireResourceAccessGrantInput(request.GetGrant())
		result.Grant = &grant
	}
	for _, grant := range request.GetGrants() {
		if grant == nil {
			return semantic.Request{}, invalidPlatformWire()
		}
		result.Grants = append(result.Grants, fromWireResourceAccessGrantInput(grant))
	}
	if err := semantic.ValidateRequest(ctx, result); err != nil {
		return semantic.Request{}, err
	}
	return result, nil
}
func ToWireResourceAccessResponse(request semantic.Request, response semantic.Response) (*runtimev1.AccessResourceResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateResponse(request, response); err != nil {
		return nil, err
	}
	result := &runtimev1.AccessResourceResponse{CommandId: response.CommandID, AffectedResources: response.AffectedResources, Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName))}
	for _, grant := range response.Grants {
		result.Grants = append(result.Grants, &runtimev1.ResourceAccessGrant{Id: grant.ID, ResourceType: grant.ResourceType, ResourceId: grant.ResourceID, PrincipalId: grant.PrincipalID, PrincipalType: grant.PrincipalType, AccessLevel: grant.AccessLevel})
	}
	return result, nil
}
func FromWireResourceAccessResponse(request semantic.Request, response *runtimev1.AccessResourceResponse) (semantic.Response, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.Response{}, err
	}
	if response == nil || response.GetReceipts() == nil {
		return semantic.Response{}, incompatibleFailureFor(platformCapability(semantic.CapabilityName))
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.Response{}, err
	}
	if err := preflightWireResourceAccessResponse(response); err != nil {
		return semantic.Response{}, err
	}
	result := semantic.Response{CommandID: response.GetCommandId(), AffectedResources: response.GetAffectedResources(), Receipts: receipts}
	for _, grant := range response.GetGrants() {
		if grant == nil {
			return semantic.Response{}, internalPlatformWire()
		}
		result.Grants = append(result.Grants, semantic.Grant{ID: grant.GetId(), ResourceType: grant.GetResourceType(), ResourceID: grant.GetResourceId(), PrincipalID: grant.GetPrincipalId(), PrincipalType: grant.GetPrincipalType(), AccessLevel: grant.GetAccessLevel()})
	}
	if err := semantic.ValidateResponse(request, result); err != nil {
		return semantic.Response{}, err
	}
	return result, nil
}
func preflightWireResourceAccessRequest(request *runtimev1.AccessResourceRequest) error {
	if len(request.GetGrants()) > semantic.MaxGrants {
		return platformLimitFailure(commonsemantic.LimitOperationCount, semantic.MaxGrants)
	}
	budget := newWireStringBudget(semantic.MaxSemanticRequestBytes)
	if !budget.take(
		request.GetCommandId(), request.GetResourceType(), request.GetResourceId(), request.GetGrantId(),
		request.GetAccessLevel(), request.GetPrincipalType(), request.GetSourceOwnerId(), request.GetTargetOwnerId(),
	) {
		return platformLimitFailure(commonsemantic.LimitSemanticRequest, semantic.MaxSemanticRequestBytes)
	}
	if grant := request.GetGrant(); grant != nil && !budget.take(grant.GetPrincipalId(), grant.GetPrincipalType(), grant.GetAccessLevel()) {
		return platformLimitFailure(commonsemantic.LimitSemanticRequest, semantic.MaxSemanticRequestBytes)
	}
	for _, grant := range request.GetGrants() {
		if grant == nil {
			return invalidPlatformWire()
		}
		if !budget.take(grant.GetPrincipalId(), grant.GetPrincipalType(), grant.GetAccessLevel()) {
			return platformLimitFailure(commonsemantic.LimitSemanticRequest, semantic.MaxSemanticRequestBytes)
		}
	}
	if proto.Size(request) > envelope.MaxRequestWireBytes {
		return platformLimitFailure(commonsemantic.LimitWireFrame, envelope.MaxRequestWireBytes)
	}
	return nil
}

func preflightWireResourceAccessResponse(response *runtimev1.AccessResourceResponse) error {
	if len(response.GetGrants()) > semantic.MaxGrants {
		return internalPlatformWire()
	}
	budget := newWireStringBudget(semantic.MaxSemanticResponseBytes)
	if !budget.take(response.GetCommandId()) || !budget.takeValidatedReceiptBytes(response.GetReceipts()) {
		return internalPlatformWire()
	}
	for _, grant := range response.GetGrants() {
		if grant == nil || !budget.take(
			grant.GetId(), grant.GetResourceType(), grant.GetResourceId(),
			grant.GetPrincipalId(), grant.GetPrincipalType(), grant.GetAccessLevel(),
		) {
			return internalPlatformWire()
		}
	}
	if proto.Size(response) > envelope.MaxResponseWireBytes {
		return internalPlatformWire()
	}
	return nil
}

func toWireResourceAccessGrantInput(value semantic.GrantInput) *runtimev1.ResourceAccessGrantInput {
	return &runtimev1.ResourceAccessGrantInput{PrincipalId: value.PrincipalID, PrincipalType: value.PrincipalType, AccessLevel: value.AccessLevel}
}
func fromWireResourceAccessGrantInput(value *runtimev1.ResourceAccessGrantInput) semantic.GrantInput {
	return semantic.GrantInput{PrincipalID: value.GetPrincipalId(), PrincipalType: value.GetPrincipalType(), AccessLevel: value.GetAccessLevel()}
}
func toWireResourceAccessOperation(value semantic.Operation) (runtimev1.ResourceAccessOperation, bool) {
	result, ok := map[semantic.Operation]runtimev1.ResourceAccessOperation{semantic.ListGrants: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_LIST_GRANTS, semantic.GetGrant: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_GET_GRANT, semantic.CreateGrant: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_CREATE_GRANT, semantic.EnsureGrant: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_ENSURE_GRANT, semantic.ReplaceGrants: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_REPLACE_GRANTS, semantic.UpdateGrant: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_UPDATE_GRANT, semantic.DeleteGrant: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_DELETE_GRANT, semantic.DeletePrincipalType: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_DELETE_PRINCIPAL_TYPE, semantic.TransferOwnership: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_TRANSFER_OWNERSHIP}[value]
	return result, ok
}
func fromWireResourceAccessOperation(value runtimev1.ResourceAccessOperation) (semantic.Operation, bool) {
	for semanticValue, wireValue := range map[semantic.Operation]runtimev1.ResourceAccessOperation{semantic.ListGrants: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_LIST_GRANTS, semantic.GetGrant: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_GET_GRANT, semantic.CreateGrant: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_CREATE_GRANT, semantic.EnsureGrant: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_ENSURE_GRANT, semantic.ReplaceGrants: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_REPLACE_GRANTS, semantic.UpdateGrant: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_UPDATE_GRANT, semantic.DeleteGrant: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_DELETE_GRANT, semantic.DeletePrincipalType: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_DELETE_PRINCIPAL_TYPE, semantic.TransferOwnership: runtimev1.ResourceAccessOperation_RESOURCE_ACCESS_OPERATION_TRANSFER_OWNERSHIP} {
		if wireValue == value {
			return semanticValue, true
		}
	}
	return "", false
}
