package mapping

import (
	"context"

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

func ToWireResolvePageLayoutRequest(ctx context.Context, request semantic.Request) (*runtimev1.ResolvePageLayoutRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.ResolvePageLayoutRequest{
		ObjectId: request.ObjectID, RecordTypeIds: append([]string(nil), request.RecordTypeIDs...),
		MinCatalogRevision: request.MinCatalogRevision,
	}, nil
}

func FromWireResolvePageLayoutRequest(ctx context.Context, request *runtimev1.ResolvePageLayoutRequest) (semantic.Request, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.Request{}, err
	}
	if request == nil {
		return semantic.Request{}, invalidPlatformWire()
	}
	mapped := semantic.Request{
		ObjectID: request.GetObjectId(), RecordTypeIDs: append([]string(nil), request.GetRecordTypeIds()...),
		MinCatalogRevision: request.MinCatalogRevision,
	}
	if err := semantic.ValidateRequest(ctx, mapped); err != nil {
		return semantic.Request{}, err
	}
	return mapped, nil
}

func ToWireReadObjectListFieldsRequest(
	ctx context.Context,
	request semantic.ObjectListFieldsRequest,
) (*runtimev1.ReadObjectListFieldsRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateObjectListFieldsRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.ReadObjectListFieldsRequest{
		ObjectIds:          append([]string(nil), request.ObjectIDs...),
		MinCatalogRevision: request.MinCatalogRevision,
	}, nil
}

func FromWireReadObjectListFieldsRequest(
	ctx context.Context,
	request *runtimev1.ReadObjectListFieldsRequest,
) (semantic.ObjectListFieldsRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.ObjectListFieldsRequest{}, err
	}
	if request == nil {
		return semantic.ObjectListFieldsRequest{}, invalidPlatformWire()
	}
	mapped := semantic.ObjectListFieldsRequest{
		ObjectIDs:          request.GetObjectIds(),
		MinCatalogRevision: request.MinCatalogRevision,
	}
	if err := semantic.ValidateObjectListFieldsRequest(ctx, mapped); err != nil {
		return semantic.ObjectListFieldsRequest{}, err
	}
	mapped.ObjectIDs = append([]string(nil), mapped.ObjectIDs...)
	return mapped, nil
}

func ToWireReadObjectListFieldsResponse(
	request semantic.ObjectListFieldsRequest,
	response semantic.ObjectListFieldsResponse,
) (*runtimev1.ReadObjectListFieldsResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateObjectListFieldsResponse(request, response); err != nil {
		return nil, err
	}
	objects := make([]*runtimev1.ObjectListFields, 0, len(response.Objects))
	for _, object := range response.Objects {
		objects = append(objects, &runtimev1.ObjectListFields{
			ObjectId: object.ObjectID,
			OrderedFieldIds: &runtimev1.ObjectListFieldIDs{
				Values: append([]string(nil), object.OrderedFieldIDs...),
			},
		})
	}
	return &runtimev1.ReadObjectListFieldsResponse{
		Objects:  objects,
		Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName)),
	}, nil
}

