package mapping

import (
	"context"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordaccessv1"
	commonv1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/common/v1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
)

func ToWireEvaluateRecordAccessRequest(ctx context.Context, request semantic.Request) (*runtimev1.EvaluateRecordAccessRequest, error) {
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.EvaluateRecordAccessRequest{
		ObjectId:  cloneStringPointer(request.ObjectID),
		RecordIds: append([]string(nil), request.RecordIDs...),
	}, nil
}

func FromWireEvaluateRecordAccessRequest(ctx context.Context, request *runtimev1.EvaluateRecordAccessRequest) (semantic.Request, error) {
	if request == nil {
		return semantic.Request{}, &semantic.Failure{Category: semantic.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentResponse}
	}
	result := semantic.Request{
		ObjectID:  cloneStringPointer(request.ObjectId),
		RecordIDs: append([]string(nil), request.GetRecordIds()...),
	}
	if err := semantic.ValidateRequest(ctx, result); err != nil {
		return semantic.Request{}, err
	}
	return result, nil
}

func ToWireEvaluateRecordAccessResponse(request semantic.Request, response semantic.Response) (*runtimev1.EvaluateRecordAccessResponse, error) {
	if err := semantic.ValidateResponse(request, response); err != nil {
		return nil, err
	}
	results := make([]*runtimev1.RecordAccessResult, 0, len(response.Results))
	for _, result := range response.Results {
		results = append(results, &runtimev1.RecordAccessResult{
			RecordId: result.RecordID,
			Read:     result.Access.Read,
			Update:   result.Access.Update,
			Delete:   result.Access.Delete,
			Locked:   result.Access.Locked,
		})
	}
	return &runtimev1.EvaluateRecordAccessResponse{
		Results:  results,
		Receipts: recordAccessWireReceipts(response.Receipts),
	}, nil
}

func FromWireEvaluateRecordAccessResponse(request semantic.Request, response *runtimev1.EvaluateRecordAccessResponse) (semantic.Response, error) {
	if response == nil {
		return semantic.Response{}, incompatibleFailureFor(recordAccessCapability)
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), recordAccessCapability)
	if err != nil {
		return semantic.Response{}, err
	}
	result := semantic.Response{Receipts: receipts}
	result.Results = make([]semantic.Result, 0, len(response.GetResults()))
	for _, wireResult := range response.GetResults() {
		if wireResult == nil {
			return semantic.Response{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		result.Results = append(result.Results, semantic.Result{
			RecordID: wireResult.GetRecordId(),
			Access: semantic.Access{
				Read: wireResult.GetRead(), Update: wireResult.GetUpdate(),
				Delete: wireResult.GetDelete(), Locked: wireResult.GetLocked(),
			},
		})
	}
	if err := semantic.ValidateResponse(request, result); err != nil {
		return semantic.Response{}, err
	}
	return result, nil
}

func recordAccessWireReceipts(receipts semantic.Receipts) *commonv1.ConsistencyReceipts {
	return toWireReceipts(receipts, recordAccessCapability)
}

func cloneStringPointer(value *string) *string {
	if value == nil {
		return nil
	}
	copyValue := *value
	return &copyValue
}

func validRecordAccessFailure(failure *semantic.Failure, decoded bool) bool {
	if failure == nil || !semantic.ValidTopLevelFailure(failure.Category) {
		return false
	}
	switch failure.Category {
	case semantic.FailureInvalidRequest:
		if failure.CanonicalField != "" {
			return false
		}
		switch failure.InvalidComponent {
		case semantic.InvalidComponentObjectID, semantic.InvalidComponentRecordID,
			semantic.InvalidComponentDeadline, semantic.InvalidComponentResponse:
			return true
		default:
			return false
		}
	case semantic.FailureResourceExhausted:
		if decoded && failure.LimitKind == "" {
			// gRPC itself may reject an oversized message before a typed
			// capability detail can be attached.
			return true
		}
		switch failure.LimitKind {
		case semantic.LimitObjectID:
			return failure.Maximum == semantic.MaxObjectIdentifierBytes
		case semantic.LimitRecordID:
			return failure.Maximum == semantic.MaxRecordIdentifierBytes
		case semantic.LimitRecordCount:
			return failure.Maximum == semantic.MaxRecords
		case semantic.LimitAdmission:
			return true
		default:
			return false
		}
	case semantic.FailureIncompatible:
		if !decoded {
			// The encoder owns the canonical compatibility detail and does not
			// trust caller-supplied values.
			return true
		}
		return failure.RequiredCapability == semantic.CapabilityName &&
			failure.SupportedMajor == semantic.ProtocolMajor &&
			failure.SupportedMinorMin <= semantic.ProtocolMinor &&
			failure.SupportedMinorMax >= semantic.ProtocolMinor
	case semantic.FailureStaleState:
		return failure.StaleAxis == semantic.StaleAxisCatalog || failure.StaleAxis == semantic.StaleAxisSecurity
	default:
		return true
	}
}
