package mapping

import (
	"context"

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

func ToWireCountRelatedRecordsRequest(ctx context.Context, request semantic.Request) (*runtimev1.CountRelatedRecordsRequest, error) {
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, err
	}
	counts := make([]*runtimev1.RelatedRecordCountSpec, 0, len(request.Counts))
	for _, count := range request.Counts {
		counts = append(counts, &runtimev1.RelatedRecordCountSpec{
			ParentRecordId: count.ParentRecordID, ChildObjectId: count.ChildObjectID,
			RelationshipField: count.RelationshipField, MatchValue: count.MatchValue,
		})
	}
	return &runtimev1.CountRelatedRecordsRequest{Counts: counts, MinCatalogRevision: request.MinCatalogRevision}, nil
}

func FromWireCountRelatedRecordsRequest(ctx context.Context, request *runtimev1.CountRelatedRecordsRequest) (semantic.Request, error) {
	if request == nil {
		return semantic.Request{}, relatedCountInvalid(semantic.InvalidComponentResponse)
	}
	if len(request.GetCounts()) > semantic.MaxCounts {
		return semantic.Request{}, &semantic.Failure{
			Category: semantic.FailureResourceExhausted, LimitKind: semantic.LimitOperationCount, Maximum: semantic.MaxCounts,
		}
	}
	result := semantic.Request{
		Counts: make([]semantic.CountSpec, 0, len(request.GetCounts())), MinCatalogRevision: request.MinCatalogRevision,
	}
	for _, count := range request.GetCounts() {
		if count == nil {
			return semantic.Request{}, relatedCountInvalid(semantic.InvalidComponentResponse)
		}
		result.Counts = append(result.Counts, semantic.CountSpec{
			ParentRecordID: count.GetParentRecordId(), ChildObjectID: count.GetChildObjectId(),
			RelationshipField: count.GetRelationshipField(), MatchValue: count.GetMatchValue(),
		})
	}
	if err := semantic.ValidateRequest(ctx, result); err != nil {
		return semantic.Request{}, err
	}
	return result, nil
}

func ToWireCountRelatedRecordsResponse(request semantic.Request, response semantic.Response) (*runtimev1.CountRelatedRecordsResponse, error) {
	if err := semantic.ValidateResponse(request, response); err != nil {
		return nil, err
	}
	results := make([]*runtimev1.RelatedRecordCountResult, 0, len(response.Results))
	for _, result := range response.Results {
		results = append(results, &runtimev1.RelatedRecordCountResult{Index: result.Index, Outcome: relatedCountOutcomeToWire(result.Outcome), Count: result.Count})
	}
	return &runtimev1.CountRelatedRecordsResponse{Results: results, Receipts: toWireReceipts(response.Receipts, relatedCountCapability)}, nil
}

func FromWireCountRelatedRecordsResponse(request semantic.Request, response *runtimev1.CountRelatedRecordsResponse) (semantic.Response, error) {
	if response == nil {
		return semantic.Response{}, relatedCountIncompatible()
	}
	if len(response.GetResults()) != len(request.Counts) || len(response.GetResults()) > semantic.MaxCounts {
		return semantic.Response{}, relatedCountInternal()
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), relatedCountCapability)
	if err != nil {
		return semantic.Response{}, err
	}
	result := semantic.Response{Results: make([]semantic.Result, 0, len(response.GetResults())), Receipts: receipts}
	for _, wireResult := range response.GetResults() {
		if wireResult == nil {
			return semantic.Response{}, relatedCountInternal()
		}
		outcome, ok := relatedCountOutcomeFromWire(wireResult.GetOutcome())
		if !ok {
			return semantic.Response{}, relatedCountInternal()
		}
		result.Results = append(result.Results, semantic.Result{Index: wireResult.GetIndex(), Outcome: outcome, Count: wireResult.GetCount()})
	}
	if err := semantic.ValidateResponse(request, result); err != nil {
		return semantic.Response{}, err
	}
	return result, nil
}

func relatedCountOutcomeToWire(outcome semantic.CountOutcome) runtimev1.RelatedRecordCountOutcome {
	switch outcome {
	case semantic.CountOutcomeCounted:
		return runtimev1.RelatedRecordCountOutcome_RELATED_RECORD_COUNT_OUTCOME_COUNTED
	case semantic.CountOutcomeNotAvailable:
		return runtimev1.RelatedRecordCountOutcome_RELATED_RECORD_COUNT_OUTCOME_NOT_AVAILABLE
	default:
		return runtimev1.RelatedRecordCountOutcome_RELATED_RECORD_COUNT_OUTCOME_UNSPECIFIED
	}
}

func relatedCountOutcomeFromWire(outcome runtimev1.RelatedRecordCountOutcome) (semantic.CountOutcome, bool) {
	switch outcome {
	case runtimev1.RelatedRecordCountOutcome_RELATED_RECORD_COUNT_OUTCOME_COUNTED:
		return semantic.CountOutcomeCounted, true
	case runtimev1.RelatedRecordCountOutcome_RELATED_RECORD_COUNT_OUTCOME_NOT_AVAILABLE:
		return semantic.CountOutcomeNotAvailable, true
	default:
		return "", false
	}
}

func relatedCountInvalid(component semantic.InvalidComponent) *semantic.Failure {
	return &semantic.Failure{Category: semantic.FailureInvalidRequest, InvalidComponent: component}
}

func relatedCountIncompatible() *semantic.Failure {
	return &semantic.Failure{Category: semantic.FailureIncompatible, RequiredCapability: semantic.CapabilityName, SupportedMajor: semantic.ProtocolMajor, SupportedMinorMin: semantic.ProtocolMinor, SupportedMinorMax: semantic.ProtocolMinor}
}

func relatedCountInternal() *semantic.Failure {
	return &semantic.Failure{Category: semantic.FailureInternal}
}