func FromWireReadObjectListFieldsResponse(
	request semantic.ObjectListFieldsRequest,
	response *runtimev1.ReadObjectListFieldsResponse,
) (semantic.ObjectListFieldsResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.ObjectListFieldsResponse{}, err
	}
	if response == nil || response.GetReceipts() == nil {
		return semantic.ObjectListFieldsResponse{}, incompatibleFailureFor(platformCapability(semantic.CapabilityName))
	}
	if len(response.GetObjects()) > semantic.MaxObjectSelectors {
		return semantic.ObjectListFieldsResponse{}, &semantic.Failure{Category: semantic.FailureInternal}
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.ObjectListFieldsResponse{}, err
	}
	mapped := semantic.ObjectListFieldsResponse{
		Objects:  make([]semantic.ObjectListFields, 0, len(response.GetObjects())),
		Receipts: receipts,
	}
	remainingFields := semantic.MaxResolutionProjectionEntries
	for _, object := range response.GetObjects() {
		if object == nil || object.GetOrderedFieldIds() == nil {
			return semantic.ObjectListFieldsResponse{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		orderedFieldIDs := object.GetOrderedFieldIds().GetValues()
		if len(orderedFieldIDs) > semantic.MaxListFieldsPerObject {
			return semantic.ObjectListFieldsResponse{}, &semantic.Failure{
				Category: semantic.FailureResourceExhausted, LimitKind: semantic.LimitFieldCount,
				Maximum: semantic.MaxListFieldsPerObject,
			}
		}
		if len(orderedFieldIDs) > remainingFields {
			return semantic.ObjectListFieldsResponse{}, &semantic.Failure{
				Category: semantic.FailureResourceExhausted, LimitKind: semantic.LimitFieldCount,
				Maximum: semantic.MaxResolutionProjectionEntries,
			}
		}
		remainingFields -= len(orderedFieldIDs)
		clonedFieldIDs := make([]string, len(orderedFieldIDs))
		copy(clonedFieldIDs, orderedFieldIDs)
		mapped.Objects = append(mapped.Objects, semantic.ObjectListFields{
			ObjectID: object.GetObjectId(), OrderedFieldIDs: clonedFieldIDs,
		})
	}
	if err := semantic.ValidateObjectListFieldsResponse(request, mapped); err != nil {
		return semantic.ObjectListFieldsResponse{}, err
	}
	return mapped, nil
}

func ToWireResolvePageLayoutResponse(request semantic.Request, response semantic.Response) (*runtimev1.ResolvePageLayoutResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateResponseForRequest(request, response); err != nil {
		return nil, err
	}
	selections := make([]*runtimev1.ResolvedPageLayoutSelection, 0, len(response.Selections))
	for _, value := range response.Selections {
		selections = append(selections, &runtimev1.ResolvedPageLayoutSelection{
			RecordTypeId: value.RecordTypeID, LayoutId: value.LayoutID,
		})
	}
	layouts := make([]*runtimev1.ResolvedPageLayout, 0, len(response.Layouts))
	for _, value := range response.Layouts {
		layouts = append(layouts, toWireResolvedPageLayout(value))
	}
	return &runtimev1.ResolvePageLayoutResponse{
		ObjectId: response.ObjectID, Selections: selections, Layouts: layouts,
		Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName)),
	}, nil
}

func toWireResolvedPageLayout(value semantic.ResolvedLayout) *runtimev1.ResolvedPageLayout {
	related := make([]*runtimev1.ResolvedRelatedList, 0, len(value.RelatedLists))
	for _, resolution := range value.RelatedLists {
		mappings := make([]*runtimev1.ResolvedRelatedFieldMapping, 0, len(resolution.Create.PrefillMappings))
		for _, field := range resolution.Create.PrefillMappings {
			mappings = append(mappings, &runtimev1.ResolvedRelatedFieldMapping{
				TargetFieldId: field.TargetFieldID, SourceFieldId: field.SourceFieldID,
			})
		}
		related = append(related, &runtimev1.ResolvedRelatedList{
			DisplayObjectId: resolution.DisplayObjectID, DisplayLookupField: resolution.DisplayLookupField,
			Query: &runtimev1.ResolvedRelatedQueryTarget{
				ObjectId: resolution.Query.ObjectID, LookupField: resolution.Query.LookupField,
				ParentFieldId: resolution.Query.ParentFieldID,
			},
			Detail: &runtimev1.ResolvedRelatedDetailTarget{ObjectId: resolution.Detail.ObjectID},
			List: &runtimev1.ResolvedRelatedListTarget{
				ObjectId: resolution.List.ObjectID, FilterField: resolution.List.FilterField,
				ParentFieldId: resolution.List.ParentFieldID,
			},
			Create: &runtimev1.ResolvedRelatedCreateTarget{
				Kind: resolution.Create.Kind, ObjectId: resolution.Create.ObjectID, PrefillMappings: mappings,
			},
		})
	}
	return &runtimev1.ResolvedPageLayout{
		LayoutId: value.LayoutID, DocumentKind: value.DocumentKind,
		DocumentFormatVersion: value.DocumentFormatVersion,
		LayoutDocument:        append([]byte(nil), value.LayoutDocument...), RelatedLists: related,
	}
}

