package mapping

import (
	"context"

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

func ToWirePreviewCreateDefaultsRequest(
	ctx context.Context,
	request semantic.Request,
) (*runtimev1.PreviewCreateDefaultsRequest, error) {
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.PreviewCreateDefaultsRequest{
		ObjectId: request.ObjectID, ParentFieldId: cloneStringPointer(request.ParentFieldID),
		ParentRecordId:     cloneStringPointer(request.ParentRecordID),
		MinCatalogRevision: request.MinCatalogRevision,
	}, nil
}

func FromWirePreviewCreateDefaultsRequest(
	ctx context.Context,
	request *runtimev1.PreviewCreateDefaultsRequest,
) (semantic.Request, error) {
	if request == nil {
		return semantic.Request{}, &semantic.Failure{
			Category:         semantic.FailureInvalidRequest,
			InvalidComponent: semantic.InvalidComponentResponse,
		}
	}
	result := semantic.Request{
		ObjectID:           request.GetObjectId(),
		ParentFieldID:      cloneStringPointer(request.ParentFieldId),
		ParentRecordID:     cloneStringPointer(request.ParentRecordId),
		MinCatalogRevision: request.MinCatalogRevision,
	}
	if err := semantic.ValidateRequest(ctx, result); err != nil {
		return semantic.Request{}, err
	}
	return result, nil
}

func ToWirePreviewCreateDefaultsResponse(
	request semantic.Request,
	response semantic.Response,
) (*runtimev1.PreviewCreateDefaultsResponse, error) {
	if err := semantic.ValidateResponse(request, response); err != nil {
		return nil, err
	}
	result := &runtimev1.PreviewCreateDefaultsResponse{
		Fields:   make([]*runtimev1.CreateDefaultField, 0, len(response.Fields)),
		Receipts: toWireReceipts(response.Receipts, createDefaultPreviewCapability),
	}
	for _, field := range response.Fields {
		wireField := &runtimev1.CreateDefaultField{
			Name: field.Name, Kind: toWireCreateDefaultValueKind(field.Kind),
			DisplayValue: cloneStringPointer(field.DisplayValue),
			Provenance:   toWireCreateDefaultProvenance(field.Provenance),
		}
		if field.Integer != nil {
			wireField.Value = &runtimev1.CreateDefaultField_IntegerValue{IntegerValue: *field.Integer}
		} else {
			wireField.Value = &runtimev1.CreateDefaultField_ReferenceValue{ReferenceValue: *field.Reference}
		}
		result.Fields = append(result.Fields, wireField)
	}
	return result, nil
}

func FromWirePreviewCreateDefaultsResponse(
	request semantic.Request,
	response *runtimev1.PreviewCreateDefaultsResponse,
) (semantic.Response, error) {
	if response == nil {
		return semantic.Response{}, incompatibleFailureFor(createDefaultPreviewCapability)
	}
	if len(response.GetFields()) > semantic.MaxDefaultFields {
		return semantic.Response{}, &semantic.Failure{
			Category:  semantic.FailureResourceExhausted,
			LimitKind: semantic.LimitFieldCount,
			Maximum:   semantic.MaxDefaultFields,
		}
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), createDefaultPreviewCapability)
	if err != nil {
		return semantic.Response{}, err
	}
	result := semantic.Response{
		Fields: make([]semantic.Field, 0, len(response.GetFields())), Receipts: receipts,
	}
	for _, wireField := range response.GetFields() {
		if wireField == nil {
			return semantic.Response{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		kind, ok := fromWireCreateDefaultValueKind(wireField.GetKind())
		if !ok {
			return semantic.Response{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		provenance, ok := fromWireCreateDefaultProvenance(wireField.GetProvenance())
		if !ok {
			return semantic.Response{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		field := semantic.Field{
			Name: wireField.GetName(), Kind: kind,
			DisplayValue: cloneStringPointer(wireField.DisplayValue), Provenance: provenance,
		}
		switch value := wireField.GetValue().(type) {
		case *runtimev1.CreateDefaultField_IntegerValue:
			field.Integer = &value.IntegerValue
		case *runtimev1.CreateDefaultField_ReferenceValue:
			field.Reference = &value.ReferenceValue
		default:
			return semantic.Response{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		result.Fields = append(result.Fields, field)
	}
	if err := semantic.ValidateResponse(request, result); err != nil {
		return semantic.Response{}, err
	}
	return result, nil
}

func toWireCreateDefaultValueKind(kind semantic.ValueKind) runtimev1.CreateDefaultValueKind {
	switch kind {
	case semantic.ValueInteger:
		return runtimev1.CreateDefaultValueKind_CREATE_DEFAULT_VALUE_KIND_INTEGER
	case semantic.ValueReference:
		return runtimev1.CreateDefaultValueKind_CREATE_DEFAULT_VALUE_KIND_REFERENCE
	default:
		return runtimev1.CreateDefaultValueKind_CREATE_DEFAULT_VALUE_KIND_UNSPECIFIED
	}
}

func fromWireCreateDefaultValueKind(kind runtimev1.CreateDefaultValueKind) (semantic.ValueKind, bool) {
	switch kind {
	case runtimev1.CreateDefaultValueKind_CREATE_DEFAULT_VALUE_KIND_INTEGER:
		return semantic.ValueInteger, true
	case runtimev1.CreateDefaultValueKind_CREATE_DEFAULT_VALUE_KIND_REFERENCE:
		return semantic.ValueReference, true
	default:
		return "", false
	}
}

func toWireCreateDefaultProvenance(provenance semantic.Provenance) runtimev1.CreateDefaultProvenance {
	switch provenance {
	case semantic.ProvenanceSiblingMaxPlusOne:
		return runtimev1.CreateDefaultProvenance_CREATE_DEFAULT_PROVENANCE_SIBLING_MAX_PLUS_ONE
	case semantic.ProvenanceCurrentPrincipal:
		return runtimev1.CreateDefaultProvenance_CREATE_DEFAULT_PROVENANCE_CURRENT_PRINCIPAL
	default:
		return runtimev1.CreateDefaultProvenance_CREATE_DEFAULT_PROVENANCE_UNSPECIFIED
	}
}

func fromWireCreateDefaultProvenance(provenance runtimev1.CreateDefaultProvenance) (semantic.Provenance, bool) {
	switch provenance {
	case runtimev1.CreateDefaultProvenance_CREATE_DEFAULT_PROVENANCE_SIBLING_MAX_PLUS_ONE:
		return semantic.ProvenanceSiblingMaxPlusOne, true
	case runtimev1.CreateDefaultProvenance_CREATE_DEFAULT_PROVENANCE_CURRENT_PRINCIPAL:
		return semantic.ProvenanceCurrentPrincipal, true
	default:
		return "", false
	}
}