func FromWireResolvePageLayoutResponse(request semantic.Request, response *runtimev1.ResolvePageLayoutResponse) (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))
	}
	if len(response.GetSelections()) > semantic.MaxRecordTypeSelectors ||
		len(response.GetLayouts()) > semantic.MaxRecordTypeSelectors {
		return semantic.Response{}, &semantic.Failure{
			Category:  semantic.FailureResourceExhausted,
			LimitKind: semantic.LimitRecordCount, Maximum: semantic.MaxRecordTypeSelectors,
		}
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.Response{}, err
	}
	mapped := semantic.Response{
		ObjectID:   response.GetObjectId(),
		Selections: make([]semantic.Selection, 0, len(response.GetSelections())),
		Layouts:    make([]semantic.ResolvedLayout, 0, len(response.GetLayouts())),
		Receipts:   receipts,
	}
	for _, value := range response.GetSelections() {
		if value == nil {
			return semantic.Response{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		mapped.Selections = append(mapped.Selections, semantic.Selection{
			RecordTypeID: value.RecordTypeId, LayoutID: value.GetLayoutId(),
		})
	}
	for _, value := range response.GetLayouts() {
		layout, mapErr := fromWireResolvedPageLayout(value)
		if mapErr != nil {
			return semantic.Response{}, mapErr
		}
		mapped.Layouts = append(mapped.Layouts, layout)
	}
	if err := semantic.ValidateResponseForRequest(request, mapped); err != nil {
		return semantic.Response{}, err
	}
	return mapped, nil
}

func fromWireResolvedPageLayout(value *runtimev1.ResolvedPageLayout) (semantic.ResolvedLayout, error) {
	if value == nil {
		return semantic.ResolvedLayout{}, &semantic.Failure{Category: semantic.FailureInternal}
	}
	if len(value.GetRelatedLists()) > semantic.MaxRelatedListResolutions {
		return semantic.ResolvedLayout{}, &semantic.Failure{
			Category:  semantic.FailureResourceExhausted,
			LimitKind: semantic.LimitRecordCount, Maximum: semantic.MaxRelatedListResolutions,
		}
	}
	mapped := semantic.ResolvedLayout{
		LayoutID: value.GetLayoutId(), DocumentKind: value.GetDocumentKind(),
		DocumentFormatVersion: value.GetDocumentFormatVersion(),
		LayoutDocument:        append([]byte(nil), value.GetLayoutDocument()...),
		RelatedLists:          make([]semantic.RelatedListResolution, 0, len(value.GetRelatedLists())),
	}
	for _, resolution := range value.GetRelatedLists() {
		if resolution == nil || resolution.GetQuery() == nil || resolution.GetDetail() == nil ||
			resolution.GetList() == nil || resolution.GetCreate() == nil {
			return semantic.ResolvedLayout{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		create := semantic.ResolvedCreateTarget{
			Kind: resolution.GetCreate().GetKind(), ObjectID: resolution.GetCreate().GetObjectId(),
		}
		if len(resolution.GetCreate().GetPrefillMappings()) > semantic.MaxPrefillMappingsPerResolution {
			return semantic.ResolvedLayout{}, &semantic.Failure{
				Category:  semantic.FailureResourceExhausted,
				LimitKind: semantic.LimitRecordCount, Maximum: semantic.MaxPrefillMappingsPerResolution,
			}
		}
		for _, field := range resolution.GetCreate().GetPrefillMappings() {
			if field == nil {
				return semantic.ResolvedLayout{}, &semantic.Failure{Category: semantic.FailureInternal}
			}
			create.PrefillMappings = append(create.PrefillMappings, semantic.FieldMapping{
				TargetFieldID: field.GetTargetFieldId(), SourceFieldID: field.GetSourceFieldId(),
			})
		}
		mapped.RelatedLists = append(mapped.RelatedLists, semantic.RelatedListResolution{
			DisplayObjectID: resolution.GetDisplayObjectId(), DisplayLookupField: resolution.GetDisplayLookupField(),
			Query: semantic.ResolvedQueryTarget{
				ObjectID: resolution.GetQuery().GetObjectId(), LookupField: resolution.GetQuery().GetLookupField(),
				ParentFieldID: resolution.GetQuery().GetParentFieldId(),
			},
			Detail: semantic.ResolvedDetailTarget{ObjectID: resolution.GetDetail().GetObjectId()},
			List: semantic.ResolvedListTarget{
				ObjectID: resolution.GetList().GetObjectId(), FilterField: resolution.GetList().GetFilterField(),
				ParentFieldID: resolution.GetList().GetParentFieldId(),
			},
			Create: create,
		})
	}
	return mapped, nil
}
